Nodejs Backend Patterns

Build production-ready Node.js backends with modern patterns and best practices

✨ The solution you've been looking for

Verified
Tested and verified by our team
25450 Stars

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.

nodejs backend express fastify rest-api microservices typescript authentication
Repository

See It In Action

Interactive preview & real-world examples

Live Demo
Skill Demo Animation

AI Conversation Simulator

See how users interact with this skill

User Prompt

Help me build a Node.js REST API for a user management system with Express, including authentication, validation, and database integration

Skill Processing

Analyzing request...

Agent Response

Complete API implementation with layered architecture, JWT authentication, input validation, error handling, and PostgreSQL integration

Quick Start (3 Steps)

Get up and running in minutes

1

Install

claude-code skill install nodejs-backend-patterns

claude-code skill install nodejs-backend-patterns
2

Config

3

First Trigger

@nodejs-backend-patterns help

Commands

CommandDescriptionRequired Args
@nodejs-backend-patterns building-a-rest-api-with-expressCreate a scalable REST API with proper middleware, authentication, and error handlingNone
@nodejs-backend-patterns high-performance-api-with-fastifyImplement a high-performance API using Fastify with type-safe schema validationNone
@nodejs-backend-patterns microservices-architecture-setupDesign and implement a microservices architecture with proper patterns and communicationNone

Typical Use Cases

Building a REST API with Express

Create a scalable REST API with proper middleware, authentication, and error handling

High-Performance API with Fastify

Implement a high-performance API using Fastify with type-safe schema validation

Microservices Architecture Setup

Design and implement a microservices architecture with proper patterns and communication

Overview

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:

 1import express, { Request, Response, NextFunction } from "express";
 2import helmet from "helmet";
 3import cors from "cors";
 4import compression from "compression";
 5
 6const app = express();
 7
 8// Security middleware
 9app.use(helmet());
10app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));
11app.use(compression());
12
13// Body parsing
14app.use(express.json({ limit: "10mb" }));
15app.use(express.urlencoded({ extended: true, limit: "10mb" }));
16
17// Request logging
18app.use((req: Request, res: Response, next: NextFunction) => {
19  console.log(`${req.method} ${req.path}`);
20  next();
21});
22
23const PORT = process.env.PORT || 3000;
24app.listen(PORT, () => {
25  console.log(`Server running on port ${PORT}`);
26});

Fastify - High Performance Framework

Basic Setup:

 1import Fastify from "fastify";
 2import helmet from "@fastify/helmet";
 3import cors from "@fastify/cors";
 4import compress from "@fastify/compress";
 5
 6const fastify = Fastify({
 7  logger: {
 8    level: process.env.LOG_LEVEL || "info",
 9    transport: {
10      target: "pino-pretty",
11      options: { colorize: true },
12    },
13  },
14});
15
16// Plugins
17await fastify.register(helmet);
18await fastify.register(cors, { origin: true });
19await fastify.register(compress);
20
21// Type-safe routes with schema validation
22fastify.post<{
23  Body: { name: string; email: string };
24  Reply: { id: string; name: string };
25}>(
26  "/users",
27  {
28    schema: {
29      body: {
30        type: "object",
31        required: ["name", "email"],
32        properties: {
33          name: { type: "string", minLength: 1 },
34          email: { type: "string", format: "email" },
35        },
36      },
37    },
38  },
39  async (request, reply) => {
40    const { name, email } = request.body;
41    return { id: "123", name };
42  },
43);
44
45await 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:

 1// controllers/user.controller.ts
 2import { Request, Response, NextFunction } from "express";
 3import { UserService } from "../services/user.service";
 4import { CreateUserDTO, UpdateUserDTO } from "../types/user.types";
 5
 6export class UserController {
 7  constructor(private userService: UserService) {}
 8
 9  async createUser(req: Request, res: Response, next: NextFunction) {
10    try {
11      const userData: CreateUserDTO = req.body;
12      const user = await this.userService.createUser(userData);
13      res.status(201).json(user);
14    } catch (error) {
15      next(error);
16    }
17  }
18
19  async getUser(req: Request, res: Response, next: NextFunction) {
20    try {
21      const { id } = req.params;
22      const user = await this.userService.getUserById(id);
23      res.json(user);
24    } catch (error) {
25      next(error);
26    }
27  }
28
29  async updateUser(req: Request, res: Response, next: NextFunction) {
30    try {
31      const { id } = req.params;
32      const updates: UpdateUserDTO = req.body;
33      const user = await this.userService.updateUser(id, updates);
34      res.json(user);
35    } catch (error) {
36      next(error);
37    }
38  }
39
40  async deleteUser(req: Request, res: Response, next: NextFunction) {
41    try {
42      const { id } = req.params;
43      await this.userService.deleteUser(id);
44      res.status(204).send();
45    } catch (error) {
46      next(error);
47    }
48  }
49}

