Backend Development - API Design & Server Architecture

Comprehensive guide covering essential concepts, practical examples, and best practices. Learn with step-by-step tutorials and real-world applications.

Back to Articles

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!