nodejs-backend-patterns · diff
git:20260326.3797fc8 to git:20260522.be57c0b
2 added, 596 removed. Audit A to A.
---
name: nodejs-backend-patterns
description: Build production-ready Node.js backend services with Express/Fastify, implementing middleware patterns, error handling, authentication, database integration, and API design best practices. Use when creating Node.js servers, REST APIs, GraphQL backends, or microservices architectures.
---
# Node.js Backend Patterns
Comprehensive guidance for building scalable, maintainable, and production-ready Node.js backend applications with modern frameworks, architectural patterns, and best practices.
## When to Use This Skill
- Building REST APIs or GraphQL servers
- Creating microservices with Node.js
- Implementing authentication and authorization
- Designing scalable backend architectures
- Setting up middleware and error handling
- Integrating databases (SQL and NoSQL)
- Building real-time applications with WebSockets
- Implementing background job processing
- ## Core Frameworks
-
- ### Express.js - Minimalist Framework
-
- **Basic Setup:**
-
- ```typescript
- import express, { Request, Response, NextFunction } from "express";
- import helmet from "helmet";
- import cors from "cors";
- import compression from "compression";
-
- const app = express();
-
- // Security middleware
- app.use(helmet());
- app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));
- app.use(compression());
-
- // Body parsing
- app.use(express.json({ limit: "10mb" }));
- app.use(express.urlencoded({ extended: true, limit: "10mb" }));
-
- // Request logging
- app.use((req: Request, res: Response, next: NextFunction) => {
- console.log(`${req.method} ${req.path}`);
- next();
- });
-
- const PORT = process.env.PORT || 3000;
- app.listen(PORT, () => {
- console.log(`Server running on port ${PORT}`);
- });
- ```
-
- ### Fastify - High Performance Framework
-
- **Basic Setup:**
-
- ```typescript
- import Fastify from "fastify";
- import helmet from "@fastify/helmet";
- import cors from "@fastify/cors";
- import compress from "@fastify/compress";
-
- const fastify = Fastify({
- logger: {
- level: process.env.LOG_LEVEL || "info",
- transport: {
- target: "pino-pretty",
- options: { colorize: true },
- },
- },
- });
-
- // Plugins
- await fastify.register(helmet);
- await fastify.register(cors, { origin: true });
- await fastify.register(compress);
-
- // Type-safe routes with schema validation
- fastify.post<{
- Body: { name: string; email: string };
- Reply: { id: string; name: string };
- }>(
- "/users",
- {
- schema: {
- body: {
- type: "object",
- required: ["name", "email"],
- properties: {
- name: { type: "string", minLength: 1 },
- email: { type: "string", format: "email" },
- },
- },
- },
- },
- async (request, reply) => {
- const { name, email } = request.body;
- return { id: "123", name };
- },
- );
-
- await fastify.listen({ port: 3000, host: "0.0.0.0" });
- ```
-
- ## Architectural Patterns
-
- ### Pattern 1: Layered Architecture
-
- **Structure:**
-
- ```
- src/
- ├── controllers/ # Handle HTTP requests/responses
- ├── services/ # Business logic
- ├── repositories/ # Data access layer
- ├── models/ # Data models
- ├── middleware/ # Express/Fastify middleware
- ├── routes/ # Route definitions
- ├── utils/ # Helper functions
- ├── config/ # Configuration
- └── types/ # TypeScript types
- ```
-
- **Controller Layer:**
-
- ```typescript
- // controllers/user.controller.ts
- import { Request, Response, NextFunction } from "express";
- import { UserService } from "../services/user.service";
- import { CreateUserDTO, UpdateUserDTO } from "../types/user.types";
-
- export class UserController {
- constructor(private userService: UserService) {}
-
- async createUser(req: Request, res: Response, next: NextFunction) {
- try {
- const userData: CreateUserDTO = req.body;
- const user = await this.userService.createUser(userData);
- res.status(201).json(user);
- } catch (error) {
- next(error);
- }
- }
-
- async getUser(req: Request, res: Response, next: NextFunction) {
- try {
- const { id } = req.params;
- const user = await this.userService.getUserById(id);
- res.json(user);
- } catch (error) {
- next(error);
- }
- }
-
- async updateUser(req: Request, res: Response, next: NextFunction) {
- try {
- const { id } = req.params;
- const updates: UpdateUserDTO = req.body;
- const user = await this.userService.updateUser(id, updates);
- res.json(user);
- } catch (error) {
- next(error);
- }
- }
-
- async deleteUser(req: Request, res: Response, next: NextFunction) {
- try {
- const { id } = req.params;
- await this.userService.deleteUser(id);
- res.status(204).send();
- } catch (error) {
- next(error);
- }
- }
- }
- ```
-
- **Service Layer:**
-
- ```typescript
- // services/user.service.ts
- import { UserRepository } from "../repositories/user.repository";
- import { CreateUserDTO, UpdateUserDTO, User } from "../types/user.types";
- import { NotFoundError, ValidationError } from "../utils/errors";
- import bcrypt from "bcrypt";
-
- export class UserService {
- constructor(private userRepository: UserRepository) {}
-
- async createUser(userData: CreateUserDTO): Promise<User> {
- // Validation
- const existingUser = await this.userRepository.findByEmail(userData.email);
- if (existingUser) {
- throw new ValidationError("Email already exists");
- }
-
- // Hash password
- const hashedPassword = await bcrypt.hash(userData.password, 10);
-
- // Create user
- const user = await this.userRepository.create({
- ...userData,
- password: hashedPassword,
- });
-
- // Remove password from response
- const { password, ...userWithoutPassword } = user;
- return userWithoutPassword as User;
- }
-
- async getUserById(id: string): Promise<User> {
- const user = await this.userRepository.findById(id);
- if (!user) {
- throw new NotFoundError("User not found");
- }
- const { password, ...userWithoutPassword } = user;
- return userWithoutPassword as User;
- }
-
- async updateUser(id: string, updates: UpdateUserDTO): Promise<User> {
- const user = await this.userRepository.update(id, updates);
- if (!user) {
- throw new NotFoundError("User not found");
- }
- const { password, ...userWithoutPassword } = user;
- return userWithoutPassword as User;
- }
-
- async deleteUser(id: string): Promise<void> {
- const deleted = await this.userRepository.delete(id);
- if (!deleted) {
- throw new NotFoundError("User not found");
- }
- }
- }
- ```
-
- **Repository Layer:**
-
- ```typescript
- // repositories/user.repository.ts
- import { Pool } from "pg";
- import { CreateUserDTO, UpdateUserDTO, UserEntity } from "../types/user.types";
-
- export class UserRepository {
- constructor(private db: Pool) {}
-
- async create(
- userData: CreateUserDTO & { password: string },
- ): Promise<UserEntity> {
- const query = `
- INSERT INTO users (name, email, password)
- VALUES ($1, $2, $3)
- RETURNING id, name, email, password, created_at, updated_at
- `;
- const { rows } = await this.db.query(query, [
- userData.name,
- userData.email,
- userData.password,
- ]);
- return rows[0];
- }
-
- async findById(id: string): Promise<UserEntity | null> {
- const query = "SELECT * FROM users WHERE id = $1";
- const { rows } = await this.db.query(query, [id]);
- return rows[0] || null;
- }
-
- async findByEmail(email: string): Promise<UserEntity | null> {
- const query = "SELECT * FROM users WHERE email = $1";
- const { rows } = await this.db.query(query, [email]);
- return rows[0] || null;
- }
-
- async update(id: string, updates: UpdateUserDTO): Promise<UserEntity | null> {
- const fields = Object.keys(updates);
- const values = Object.values(updates);
-
- const setClause = fields
- .map((field, idx) => `${field} = $${idx + 2}`)
- .join(", ");
-
- const query = `
- UPDATE users
- SET ${setClause}, updated_at = CURRENT_TIMESTAMP
- WHERE id = $1
- RETURNING *
- `;
-
- const { rows } = await this.db.query(query, [id, ...values]);
- return rows[0] || null;
- }
-
- async delete(id: string): Promise<boolean> {
- const query = "DELETE FROM users WHERE id = $1";
- const { rowCount } = await this.db.query(query, [id]);
- return rowCount > 0;
- }
- }
- ```
-
- ### Pattern 2: Dependency Injection
-
- Use a DI container to wire up repositories, services, and controllers. For a full container implementation, see [references/advanced-patterns.md](references/advanced-patterns.md).
-
- ## Middleware Patterns
-
- ### Authentication Middleware
-
- ```typescript
- // middleware/auth.middleware.ts
- import { Request, Response, NextFunction } from "express";
- import jwt from "jsonwebtoken";
- import { UnauthorizedError } from "../utils/errors";
-
- interface JWTPayload {
- userId: string;
- email: string;
- }
-
- declare global {
- namespace Express {
- interface Request {
- user?: JWTPayload;
- }
- }
- }
-
- export const authenticate = async (
- req: Request,
- res: Response,
- next: NextFunction,
- ) => {
- try {
- const token = req.headers.authorization?.replace("Bearer ", "");
-
- if (!token) {
- throw new UnauthorizedError("No token provided");
- }
-
- const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
-
- req.user = payload;
- next();
- } catch (error) {
- next(new UnauthorizedError("Invalid token"));
- }
- };
-
- export const authorize = (...roles: string[]) => {
- return async (req: Request, res: Response, next: NextFunction) => {
- if (!req.user) {
- return next(new UnauthorizedError("Not authenticated"));
- }
-
- // Check if user has required role
- const hasRole = roles.some((role) => req.user?.roles?.includes(role));
-
- if (!hasRole) {
- return next(new UnauthorizedError("Insufficient permissions"));
- }
-
- next();
- };
- };
- ```
-
- ### Validation Middleware
-
- ```typescript
- // middleware/validation.middleware.ts
- import { Request, Response, NextFunction } from "express";
- import { AnyZodObject, ZodError } from "zod";
- import { ValidationError } from "../utils/errors";
-
- export const validate = (schema: AnyZodObject) => {
- return async (req: Request, res: Response, next: NextFunction) => {
- try {
- await schema.parseAsync({
- body: req.body,
- query: req.query,
- params: req.params,
- });
- next();
- } catch (error) {
- if (error instanceof ZodError) {
- const errors = error.errors.map((err) => ({
- field: err.path.join("."),
- message: err.message,
- }));
- next(new ValidationError("Validation failed", errors));
- } else {
- next(error);
- }
- }
- };
- };
-
- // Usage with Zod
- import { z } from "zod";
-
- const createUserSchema = z.object({
- body: z.object({
- name: z.string().min(1),
- email: z.string().email(),
- password: z.string().min(8),
- }),
- });
-
- router.post("/users", validate(createUserSchema), userController.createUser);
- ```
-
- ### Rate Limiting Middleware
-
- ```typescript
- // middleware/rate-limit.middleware.ts
- import rateLimit from "express-rate-limit";
- import RedisStore from "rate-limit-redis";
- import Redis from "ioredis";
-
- const redis = new Redis({
- host: process.env.REDIS_HOST,
- port: parseInt(process.env.REDIS_PORT || "6379"),
- });
-
- export const apiLimiter = rateLimit({
- store: new RedisStore({
- client: redis,
- prefix: "rl:",
- }),
- windowMs: 15 * 60 * 1000, // 15 minutes
- max: 100, // Limit each IP to 100 requests per windowMs
- message: "Too many requests from this IP, please try again later",
- standardHeaders: true,
- legacyHeaders: false,
- });
-
- export const authLimiter = rateLimit({
- store: new RedisStore({
- client: redis,
- prefix: "rl:auth:",
- }),
- windowMs: 15 * 60 * 1000,
- max: 5, // Stricter limit for auth endpoints
- skipSuccessfulRequests: true,
- });
- ```
-
- ### Request Logging Middleware
-
- ```typescript
- // middleware/logger.middleware.ts
- import { Request, Response, NextFunction } from "express";
- import pino from "pino";
-
- const logger = pino({
- level: process.env.LOG_LEVEL || "info",
- transport: {
- target: "pino-pretty",
- options: { colorize: true },
- },
- });
-
- export const requestLogger = (
- req: Request,
- res: Response,
- next: NextFunction,
- ) => {
- const start = Date.now();
-
- // Log response when finished
- res.on("finish", () => {
- const duration = Date.now() - start;
- logger.info({
- method: req.method,
- url: req.url,
- status: res.statusCode,
- duration: `${duration}ms`,
- userAgent: req.headers["user-agent"],
- ip: req.ip,
- });
- });
-
- next();
- };
-
- export { logger };
- ```
-
- ## Error Handling
-
- ### Custom Error Classes
-
- ```typescript
- // utils/errors.ts
- export class AppError extends Error {
- constructor(
- public message: string,
- public statusCode: number = 500,
- public isOperational: boolean = true,
- ) {
- super(message);
- Object.setPrototypeOf(this, AppError.prototype);
- Error.captureStackTrace(this, this.constructor);
- }
- }
-
- export class ValidationError extends AppError {
- constructor(
- message: string,
- public errors?: any[],
- ) {
- super(message, 400);
- }
- }
-
- export class NotFoundError extends AppError {
- constructor(message: string = "Resource not found") {
- super(message, 404);
- }
- }
-
- export class UnauthorizedError extends AppError {
- constructor(message: string = "Unauthorized") {
- super(message, 401);
- }
- }
-
- export class ForbiddenError extends AppError {
- constructor(message: string = "Forbidden") {
- super(message, 403);
- }
- }
-
- export class ConflictError extends AppError {
- constructor(message: string) {
- super(message, 409);
- }
- }
- ```
-
- ### Global Error Handler
-
- ```typescript
- // middleware/error-handler.ts
- import { Request, Response, NextFunction } from "express";
- import { AppError } from "../utils/errors";
- import { logger } from "./logger.middleware";
-
- export const errorHandler = (
- err: Error,
- req: Request,
- res: Response,
- next: NextFunction,
- ) => {
- if (err instanceof AppError) {
- return res.status(err.statusCode).json({
- status: "error",
- message: err.message,
- ...(err instanceof ValidationError && { errors: err.errors }),
- });
- }
-
- // Log unexpected errors
- logger.error({
- error: err.message,
- stack: err.stack,
- url: req.url,
- method: req.method,
- });
-
- // Don't leak error details in production
- const message =
- process.env.NODE_ENV === "production"
- ? "Internal server error"
- : err.message;
-
- res.status(500).json({
- status: "error",
- message,
- });
- };
-
- // Async error wrapper
- export const asyncHandler = (
- fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
- ) => {
- return (req: Request, res: Response, next: NextFunction) => {
- Promise.resolve(fn(req, res, next)).catch(next);
- };
- };
- ```
-
- ## Database Patterns
-
- Node.js supports both SQL and NoSQL databases. Use connection pooling for all production databases.
-
- Key patterns covered in [references/advanced-patterns.md](references/advanced-patterns.md):
- - **PostgreSQL with connection pool** — `pg` Pool configuration and graceful shutdown
- - **MongoDB with Mongoose** — connection management and schema definition
- - **Transaction pattern** — `BEGIN`/`COMMIT`/`ROLLBACK` with `pg` client
-
- ## Authentication & Authorization
-
- JWT-based auth with access tokens (short-lived, 15m) and refresh tokens (7d). Full `AuthService` implementation with `bcrypt` password comparison in [references/advanced-patterns.md](references/advanced-patterns.md).
-
- ## Caching Strategies
-
- Redis-backed `CacheService` with get/set/delete/invalidatePattern, plus a `@Cacheable` decorator for method-level caching. See [references/advanced-patterns.md](references/advanced-patterns.md).
-
- ## API Response Format
+ ## Detailed patterns and worked examples
- Standardized `ApiResponse` helper with `success`, `error`, and `paginated` static methods. See [references/advanced-patterns.md](references/advanced-patterns.md).
+ Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient.
## Best Practices
1. **Use TypeScript**: Type safety prevents runtime errors
2. **Implement proper error handling**: Use custom error classes
3. **Validate input**: Use libraries like Zod or Joi
4. **Use environment variables**: Never hardcode secrets
5. **Implement logging**: Use structured logging (Pino, Winston)
6. **Add rate limiting**: Prevent abuse
7. **Use HTTPS**: Always in production
8. **Implement CORS properly**: Don't use `*` in production
9. **Use dependency injection**: Easier testing and maintenance
10. **Write tests**: Unit, integration, and E2E tests
11. **Handle graceful shutdown**: Clean up resources
12. **Use connection pooling**: For databases
13. **Implement health checks**: For monitoring
14. **Use compression**: Reduce response size
15. **Monitor performance**: Use APM tools
## Testing Patterns
See `javascript-testing-patterns` skill for comprehensive testing guidance.