Service Layer:

 1// services/user.service.ts
 2import { UserRepository } from "../repositories/user.repository";
 3import { CreateUserDTO, UpdateUserDTO, User } from "../types/user.types";
 4import { NotFoundError, ValidationError } from "../utils/errors";
 5import bcrypt from "bcrypt";
 6
 7export class UserService {
 8  constructor(private userRepository: UserRepository) {}
 9
10  async createUser(userData: CreateUserDTO): Promise<User> {
11    // Validation
12    const existingUser = await this.userRepository.findByEmail(userData.email);
13    if (existingUser) {
14      throw new ValidationError("Email already exists");
15    }
16
17    // Hash password
18    const hashedPassword = await bcrypt.hash(userData.password, 10);
19
20    // Create user
21    const user = await this.userRepository.create({
22      ...userData,
23      password: hashedPassword,
24    });
25
26    // Remove password from response
27    const { password, ...userWithoutPassword } = user;
28    return userWithoutPassword as User;
29  }
30
31  async getUserById(id: string): Promise<User> {
32    const user = await this.userRepository.findById(id);
33    if (!user) {
34      throw new NotFoundError("User not found");
35    }
36    const { password, ...userWithoutPassword } = user;
37    return userWithoutPassword as User;
38  }
39
40  async updateUser(id: string, updates: UpdateUserDTO): Promise<User> {
41    const user = await this.userRepository.update(id, updates);
42    if (!user) {
43      throw new NotFoundError("User not found");
44    }
45    const { password, ...userWithoutPassword } = user;
46    return userWithoutPassword as User;
47  }
48
49  async deleteUser(id: string): Promise<void> {
50    const deleted = await this.userRepository.delete(id);
51    if (!deleted) {
52      throw new NotFoundError("User not found");
53    }
54  }
55}

Repository Layer:

 1// repositories/user.repository.ts
 2import { Pool } from "pg";
 3import { CreateUserDTO, UpdateUserDTO, UserEntity } from "../types/user.types";
 4
 5export class UserRepository {
 6  constructor(private db: Pool) {}
 7
 8  async create(
 9    userData: CreateUserDTO & { password: string },
10  ): Promise<UserEntity> {
11    const query = `
12      INSERT INTO users (name, email, password)
13      VALUES ($1, $2, $3)
14      RETURNING id, name, email, password, created_at, updated_at
15    `;
16    const { rows } = await this.db.query(query, [
17      userData.name,
18      userData.email,
19      userData.password,
20    ]);
21    return rows[0];
22  }
23
24  async findById(id: string): Promise<UserEntity | null> {
25    const query = "SELECT * FROM users WHERE id = $1";
26    const { rows } = await this.db.query(query, [id]);
27    return rows[0] || null;
28  }
29
30  async findByEmail(email: string): Promise<UserEntity | null> {
31    const query = "SELECT * FROM users WHERE email = $1";
32    const { rows } = await this.db.query(query, [email]);
33    return rows[0] || null;
34  }
35
36  async update(id: string, updates: UpdateUserDTO): Promise<UserEntity | null> {
37    const fields = Object.keys(updates);
38    const values = Object.values(updates);
39
40    const setClause = fields
41      .map((field, idx) => `${field} = $${idx + 2}`)
42      .join(", ");
43
44    const query = `
45      UPDATE users
46      SET ${setClause}, updated_at = CURRENT_TIMESTAMP
47      WHERE id = $1
48      RETURNING *
49    `;
50
51    const { rows } = await this.db.query(query, [id, ...values]);
52    return rows[0] || null;
53  }
54
55  async delete(id: string): Promise<boolean> {
56    const query = "DELETE FROM users WHERE id = $1";
57    const { rowCount } = await this.db.query(query, [id]);
58    return rowCount > 0;
59  }
60}

