Introduction to Backend Development
Backend development forms the foundation of modern web applications, handling server-side logic, databases, APIs, and system architecture. A well-designed backend ensures scalability, security, and performance while providing reliable services to frontend applications and mobile clients.
"The backend is like the foundation of a house - invisible to users but crucial for everything else to work properly."
Backend Architecture Patterns
Monolithic Architecture
- Single deployable unit
- Shared database and resources
- Simpler development and testing
- Scaling challenges as app grows
Microservices Architecture
- Distributed services
- Independent deployment
- Technology diversity
- Better scalability and fault isolation
Serverless Architecture
- Function-as-a-Service (FaaS)
- Event-driven execution
- Automatic scaling
- Pay-per-execution model
RESTful API Design
REST Principles
REST API Best Practices
- Stateless: Each request contains all necessary information
- Resource-based: URLs represent resources, not actions
- HTTP Methods: Use appropriate verbs (GET, POST, PUT, DELETE)
- Consistent: Follow naming conventions and patterns
- Versioned: Support multiple API versions
API Endpoint Design
# Good REST API Design # Resources and Collections GET /api/v1/users # Get all users GET /api/v1/users/123 # Get specific user POST /api/v1/users # Create new user PUT /api/v1/users/123 # Update user (full) PATCH /api/v1/users/123 # Update user (partial) DELETE /api/v1/users/123 # Delete user # Nested Resources GET /api/v1/users/123/posts # Get user's posts POST /api/v1/users/123/posts # Create post for user # Query Parameters for Filtering GET /api/v1/users?page=2&limit=10&sort=created_at&order=desc GET /api/v1/posts?category=tech&status=published&author=john # Status Codes 200 OK # Successful GET, PUT, PATCH 201 Created # Successful POST 204 No Content # Successful DELETE 400 Bad Request # Invalid request data 401 Unauthorized # Authentication required 403 Forbidden # Access denied 404 Not Found # Resource doesn't exist 422 Unprocessable Entity # Validation errors 500 Internal Server Error # Server error
Node.js Backend Development
Express.js API Server
Express.js Setup
const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const { body, validationResult } = require('express-validator'); const app = express(); // Middleware app.use(helmet()); // Security headers app.use(cors()); // CORS support app.use(express.json({ limit: '10mb' })); // JSON parsing app.use(express.urlencoded({ extended: true })); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }); app.use('/api/', limiter); // Error handling middleware const errorHandler = (err, req, res, next) => { console.error(err.stack); if (err.name === 'ValidationError') { return res.status(400).json({ success: false, message: 'Validation Error', errors: err.errors }); } if (err.name === 'UnauthorizedError') { return res.status(401).json({ success: false, message: 'Unauthorized access' }); } res.status(500).json({ success: false, message: 'Internal server error' }); }; // Routes app.use('/api/auth', require('./routes/auth')); app.use('/api/users', require('./routes/users')); app.use('/api/posts', require('./routes/posts')); app.use(errorHandler); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });
Database Integration with Mongoose
const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); // User Schema const userSchema = new mongoose.Schema({ username: { type: String, required: [true, 'Username is required'], unique: true, trim: true, minlength: [3, 'Username must be at least 3 characters'], maxlength: [30, 'Username cannot exceed 30 characters'] }, email: { type: String, required: [true, 'Email is required'], unique: true, lowercase: true, validate: { validator: function(email) { return /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(email); }, message: 'Please enter a valid email' } }, password: { type: String, required: [true, 'Password is required'], minlength: [6, 'Password must be at least 6 characters'], select: false // Don't include in queries by default }, profile: { firstName: String, lastName: String, avatar: String, bio: String }, role: { type: String, enum: ['user', 'admin', 'moderator'], default: 'user' }, isActive: { type: Boolean, default: true }, lastLogin: Date }, { timestamps: true }); // Index for performance userSchema.index({ email: 1 }); userSchema.index({ username: 1 }); // Hash password before saving userSchema.pre('save', async function(next) { if (!this.isModified('password')) return next(); const salt = await bcrypt.genSalt(12); this.password = await bcrypt.hash(this.password, salt); next(); }); // Compare password method userSchema.methods.comparePassword = async function(candidatePassword) { return await bcrypt.compare(candidatePassword, this.password); }; // Generate JWT token userSchema.methods.generateAuthToken = function() { const payload = { id: this._id, username: this.username, role: this.role }; return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRE || '24h' }); }; // Virtual for full name userSchema.virtual('fullName').get(function() { return `${this.profile.firstName} ${this.profile.lastName}`.trim(); }); module.exports = mongoose.model('User', userSchema);
Authentication and Authorization
const jwt = require('jsonwebtoken'); const User = require('../models/User'); // Authentication middleware const authenticate = async (req, res, next) => { try { const token = req.header('Authorization')?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ success: false, message: 'Access token required' }); } const decoded = jwt.verify(token, process.env.JWT_SECRET); const user = await User.findById(decoded.id); if (!user || !user.isActive) { return res.status(401).json({ success: false, message: 'Invalid token or user not found' }); } req.user = user; next(); } catch (error) { res.status(401).json({ success: false, message: 'Invalid token' }); } }; // Authorization middleware const authorize = (...roles) => { return (req, res, next) => { if (!roles.includes(req.user.role)) { return res.status(403).json({ success: false, message: 'Access denied. Insufficient permissions' }); } next(); }; }; // Auth routes const express = require('express'); const router = express.Router(); // Register router.post('/register', [ body('username').isLength({ min: 3 }).trim(), body('email').isEmail().normalizeEmail(), body('password').isLength({ min: 6 }) ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Validation errors', errors: errors.array() }); } const { username, email, password } = req.body; // Check if user exists const existingUser = await User.findOne({ $or: [{ email }, { username }] }); if (existingUser) { return res.status(400).json({ success: false, message: 'User already exists' }); } // Create user const user = new User({ username, email, password }); await user.save(); const token = user.generateAuthToken(); res.status(201).json({ success: true, message: 'User registered successfully', token, user: { id: user._id, username: user.username, email: user.email, role: user.role } }); } catch (error) { res.status(500).json({ success: false, message: 'Registration failed', error: error.message }); } }); // Login router.post('/login', async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }).select('+password'); if (!user || !(await user.comparePassword(password))) { return res.status(401).json({ success: false, message: 'Invalid credentials' }); } // Update last login user.lastLogin = new Date(); await user.save(); const token = user.generateAuthToken(); res.json({ success: true, message: 'Login successful', token, user: { id: user._id, username: user.username, email: user.email, role: user.role } }); } catch (error) { res.status(500).json({ success: false, message: 'Login failed', error: error.message }); } }); module.exports = router;
Python Backend with FastAPI
FastAPI Application Setup
FastAPI Project Structure
from fastapi import FastAPI, HTTPException, Depends, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, EmailStr from sqlalchemy import create_engine, Column, Integer, String, DateTime, Boolean from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, Session from passlib.context import CryptContext from jose import JWTError, jwt from datetime import datetime, timedelta import os app = FastAPI( title="My API", description="A comprehensive API built with FastAPI", version="1.0.0" ) # CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Database setup SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./app.db") engine = create_engine(SQLALCHEMY_DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() # Password hashing pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # JWT settings SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 security = HTTPBearer() # User model class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, index=True) email = Column(String, unique=True, index=True) hashed_password = Column(String) is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=datetime.utcnow) Base.metadata.create_all(bind=engine) # Pydantic models class UserCreate(BaseModel): username: str email: EmailStr password: str class UserResponse(BaseModel): id: int username: str email: str is_active: bool created_at: datetime class Config: from_attributes = True class Token(BaseModel): access_token: str token_type: str # Dependency to get database session def get_db(): db = SessionLocal() try: yield db finally: db.close() # Utility functions def verify_password(plain_password, hashed_password): return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password): return pwd_context.hash(password) def create_access_token(data: dict): to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db) ): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: token = credentials.credentials payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception user = db.query(User).filter(User.username == username).first() if user is None: raise credentials_exception return user # Routes @app.post("/register", response_model=UserResponse) async def register_user(user: UserCreate, db: Session = Depends(get_db)): # Check if user exists db_user = db.query(User).filter( (User.email == user.email) | (User.username == user.username) ).first() if db_user: raise HTTPException( status_code=400, detail="Username or email already registered" ) # Create new user hashed_password = get_password_hash(user.password) db_user = User( username=user.username, email=user.email, hashed_password=hashed_password ) db.add(db_user) db.commit() db.refresh(db_user) return db_user @app.post("/login", response_model=Token) async def login_user(email: str, password: str, db: Session = Depends(get_db)): user = db.query(User).filter(User.email == email).first() if not user or not verify_password(password, user.hashed_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"} @app.get("/users/me", response_model=UserResponse) async def read_users_me(current_user: User = Depends(get_current_user)): return current_user if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
GraphQL APIs
GraphQL with Apollo Server
const { ApolloServer, gql } = require('apollo-server-express'); const { GraphQLScalarType } = require('graphql'); const { Kind } = require('graphql/language'); // Type definitions const typeDefs = gql` scalar DateTime type User { id: ID! username: String! email: String! profile: Profile posts: [Post!]! createdAt: DateTime! } type Profile { firstName: String lastName: String bio: String avatar: String } type Post { id: ID! title: String! content: String! author: User! published: Boolean! createdAt: DateTime! updatedAt: DateTime! } input CreatePostInput { title: String! content: String! published: Boolean = false } input UpdatePostInput { title: String content: String published: Boolean } type Query { users: [User!]! user(id: ID!): User posts: [Post!]! post(id: ID!): Post myPosts: [Post!]! } type Mutation { createPost(input: CreatePostInput!): Post! updatePost(id: ID!, input: UpdatePostInput!): Post! deletePost(id: ID!): Boolean! } type Subscription { postAdded: Post! postUpdated: Post! } `; // Custom scalar for DateTime const dateTimeScalar = new GraphQLScalarType({ name: 'DateTime', description: 'DateTime custom scalar type', serialize(value) { return value.toISOString(); }, parseValue(value) { return new Date(value); }, parseLiteral(ast) { if (ast.kind === Kind.STRING) { return new Date(ast.value); } return null; }, }); // Resolvers const resolvers = { DateTime: dateTimeScalar, Query: { users: async () => { return await User.find().populate('profile'); }, user: async (_, { id }) => { return await User.findById(id).populate('profile'); }, posts: async () => { return await Post.find().populate('author'); }, post: async (_, { id }) => { return await Post.findById(id).populate('author'); }, myPosts: async (_, __, { user }) => { if (!user) throw new Error('Authentication required'); return await Post.find({ author: user.id }); } }, Mutation: { createPost: async (_, { input }, { user }) => { if (!user) throw new Error('Authentication required'); const post = new Post({ ...input, author: user.id }); await post.save(); await post.populate('author'); // Publish subscription pubsub.publish('POST_ADDED', { postAdded: post }); return post; }, updatePost: async (_, { id, input }, { user }) => { if (!user) throw new Error('Authentication required'); const post = await Post.findById(id); if (!post) throw new Error('Post not found'); if (post.author.toString() !== user.id) { throw new Error('Not authorized'); } Object.assign(post, input); post.updatedAt = new Date(); await post.save(); await post.populate('author'); pubsub.publish('POST_UPDATED', { postUpdated: post }); return post; }, deletePost: async (_, { id }, { user }) => { if (!user) throw new Error('Authentication required'); const post = await Post.findById(id); if (!post) throw new Error('Post not found'); if (post.author.toString() !== user.id) { throw new Error('Not authorized'); } await Post.findByIdAndDelete(id); return true; } }, Subscription: { postAdded: { subscribe: () => pubsub.asyncIterator(['POST_ADDED']) }, postUpdated: { subscribe: () => pubsub.asyncIterator(['POST_UPDATED']) } }, User: { posts: async (parent) => { return await Post.find({ author: parent.id }); } }, Post: { author: async (parent) => { return await User.findById(parent.author); } } };
Microservices Architecture
Service Decomposition
- Decompose by business capability
- Single responsibility per service
- Loose coupling between services
- Database per service pattern
Inter-Service Communication
- Synchronous: HTTP/REST, gRPC
- Asynchronous: Message queues, Events
- Service mesh for complex routing
- Circuit breaker pattern
Service Discovery
- Service registry (Consul, Eureka)
- Load balancing strategies
- Health checks and monitoring
- API gateway pattern
Message Queue with Redis
const Queue = require('bull'); const redis = require('redis'); // Create Redis connection const redisClient = redis.createClient({ host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379 }); // Create queues const emailQueue = new Queue('email processing', { redis: { host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379 } }); const imageQueue = new Queue('image processing', { redis: { host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379 } }); // Email processing job emailQueue.process('send-email', async (job) => { const { to, subject, html, attachments } = job.data; try { // Send email using nodemailer or other service await sendEmail({ to, subject, html, attachments }); console.log(`Email sent to ${to}`); return { success: true, recipient: to }; } catch (error) { console.error('Email sending failed:', error); throw error; } }); // Image processing job imageQueue.process('resize-image', 5, async (job) => { const { imagePath, sizes } = job.data; try { const results = []; for (const size of sizes) { const resizedPath = await resizeImage(imagePath, size); results.push({ size, path: resizedPath }); // Update progress const progress = (results.length / sizes.length) * 100; job.progress(progress); } return results; } catch (error) { console.error('Image processing failed:', error); throw error; } }); // Add jobs to queues const addEmailJob = async (emailData) => { const job = await emailQueue.add('send-email', emailData, { attempts: 3, backoff: { type: 'exponential', delay: 2000 } }); return job.id; }; const addImageProcessingJob = async (imageData) => { const job = await imageQueue.add('resize-image', imageData, { attempts: 2, removeOnComplete: 10, removeOnFail: 5 }); return job.id; }; // Queue event handlers emailQueue.on('completed', (job, result) => { console.log(`Email job ${job.id} completed:`, result); }); emailQueue.on('failed', (job, err) => { console.log(`Email job ${job.id} failed:`, err.message); }); imageQueue.on('progress', (job, progress) => { console.log(`Image job ${job.id} progress: ${progress}%`); }); module.exports = { emailQueue, imageQueue, addEmailJob, addImageProcessingJob };
API Documentation and Testing
API Documentation with Swagger
const swaggerJsdoc = require('swagger-jsdoc'); const swaggerUi = require('swagger-ui-express'); const options = { definition: { openapi: '3.0.0', info: { title: 'My API', version: '1.0.0', description: 'A comprehensive API documentation', }, servers: [ { url: 'http://localhost:3000/api', description: 'Development server', }, ], components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', }, }, }, }, apis: ['./routes/*.js'], // paths to files containing OpenAPI definitions }; const specs = swaggerJsdoc(options); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); /** * @swagger * /users: * get: * summary: Get all users * tags: [Users] * security: * - bearerAuth: [] * parameters: * - in: query * name: page * schema: * type: integer * description: Page number * - in: query * name: limit * schema: * type: integer * description: Number of items per page * responses: * 200: * description: List of users * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * data: * type: array * items: * $ref: '#/components/schemas/User' * pagination: * $ref: '#/components/schemas/Pagination' */
API Testing with Jest
const request = require('supertest'); const app = require('../app'); const User = require('../models/User'); describe('User API', () => { let authToken; let userId; beforeAll(async () => { // Setup test database await User.deleteMany({}); }); afterAll(async () => { // Cleanup await User.deleteMany({}); }); describe('POST /api/auth/register', () => { test('should register a new user', async () => { const userData = { username: 'testuser', email: 'test@example.com', password: 'password123' }; const response = await request(app) .post('/api/auth/register') .send(userData); expect(response.status).toBe(201); expect(response.body.success).toBe(true); expect(response.body.user.email).toBe(userData.email); expect(response.body.token).toBeDefined(); authToken = response.body.token; userId = response.body.user.id; }); test('should not register user with existing email', async () => { const userData = { username: 'testuser2', email: 'test@example.com', password: 'password123' }; const response = await request(app) .post('/api/auth/register') .send(userData); expect(response.status).toBe(400); expect(response.body.success).toBe(false); }); }); describe('POST /api/auth/login', () => { test('should login with valid credentials', async () => { const loginData = { email: 'test@example.com', password: 'password123' }; const response = await request(app) .post('/api/auth/login') .send(loginData); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.token).toBeDefined(); }); test('should not login with invalid credentials', async () => { const loginData = { email: 'test@example.com', password: 'wrongpassword' }; const response = await request(app) .post('/api/auth/login') .send(loginData); expect(response.status).toBe(401); expect(response.body.success).toBe(false); }); }); describe('GET /api/users/me', () => { test('should get current user with valid token', async () => { const response = await request(app) .get('/api/users/me') .set('Authorization', `Bearer ${authToken}`); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.user.id).toBe(userId); }); test('should not get user without token', async () => { const response = await request(app) .get('/api/users/me'); expect(response.status).toBe(401); }); }); });
Performance and Scalability
Caching Strategies
- Redis for application-level caching
- CDN for static content delivery
- Database query result caching
- HTTP response caching headers
Load Balancing
- Round-robin distribution
- Least connections algorithm
- Geographic load balancing
- Health check monitoring
Database Optimization
- Index optimization
- Connection pooling
- Read replicas for scaling reads
- Database sharding strategies
Monitoring and Logging
Application Monitoring
const winston = require('winston'); const morgan = require('morgan'); // Configure winston logger const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'user-service' }, transports: [ new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }), ], }); if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() })); } // HTTP request logging app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } })); // Performance monitoring middleware const performanceMonitor = (req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; logger.info('Request performance', { method: req.method, url: req.url, statusCode: res.statusCode, duration: `${duration}ms`, userAgent: req.get('User-Agent'), ip: req.ip }); // Alert on slow requests if (duration > 1000) { logger.warn('Slow request detected', { method: req.method, url: req.url, duration: `${duration}ms` }); } }); next(); }; app.use(performanceMonitor); module.exports = logger;
Deployment and DevOps
Docker Configuration
# Dockerfile FROM node:18-alpine WORKDIR /app # Copy package files COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Copy source code COPY . . # Create non-root user RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 # Change ownership RUN chown -R nextjs:nodejs /app USER nextjs EXPOSE 3000 CMD ["npm", "start"] # docker-compose.yml version: '3.8' services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=mongodb://mongo:27017/myapp - REDIS_URL=redis://redis:6379 depends_on: - mongo - redis restart: unless-stopped mongo: image: mongo:5 ports: - "27017:27017" volumes: - mongo_data:/data/db restart: unless-stopped redis: image: redis:7-alpine ports: - "6379:6379" restart: unless-stopped nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - app restart: unless-stopped volumes: mongo_data:
Conclusion
Backend development requires understanding multiple concepts from API design to system architecture. Focus on building secure, scalable, and maintainable systems. Start with simple applications and gradually incorporate advanced patterns like microservices, caching, and monitoring.
Practice building different types of APIs, experiment with various databases, and always consider security and performance from the beginning. The backend is the foundation of great applications!