Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    wshobson

    nodejs-backend-patterns

    wshobson/nodejs-backend-patterns
    Coding
    28,185
    8 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Build production-ready Node.js backend services with Express/Fastify, implementing middleware patterns, error handling, authentication, database integration, and API design best practices...

    SKILL.md

    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:

    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:

    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:

    // 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:

    // 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:

    // 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.

    Middleware Patterns

    Authentication Middleware

    // 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

    // 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

    // 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

    // 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

    // 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

    // 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:

    • 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.

    Caching Strategies

    Redis-backed CacheService with get/set/delete/invalidatePattern, plus a @Cacheable decorator for method-level caching. See references/advanced-patterns.md.

    API Response Format

    Standardized ApiResponse helper with success, error, and paginated static methods. See references/advanced-patterns.md.

    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.

    Recommended Servers
    Supabase
    Supabase
    Vercel Grep
    Vercel Grep
    InstantDB
    InstantDB
    Repository
    wshobson/agents
    Files