Pattern 2: Dependency Injection

DI Container:

 1// di-container.ts
 2import { Pool } from "pg";
 3import { UserRepository } from "./repositories/user.repository";
 4import { UserService } from "./services/user.service";
 5import { UserController } from "./controllers/user.controller";
 6import { AuthService } from "./services/auth.service";
 7
 8class Container {
 9  private instances = new Map<string, any>();
10
11  register<T>(key: string, factory: () => T): void {
12    this.instances.set(key, factory);
13  }
14
15  resolve<T>(key: string): T {
16    const factory = this.instances.get(key);
17    if (!factory) {
18      throw new Error(`No factory registered for ${key}`);
19    }
20    return factory();
21  }
22
23  singleton<T>(key: string, factory: () => T): void {
24    let instance: T;
25    this.instances.set(key, () => {
26      if (!instance) {
27        instance = factory();
28      }
29      return instance;
30    });
31  }
32}
33
34export const container = new Container();
35
36// Register dependencies
37container.singleton(
38  "db",
39  () =>
40    new Pool({
41      host: process.env.DB_HOST,
42      port: parseInt(process.env.DB_PORT || "5432"),
43      database: process.env.DB_NAME,
44      user: process.env.DB_USER,
45      password: process.env.DB_PASSWORD,
46      max: 20,
47      idleTimeoutMillis: 30000,
48      connectionTimeoutMillis: 2000,
49    }),
50);
51
52container.singleton(
53  "userRepository",
54  () => new UserRepository(container.resolve("db")),
55);
56
57container.singleton(
58  "userService",
59  () => new UserService(container.resolve("userRepository")),
60);
61
62container.register(
63  "userController",
64  () => new UserController(container.resolve("userService")),
65);
66
67container.singleton(
68  "authService",
69  () => new AuthService(container.resolve("userRepository")),
70);

Middleware Patterns

Authentication Middleware

 1// middleware/auth.middleware.ts
 2import { Request, Response, NextFunction } from "express";
 3import jwt from "jsonwebtoken";
 4import { UnauthorizedError } from "../utils/errors";
 5
 6interface JWTPayload {
 7  userId: string;
 8  email: string;
 9}
10
11declare global {
12  namespace Express {
13    interface Request {
14      user?: JWTPayload;
15    }
16  }
17}
18
19export const authenticate = async (
20  req: Request,
21  res: Response,
22  next: NextFunction,
23) => {
24  try {
25    const token = req.headers.authorization?.replace("Bearer ", "");
26
27    if (!token) {
28      throw new UnauthorizedError("No token provided");
29    }
30
31    const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
32
33    req.user = payload;
34    next();
35  } catch (error) {
36    next(new UnauthorizedError("Invalid token"));
37  }
38};
39
40export const authorize = (...roles: string[]) => {
41  return async (req: Request, res: Response, next: NextFunction) => {
42    if (!req.user) {
43      return next(new UnauthorizedError("Not authenticated"));
44    }
45
46    // Check if user has required role
47    const hasRole = roles.some((role) => req.user?.roles?.includes(role));
48
49    if (!hasRole) {
50      return next(new UnauthorizedError("Insufficient permissions"));
51    }
52
53    next();
54  };
55};

Validation Middleware

 1// middleware/validation.middleware.ts
 2import { Request, Response, NextFunction } from "express";
 3import { AnyZodObject, ZodError } from "zod";
 4import { ValidationError } from "../utils/errors";
 5
 6export const validate = (schema: AnyZodObject) => {
 7  return async (req: Request, res: Response, next: NextFunction) => {
 8    try {
 9      await schema.parseAsync({
10        body: req.body,
11        query: req.query,
12        params: req.params,
13      });
14      next();
15    } catch (error) {
16      if (error instanceof ZodError) {
17        const errors = error.errors.map((err) => ({
18          field: err.path.join("."),
19          message: err.message,
20        }));
21        next(new ValidationError("Validation failed", errors));
22      } else {
23        next(error);
24      }
25    }
26  };
27};
28
29// Usage with Zod
30import { z } from "zod";
31
32const createUserSchema = z.object({
33  body: z.object({
34    name: z.string().min(1),
35    email: z.string().email(),
36    password: z.string().min(8),
37  }),
38});
39
40router.post("/users", validate(createUserSchema), userController.createUser);

