Claude Agent Skill · by Wshobson

Nodejs Backend Patterns

Solid patterns for building Node.js backends that don't fall apart in production. Covers the essentials like proper layered architecture with controllers, servi

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill nodejs-backend-patterns
Works with Paperclip

How Nodejs Backend Patterns fits into a Paperclip company.

Nodejs Backend Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md639 lines
Expand
---name: nodejs-backend-patternsdescription: 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:** ```typescriptimport express, { Request, Response, NextFunction } from "express";import helmet from "helmet";import cors from "cors";import compression from "compression"; const app = express(); // Security middlewareapp.use(helmet());app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));app.use(compression()); // Body parsingapp.use(express.json({ limit: "10mb" }));app.use(express.urlencoded({ extended: true, limit: "10mb" })); // Request loggingapp.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:** ```typescriptimport 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 },    },  },}); // Pluginsawait fastify.register(helmet);await fastify.register(cors, { origin: true });await fastify.register(compress); // Type-safe routes with schema validationfastify.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.tsimport { 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.tsimport { 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.tsimport { 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.tsimport { 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.tsimport { 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 Zodimport { 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.tsimport 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.tsimport { 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.tsexport 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.tsimport { 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 wrapperexport 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). ## Best Practices 1. **Use TypeScript**: Type safety prevents runtime errors2. **Implement proper error handling**: Use custom error classes3. **Validate input**: Use libraries like Zod or Joi4. **Use environment variables**: Never hardcode secrets5. **Implement logging**: Use structured logging (Pino, Winston)6. **Add rate limiting**: Prevent abuse7. **Use HTTPS**: Always in production8. **Implement CORS properly**: Don't use `*` in production9. **Use dependency injection**: Easier testing and maintenance10. **Write tests**: Unit, integration, and E2E tests11. **Handle graceful shutdown**: Clean up resources12. **Use connection pooling**: For databases13. **Implement health checks**: For monitoring14. **Use compression**: Reduce response size15. **Monitor performance**: Use APM tools ## Testing Patterns See `javascript-testing-patterns` skill for comprehensive testing guidance.