Claude Cursor Skill

nodejs-backend-patterns

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 architectu

LLM Mart · 0 points · 12 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download wshobson-agents-plugins_javascript-typescript_skills_nodejs-backend-patterns-554237f.zip · 8 KB
Part of wshobson/agents — 170 skills

Install

skills CLI npx skills add https://github.com/wshobson/agents/tree/main/plugins/javascript-typescript/skills/nodejs-backend-patterns
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wshobson-agents@llmmart
Git git clone https://github.com/wshobson/agents.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wshobson/agents collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

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

Detailed patterns and worked examples

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.

Files (agents)
  • references
    • advanced-patterns.md 9.2 KB
      # Node.js Advanced Patterns
      
      Advanced patterns for dependency injection, database integration, authentication, caching, and API response formatting.
      
      ## Dependency Injection
      
      ### DI Container
      
      ```typescript
      // di-container.ts
      import { Pool } from "pg";
      import { UserRepository } from "./repositories/user.repository";
      import { UserService } from "./services/user.service";
      import { UserController } from "./controllers/user.controller";
      import { AuthService } from "./services/auth.service";
      
      class Container {
        private instances = new Map<string, any>();
      
        register<T>(key: string, factory: () => T): void {
          this.instances.set(key, factory);
        }
      
        resolve<T>(key: string): T {
          const factory = this.instances.get(key);
          if (!factory) {
            throw new Error(`No factory registered for ${key}`);
          }
          return factory();
        }
      
        singleton<T>(key: string, factory: () => T): void {
          let instance: T;
          this.instances.set(key, () => {
            if (!instance) {
              instance = factory();
            }
            return instance;
          });
        }
      }
      
      export const container = new Container();
      
      // Register dependencies
      container.singleton(
        "db",
        () =>
          new Pool({
            host: process.env.DB_HOST,
            port: parseInt(process.env.DB_PORT || "5432"),
            database: process.env.DB_NAME,
            user: process.env.DB_USER,
            password: process.env.DB_PASSWORD,
            max: 20,
            idleTimeoutMillis: 30000,
            connectionTimeoutMillis: 2000,
          }),
      );
      
      container.singleton(
        "userRepository",
        () => new UserRepository(container.resolve("db")),
      );
      
      container.singleton(
        "userService",
        () => new UserService(container.resolve("userRepository")),
      );
      
      container.register(
        "userController",
        () => new UserController(container.resolve("userService")),
      );
      
      container.singleton(
        "authService",
        () => new AuthService(container.resolve("userRepository")),
      );
      ```
      
      ## Database Patterns
      
      ### PostgreSQL with Connection Pool
      
      ```typescript
      // config/database.ts
      import { Pool, PoolConfig } from "pg";
      
      const poolConfig: PoolConfig = {
        host: process.env.DB_HOST,
        port: parseInt(process.env.DB_PORT || "5432"),
        database: process.env.DB_NAME,
        user: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        max: 20,
        idleTimeoutMillis: 30000,
        connectionTimeoutMillis: 2000,
      };
      
      export const pool = new Pool(poolConfig);
      
      // Test connection
      pool.on("connect", () => {
        console.log("Database connected");
      });
      
      pool.on("error", (err) => {
        console.error("Unexpected database error", err);
        process.exit(-1);
      });
      
      // Graceful shutdown
      export const closeDatabase = async () => {
        await pool.end();
        console.log("Database connection closed");
      };
      ```
      
      ### MongoDB with Mongoose
      
      ```typescript
      // config/mongoose.ts
      import mongoose from "mongoose";
      
      const connectDB = async () => {
        try {
          await mongoose.connect(process.env.MONGODB_URI!, {
            maxPoolSize: 10,
            serverSelectionTimeoutMS: 5000,
            socketTimeoutMS: 45000,
          });
      
          console.log("MongoDB connected");
        } catch (error) {
          console.error("MongoDB connection error:", error);
          process.exit(1);
        }
      };
      
      mongoose.connection.on("disconnected", () => {
        console.log("MongoDB disconnected");
      });
      
      mongoose.connection.on("error", (err) => {
        console.error("MongoDB error:", err);
      });
      
      export { connectDB };
      
      // Model example
      import { Schema, model, Document } from "mongoose";
      
      interface IUser extends Document {
        name: string;
        email: string;
        password: string;
        createdAt: Date;
        updatedAt: Date;
      }
      
      const userSchema = new Schema<IUser>(
        {
          name: { type: String, required: true },
          email: { type: String, required: true, unique: true },
          password: { type: String, required: true },
        },
        {
          timestamps: true,
        },
      );
      
      // Indexes
      userSchema.index({ email: 1 });
      
      export const User = model<IUser>("User", userSchema);
      ```
      
      ### Transaction Pattern
      
      ```typescript
      // services/order.service.ts
      import { Pool } from "pg";
      
      export class OrderService {
        constructor(private db: Pool) {}
      
        async createOrder(userId: string, items: any[]) {
          const client = await this.db.connect();
      
          try {
            await client.query("BEGIN");
      
            // Create order
            const orderResult = await client.query(
              "INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
              [userId, calculateTotal(items)],
            );
            const orderId = orderResult.rows[0].id;
      
            // Create order items
            for (const item of items) {
              await client.query(
                "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)",
                [orderId, item.productId, item.quantity, item.price],
              );
      
              // Update inventory
              await client.query(
                "UPDATE products SET stock = stock - $1 WHERE id = $2",
                [item.quantity, item.productId],
              );
            }
      
            await client.query("COMMIT");
            return orderId;
          } catch (error) {
            await client.query("ROLLBACK");
            throw error;
          } finally {
            client.release();
          }
        }
      }
      ```
      
      ## Authentication & Authorization
      
      ### JWT Authentication
      
      ```typescript
      // services/auth.service.ts
      import jwt from "jsonwebtoken";
      import bcrypt from "bcrypt";
      import { UserRepository } from "../repositories/user.repository";
      import { UnauthorizedError } from "../utils/errors";
      
      export class AuthService {
        constructor(private userRepository: UserRepository) {}
      
        async login(email: string, password: string) {
          const user = await this.userRepository.findByEmail(email);
      
          if (!user) {
            throw new UnauthorizedError("Invalid credentials");
          }
      
          const isValid = await bcrypt.compare(password, user.password);
      
          if (!isValid) {
            throw new UnauthorizedError("Invalid credentials");
          }
      
          const token = this.generateToken({
            userId: user.id,
            email: user.email,
          });
      
          const refreshToken = this.generateRefreshToken({
            userId: user.id,
          });
      
          return {
            token,
            refreshToken,
            user: {
              id: user.id,
              name: user.name,
              email: user.email,
            },
          };
        }
      
        async refreshToken(refreshToken: string) {
          try {
            const payload = jwt.verify(
              refreshToken,
              process.env.REFRESH_TOKEN_SECRET!,
            ) as { userId: string };
      
            const user = await this.userRepository.findById(payload.userId);
      
            if (!user) {
              throw new UnauthorizedError("User not found");
            }
      
            const token = this.generateToken({
              userId: user.id,
              email: user.email,
            });
      
            return { token };
          } catch (error) {
            throw new UnauthorizedError("Invalid refresh token");
          }
        }
      
        private generateToken(payload: any): string {
          return jwt.sign(payload, process.env.JWT_SECRET!, {
            expiresIn: "15m",
          });
        }
      
        private generateRefreshToken(payload: any): string {
          return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, {
            expiresIn: "7d",
          });
        }
      }
      ```
      
      ## Caching Strategies
      
      ```typescript
      // utils/cache.ts
      import Redis from "ioredis";
      
      const redis = new Redis({
        host: process.env.REDIS_HOST,
        port: parseInt(process.env.REDIS_PORT || "6379"),
        retryStrategy: (times) => {
          const delay = Math.min(times * 50, 2000);
          return delay;
        },
      });
      
      export class CacheService {
        async get<T>(key: string): Promise<T | null> {
          const data = await redis.get(key);
          return data ? JSON.parse(data) : null;
        }
      
        async set(key: string, value: any, ttl?: number): Promise<void> {
          const serialized = JSON.stringify(value);
          if (ttl) {
            await redis.setex(key, ttl, serialized);
          } else {
            await redis.set(key, serialized);
          }
        }
      
        async delete(key: string): Promise<void> {
          await redis.del(key);
        }
      
        async invalidatePattern(pattern: string): Promise<void> {
          const keys = await redis.keys(pattern);
          if (keys.length > 0) {
            await redis.del(...keys);
          }
        }
      }
      
      // Cache decorator
      export function Cacheable(ttl: number = 300) {
        return function (
          target: any,
          propertyKey: string,
          descriptor: PropertyDescriptor,
        ) {
          const originalMethod = descriptor.value;
      
          descriptor.value = async function (...args: any[]) {
            const cache = new CacheService();
            const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
      
            const cached = await cache.get(cacheKey);
            if (cached) {
              return cached;
            }
      
            const result = await originalMethod.apply(this, args);
            await cache.set(cacheKey, result, ttl);
      
            return result;
          };
      
          return descriptor;
        };
      }
      ```
      
      ## API Response Format
      
      ```typescript
      // utils/response.ts
      import { Response } from "express";
      
      export class ApiResponse {
        static success<T>(
          res: Response,
          data: T,
          message?: string,
          statusCode = 200,
        ) {
          return res.status(statusCode).json({
            status: "success",
            message,
            data,
          });
        }
      
        static error(res: Response, message: string, statusCode = 500, errors?: any) {
          return res.status(statusCode).json({
            status: "error",
            message,
            ...(errors && { errors }),
          });
        }
      
        static paginated<T>(
          res: Response,
          data: T[],
          page: number,
          limit: number,
          total: number,
        ) {
          return res.json({
            status: "success",
            data,
            pagination: {
              page,
              limit,
              total,
              pages: Math.ceil(total / limit),
            },
          });
        }
      }
      ```
      
    • details.md 14.7 KB
      # nodejs-backend-patterns — detailed patterns and worked examples
      
      ## 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
      
      Standardized `ApiResponse` helper with `success`, `error`, and `paginated` static methods. See [references/advanced-patterns.md](references/advanced-patterns.md).
      
  • SKILL.md 1.9 KB
    ---
    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
    
    ## Detailed patterns and worked examples
    
    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.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related