Rate Limiting Middleware

 1// middleware/rate-limit.middleware.ts
 2import rateLimit from "express-rate-limit";
 3import RedisStore from "rate-limit-redis";
 4import Redis from "ioredis";
 5
 6const redis = new Redis({
 7  host: process.env.REDIS_HOST,
 8  port: parseInt(process.env.REDIS_PORT || "6379"),
 9});
10
11export const apiLimiter = rateLimit({
12  store: new RedisStore({
13    client: redis,
14    prefix: "rl:",
15  }),
16  windowMs: 15 * 60 * 1000, // 15 minutes
17  max: 100, // Limit each IP to 100 requests per windowMs
18  message: "Too many requests from this IP, please try again later",
19  standardHeaders: true,
20  legacyHeaders: false,
21});
22
23export const authLimiter = rateLimit({
24  store: new RedisStore({
25    client: redis,
26    prefix: "rl:auth:",
27  }),
28  windowMs: 15 * 60 * 1000,
29  max: 5, // Stricter limit for auth endpoints
30  skipSuccessfulRequests: true,
31});

Request Logging Middleware

 1// middleware/logger.middleware.ts
 2import { Request, Response, NextFunction } from "express";
 3import pino from "pino";
 4
 5const logger = pino({
 6  level: process.env.LOG_LEVEL || "info",
 7  transport: {
 8    target: "pino-pretty",
 9    options: { colorize: true },
10  },
11});
12
13export const requestLogger = (
14  req: Request,
15  res: Response,
16  next: NextFunction,
17) => {
18  const start = Date.now();
19
20  // Log response when finished
21  res.on("finish", () => {
22    const duration = Date.now() - start;
23    logger.info({
24      method: req.method,
25      url: req.url,
26      status: res.statusCode,
27      duration: `${duration}ms`,
28      userAgent: req.headers["user-agent"],
29      ip: req.ip,
30    });
31  });
32
33  next();
34};
35
36export { logger };

Error Handling

Custom Error Classes

 1// utils/errors.ts
 2export class AppError extends Error {
 3  constructor(
 4    public message: string,
 5    public statusCode: number = 500,
 6    public isOperational: boolean = true,
 7  ) {
 8    super(message);
 9    Object.setPrototypeOf(this, AppError.prototype);
10    Error.captureStackTrace(this, this.constructor);
11  }
12}
13
14export class ValidationError extends AppError {
15  constructor(
16    message: string,
17    public errors?: any[],
18  ) {
19    super(message, 400);
20  }
21}
22
23export class NotFoundError extends AppError {
24  constructor(message: string = "Resource not found") {
25    super(message, 404);
26  }
27}
28
29export class UnauthorizedError extends AppError {
30  constructor(message: string = "Unauthorized") {
31    super(message, 401);
32  }
33}
34
35export class ForbiddenError extends AppError {
36  constructor(message: string = "Forbidden") {
37    super(message, 403);
38  }
39}
40
41export class ConflictError extends AppError {
42  constructor(message: string) {
43    super(message, 409);
44  }
45}

Global Error Handler

 1// middleware/error-handler.ts
 2import { Request, Response, NextFunction } from "express";
 3import { AppError } from "../utils/errors";
 4import { logger } from "./logger.middleware";
 5
 6export const errorHandler = (
 7  err: Error,
 8  req: Request,
 9  res: Response,
10  next: NextFunction,
11) => {
12  if (err instanceof AppError) {
13    return res.status(err.statusCode).json({
14      status: "error",
15      message: err.message,
16      ...(err instanceof ValidationError && { errors: err.errors }),
17    });
18  }
19
20  // Log unexpected errors
21  logger.error({
22    error: err.message,
23    stack: err.stack,
24    url: req.url,
25    method: req.method,
26  });
27
28  // Don't leak error details in production
29  const message =
30    process.env.NODE_ENV === "production"
31      ? "Internal server error"
32      : err.message;
33
34  res.status(500).json({
35    status: "error",
36    message,
37  });
38};
39
40// Async error wrapper
41export const asyncHandler = (
42  fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
43) => {
44  return (req: Request, res: Response, next: NextFunction) => {
45    Promise.resolve(fn(req, res, next)).catch(next);
46  };
47};

Database Patterns

PostgreSQL with Connection Pool

 1// config/database.ts
 2import { Pool, PoolConfig } from "pg";
 3
 4const poolConfig: PoolConfig = {
 5  host: process.env.DB_HOST,
 6  port: parseInt(process.env.DB_PORT || "5432"),
 7  database: process.env.DB_NAME,
 8  user: process.env.DB_USER,
 9  password: process.env.DB_PASSWORD,
10  max: 20,
11  idleTimeoutMillis: 30000,
12  connectionTimeoutMillis: 2000,
13};
14
15export const pool = new Pool(poolConfig);
16
17// Test connection
18pool.on("connect", () => {
19  console.log("Database connected");
20});
21
22pool.on("error", (err) => {
23  console.error("Unexpected database error", err);
24  process.exit(-1);
25});
26
27// Graceful shutdown
28export const closeDatabase = async () => {
29  await pool.end();
30  console.log("Database connection closed");
31};

MongoDB with Mongoose

 1// config/mongoose.ts
 2import mongoose from "mongoose";
 3
 4const connectDB = async () => {
 5  try {
 6    await mongoose.connect(process.env.MONGODB_URI!, {
 7      maxPoolSize: 10,
 8      serverSelectionTimeoutMS: 5000,
 9      socketTimeoutMS: 45000,
10    });
11
12    console.log("MongoDB connected");
13  } catch (error) {
14    console.error("MongoDB connection error:", error);
15    process.exit(1);
16  }
17};
18
19mongoose.connection.on("disconnected", () => {
20  console.log("MongoDB disconnected");
21});
22
23mongoose.connection.on("error", (err) => {
24  console.error("MongoDB error:", err);
25});
26
27export { connectDB };
28
29// Model example
30import { Schema, model, Document } from "mongoose";
31
32interface IUser extends Document {
33  name: string;
34  email: string;
35  password: string;
36  createdAt: Date;
37  updatedAt: Date;
38}
39
40const userSchema = new Schema<IUser>(
41  {
42    name: { type: String, required: true },
43    email: { type: String, required: true, unique: true },
44    password: { type: String, required: true },
45  },
46  {
47    timestamps: true,
48  },
49);
50
51// Indexes
52userSchema.index({ email: 1 });
53
54export const User = model<IUser>("User", userSchema);

Transaction Pattern

 1// services/order.service.ts
 2import { Pool } from "pg";
 3
 4export class OrderService {
 5  constructor(private db: Pool) {}
 6
 7  async createOrder(userId: string, items: any[]) {
 8    const client = await this.db.connect();
 9
10    try {
11      await client.query("BEGIN");
12
13      // Create order
14      const orderResult = await client.query(
15        "INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
16        [userId, calculateTotal(items)],
17      );
18      const orderId = orderResult.rows[0].id;
19
20      // Create order items
21      for (const item of items) {
22        await client.query(
23          "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)",
24          [orderId, item.productId, item.quantity, item.price],
25        );
26
27        // Update inventory
28        await client.query(
29          "UPDATE products SET stock = stock - $1 WHERE id = $2",
30          [item.quantity, item.productId],
31        );
32      }
33
34      await client.query("COMMIT");
35      return orderId;
36    } catch (error) {
37      await client.query("ROLLBACK");
38      throw error;
39    } finally {
40      client.release();
41    }
42  }
43}

Authentication & Authorization

JWT Authentication

 1// services/auth.service.ts
 2import jwt from "jsonwebtoken";
 3import bcrypt from "bcrypt";
 4import { UserRepository } from "../repositories/user.repository";
 5import { UnauthorizedError } from "../utils/errors";
 6
 7export class AuthService {
 8  constructor(private userRepository: UserRepository) {}
 9
10  async login(email: string, password: string) {
11    const user = await this.userRepository.findByEmail(email);
12
13    if (!user) {
14      throw new UnauthorizedError("Invalid credentials");
15    }
16
17    const isValid = await bcrypt.compare(password, user.password);
18
19    if (!isValid) {
20      throw new UnauthorizedError("Invalid credentials");
21    }
22
23    const token = this.generateToken({
24      userId: user.id,
25      email: user.email,
26    });
27
28    const refreshToken = this.generateRefreshToken({
29      userId: user.id,
30    });
31
32    return {
33      token,
34      refreshToken,
35      user: {
36        id: user.id,
37        name: user.name,
38        email: user.email,
39      },
40    };
41  }
42
43  async refreshToken(refreshToken: string) {
44    try {
45      const payload = jwt.verify(
46        refreshToken,
47        process.env.REFRESH_TOKEN_SECRET!,
48      ) as { userId: string };
49
50      const user = await this.userRepository.findById(payload.userId);
51
52      if (!user) {
53        throw new UnauthorizedError("User not found");
54      }
55
56      const token = this.generateToken({
57        userId: user.id,
58        email: user.email,
59      });
60
61      return { token };
62    } catch (error) {
63      throw new UnauthorizedError("Invalid refresh token");
64    }
65  }
66
67  private generateToken(payload: any): string {
68    return jwt.sign(payload, process.env.JWT_SECRET!, {
69      expiresIn: "15m",
70    });
71  }
72
73  private generateRefreshToken(payload: any): string {
74    return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, {
75      expiresIn: "7d",
76    });
77  }
78}

Caching Strategies

 1// utils/cache.ts
 2import Redis from "ioredis";
 3
 4const redis = new Redis({
 5  host: process.env.REDIS_HOST,
 6  port: parseInt(process.env.REDIS_PORT || "6379"),
 7  retryStrategy: (times) => {
 8    const delay = Math.min(times * 50, 2000);
 9    return delay;
10  },
11});
12
13export class CacheService {
14  async get<T>(key: string): Promise<T | null> {
15    const data = await redis.get(key);
16    return data ? JSON.parse(data) : null;
17  }
18
19  async set(key: string, value: any, ttl?: number): Promise<void> {
20    const serialized = JSON.stringify(value);
21    if (ttl) {
22      await redis.setex(key, ttl, serialized);
23    } else {
24      await redis.set(key, serialized);
25    }
26  }
27
28  async delete(key: string): Promise<void> {
29    await redis.del(key);
30  }
31
32  async invalidatePattern(pattern: string): Promise<void> {
33    const keys = await redis.keys(pattern);
34    if (keys.length > 0) {
35      await redis.del(...keys);
36    }
37  }
38}
39
40// Cache decorator
41export function Cacheable(ttl: number = 300) {
42  return function (
43    target: any,
44    propertyKey: string,
45    descriptor: PropertyDescriptor,
46  ) {
47    const originalMethod = descriptor.value;
48
49    descriptor.value = async function (...args: any[]) {
50      const cache = new CacheService();
51      const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
52
53      const cached = await cache.get(cacheKey);
54      if (cached) {
55        return cached;
56      }
57
58      const result = await originalMethod.apply(this, args);
59      await cache.set(cacheKey, result, ttl);
60
61      return result;
62    };
63
64    return descriptor;
65  };
66}

API Response Format

 1// utils/response.ts
 2import { Response } from "express";
 3
 4export class ApiResponse {
 5  static success<T>(
 6    res: Response,
 7    data: T,
 8    message?: string,
 9    statusCode = 200,
10  ) {
11    return res.status(statusCode).json({
12      status: "success",
13      message,
14      data,
15    });
16  }
17
18  static error(res: Response, message: string, statusCode = 500, errors?: any) {
19    return res.status(statusCode).json({
20      status: "error",
21      message,
22      ...(errors && { errors }),
23    });
24  }
25
26  static paginated<T>(
27    res: Response,
28    data: T[],
29    page: number,
30    limit: number,
31    total: number,
32  ) {
33    return res.json({
34      status: "success",
35      data,
36      pagination: {
37        page,
38        limit,
39        total,
40        pages: Math.ceil(total / limit),
41      },
42    });
43  }
44}

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.

Resources

What Users Are Saying

Real feedback from the community

Environment Matrix

Dependencies

Node.js 16+
TypeScript 4.5+
Express.js 4.x or Fastify 4.x

Framework Support

Express.js ✓ (recommended) Fastify ✓ (high-performance) PostgreSQL ✓ MongoDB/Mongoose ✓ Redis ✓ JWT authentication ✓

Context Window

Token Usage ~5K-15K tokens for complete backend implementations

Security & Privacy

Information

Author
wshobson
Updated
2026-01-30
Category
full-stack