Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Part of a 72-plugin marketplace with 112 AI agents and 146 skills for Claude Code development automation.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: nodejs-backend-patterns3description: 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.4---56# Node.js Backend Patterns78Comprehensive guidance for building scalable, maintainable, and production-ready Node.js backend applications with modern frameworks, architectural patterns, and best practices.910## When to Use This Skill1112- Building REST APIs or GraphQL servers13- Creating microservices with Node.js14- Implementing authentication and authorization15- Designing scalable backend architectures16- Setting up middleware and error handling17- Integrating databases (SQL and NoSQL)18- Building real-time applications with WebSockets19- Implementing background job processing2021## Core Frameworks2223### Express.js - Minimalist Framework2425**Basic Setup:**2627```typescript28import express, { Request, Response, NextFunction } from "express";29import helmet from "helmet";30import cors from "cors";31import compression from "compression";3233const app = express();3435// Security middleware36app.use(helmet());37app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));38app.use(compression());3940// Body parsing41app.use(express.json({ limit: "10mb" }));42app.use(express.urlencoded({ extended: true, limit: "10mb" }));4344// Request logging45app.use((req: Request, res: Response, next: NextFunction) => {46console.log(`${req.method} ${req.path}`);47next();48});4950const PORT = process.env.PORT || 3000;51app.listen(PORT, () => {52console.log(`Server running on port ${PORT}`);53});54```5556### Fastify - High Performance Framework5758**Basic Setup:**5960```typescript61import Fastify from "fastify";62import helmet from "@fastify/helmet";63import cors from "@fastify/cors";64import compress from "@fastify/compress";6566const fastify = Fastify({67logger: {68level: process.env.LOG_LEVEL || "info",69transport: {70target: "pino-pretty",71options: { colorize: true },72},73},74});7576// Plugins77await fastify.register(helmet);78await fastify.register(cors, { origin: true });79await fastify.register(compress);8081// Type-safe routes with schema validation82fastify.post<{83Body: { name: string; email: string };84Reply: { id: string; name: string };85}>(86"/users",87{88schema: {89body: {90type: "object",91required: ["name", "email"],92properties: {93name: { type: "string", minLength: 1 },94email: { type: "string", format: "email" },95},96},97},98},99async (request, reply) => {100const { name, email } = request.body;101return { id: "123", name };102},103);104105await fastify.listen({ port: 3000, host: "0.0.0.0" });106```107108## Architectural Patterns109110### Pattern 1: Layered Architecture111112**Structure:**113114```115src/116├── controllers/ # Handle HTTP requests/responses117├── services/ # Business logic118├── repositories/ # Data access layer119├── models/ # Data models120├── middleware/ # Express/Fastify middleware121├── routes/ # Route definitions122├── utils/ # Helper functions123├── config/ # Configuration124└── types/ # TypeScript types125```126127**Controller Layer:**128129```typescript130// controllers/user.controller.ts131import { Request, Response, NextFunction } from "express";132import { UserService } from "../services/user.service";133import { CreateUserDTO, UpdateUserDTO } from "../types/user.types";134135export class UserController {136constructor(private userService: UserService) {}137138async createUser(req: Request, res: Response, next: NextFunction) {139try {140const userData: CreateUserDTO = req.body;141const user = await this.userService.createUser(userData);142res.status(201).json(user);143} catch (error) {144next(error);145}146}147148async getUser(req: Request, res: Response, next: NextFunction) {149try {150const { id } = req.params;151const user = await this.userService.getUserById(id);152res.json(user);153} catch (error) {154next(error);155}156}157158async updateUser(req: Request, res: Response, next: NextFunction) {159try {160const { id } = req.params;161const updates: UpdateUserDTO = req.body;162const user = await this.userService.updateUser(id, updates);163res.json(user);164} catch (error) {165next(error);166}167}168169async deleteUser(req: Request, res: Response, next: NextFunction) {170try {171const { id } = req.params;172await this.userService.deleteUser(id);173res.status(204).send();174} catch (error) {175next(error);176}177}178}179```180181**Service Layer:**182183```typescript184// services/user.service.ts185import { UserRepository } from "../repositories/user.repository";186import { CreateUserDTO, UpdateUserDTO, User } from "../types/user.types";187import { NotFoundError, ValidationError } from "../utils/errors";188import bcrypt from "bcrypt";189190export class UserService {191constructor(private userRepository: UserRepository) {}192193async createUser(userData: CreateUserDTO): Promise<User> {194// Validation195const existingUser = await this.userRepository.findByEmail(userData.email);196if (existingUser) {197throw new ValidationError("Email already exists");198}199200// Hash password201const hashedPassword = await bcrypt.hash(userData.password, 10);202203// Create user204const user = await this.userRepository.create({205...userData,206password: hashedPassword,207});208209// Remove password from response210const { password, ...userWithoutPassword } = user;211return userWithoutPassword as User;212}213214async getUserById(id: string): Promise<User> {215const user = await this.userRepository.findById(id);216if (!user) {217throw new NotFoundError("User not found");218}219const { password, ...userWithoutPassword } = user;220return userWithoutPassword as User;221}222223async updateUser(id: string, updates: UpdateUserDTO): Promise<User> {224const user = await this.userRepository.update(id, updates);225if (!user) {226throw new NotFoundError("User not found");227}228const { password, ...userWithoutPassword } = user;229return userWithoutPassword as User;230}231232async deleteUser(id: string): Promise<void> {233const deleted = await this.userRepository.delete(id);234if (!deleted) {235throw new NotFoundError("User not found");236}237}238}239```240241**Repository Layer:**242243```typescript244// repositories/user.repository.ts245import { Pool } from "pg";246import { CreateUserDTO, UpdateUserDTO, UserEntity } from "../types/user.types";247248export class UserRepository {249constructor(private db: Pool) {}250251async create(252userData: CreateUserDTO & { password: string },253): Promise<UserEntity> {254const query = `255INSERT INTO users (name, email, password)256VALUES ($1, $2, $3)257RETURNING id, name, email, password, created_at, updated_at258`;259const { rows } = await this.db.query(query, [260userData.name,261userData.email,262userData.password,263]);264return rows[0];265}266267async findById(id: string): Promise<UserEntity | null> {268const query = "SELECT * FROM users WHERE id = $1";269const { rows } = await this.db.query(query, [id]);270return rows[0] || null;271}272273async findByEmail(email: string): Promise<UserEntity | null> {274const query = "SELECT * FROM users WHERE email = $1";275const { rows } = await this.db.query(query, [email]);276return rows[0] || null;277}278279async update(id: string, updates: UpdateUserDTO): Promise<UserEntity | null> {280const fields = Object.keys(updates);281const values = Object.values(updates);282283const setClause = fields284.map((field, idx) => `${field} = $${idx + 2}`)285.join(", ");286287const query = `288UPDATE users289SET ${setClause}, updated_at = CURRENT_TIMESTAMP290WHERE id = $1291RETURNING *292`;293294const { rows } = await this.db.query(query, [id, ...values]);295return rows[0] || null;296}297298async delete(id: string): Promise<boolean> {299const query = "DELETE FROM users WHERE id = $1";300const { rowCount } = await this.db.query(query, [id]);301return rowCount > 0;302}303}304```305306### Pattern 2: Dependency Injection307308Use a DI container to wire up repositories, services, and controllers. For a full container implementation, see [references/advanced-patterns.md](references/advanced-patterns.md).309310## Middleware Patterns311312### Authentication Middleware313314```typescript315// middleware/auth.middleware.ts316import { Request, Response, NextFunction } from "express";317import jwt from "jsonwebtoken";318import { UnauthorizedError } from "../utils/errors";319320interface JWTPayload {321userId: string;322email: string;323}324325declare global {326namespace Express {327interface Request {328user?: JWTPayload;329}330}331}332333export const authenticate = async (334req: Request,335res: Response,336next: NextFunction,337) => {338try {339const token = req.headers.authorization?.replace("Bearer ", "");340341if (!token) {342throw new UnauthorizedError("No token provided");343}344345const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;346347req.user = payload;348next();349} catch (error) {350next(new UnauthorizedError("Invalid token"));351}352};353354export const authorize = (...roles: string[]) => {355return async (req: Request, res: Response, next: NextFunction) => {356if (!req.user) {357return next(new UnauthorizedError("Not authenticated"));358}359360// Check if user has required role361const hasRole = roles.some((role) => req.user?.roles?.includes(role));362363if (!hasRole) {364return next(new UnauthorizedError("Insufficient permissions"));365}366367next();368};369};370```371372### Validation Middleware373374```typescript375// middleware/validation.middleware.ts376import { Request, Response, NextFunction } from "express";377import { AnyZodObject, ZodError } from "zod";378import { ValidationError } from "../utils/errors";379380export const validate = (schema: AnyZodObject) => {381return async (req: Request, res: Response, next: NextFunction) => {382try {383await schema.parseAsync({384body: req.body,385query: req.query,386params: req.params,387});388next();389} catch (error) {390if (error instanceof ZodError) {391const errors = error.errors.map((err) => ({392field: err.path.join("."),393message: err.message,394}));395next(new ValidationError("Validation failed", errors));396} else {397next(error);398}399}400};401};402403// Usage with Zod404import { z } from "zod";405406const createUserSchema = z.object({407body: z.object({408name: z.string().min(1),409email: z.string().email(),410password: z.string().min(8),411}),412});413414router.post("/users", validate(createUserSchema), userController.createUser);415```416417### Rate Limiting Middleware418419```typescript420// middleware/rate-limit.middleware.ts421import rateLimit from "express-rate-limit";422import RedisStore from "rate-limit-redis";423import Redis from "ioredis";424425const redis = new Redis({426host: process.env.REDIS_HOST,427port: parseInt(process.env.REDIS_PORT || "6379"),428});429430export const apiLimiter = rateLimit({431store: new RedisStore({432client: redis,433prefix: "rl:",434}),435windowMs: 15 * 60 * 1000, // 15 minutes436max: 100, // Limit each IP to 100 requests per windowMs437message: "Too many requests from this IP, please try again later",438standardHeaders: true,439legacyHeaders: false,440});441442export const authLimiter = rateLimit({443store: new RedisStore({444client: redis,445prefix: "rl:auth:",446}),447windowMs: 15 * 60 * 1000,448max: 5, // Stricter limit for auth endpoints449skipSuccessfulRequests: true,450});451```452453### Request Logging Middleware454455```typescript456// middleware/logger.middleware.ts457import { Request, Response, NextFunction } from "express";458import pino from "pino";459460const logger = pino({461level: process.env.LOG_LEVEL || "info",462transport: {463target: "pino-pretty",464options: { colorize: true },465},466});467468export const requestLogger = (469req: Request,470res: Response,471next: NextFunction,472) => {473const start = Date.now();474475// Log response when finished476res.on("finish", () => {477const duration = Date.now() - start;478logger.info({479method: req.method,480url: req.url,481status: res.statusCode,482duration: `${duration}ms`,483userAgent: req.headers["user-agent"],484ip: req.ip,485});486});487488next();489};490491export { logger };492```493494## Error Handling495496### Custom Error Classes497498```typescript499// utils/errors.ts500export class AppError extends Error {501constructor(502public message: string,503public statusCode: number = 500,504public isOperational: boolean = true,505) {506super(message);507Object.setPrototypeOf(this, AppError.prototype);508Error.captureStackTrace(this, this.constructor);509}510}511512export class ValidationError extends AppError {513constructor(514message: string,515public errors?: any[],516) {517super(message, 400);518}519}520521export class NotFoundError extends AppError {522constructor(message: string = "Resource not found") {523super(message, 404);524}525}526527export class UnauthorizedError extends AppError {528constructor(message: string = "Unauthorized") {529super(message, 401);530}531}532533export class ForbiddenError extends AppError {534constructor(message: string = "Forbidden") {535super(message, 403);536}537}538539export class ConflictError extends AppError {540constructor(message: string) {541super(message, 409);542}543}544```545546### Global Error Handler547548```typescript549// middleware/error-handler.ts550import { Request, Response, NextFunction } from "express";551import { AppError } from "../utils/errors";552import { logger } from "./logger.middleware";553554export const errorHandler = (555err: Error,556req: Request,557res: Response,558next: NextFunction,559) => {560if (err instanceof AppError) {561return res.status(err.statusCode).json({562status: "error",563message: err.message,564...(err instanceof ValidationError && { errors: err.errors }),565});566}567568// Log unexpected errors569logger.error({570error: err.message,571stack: err.stack,572url: req.url,573method: req.method,574});575576// Don't leak error details in production577const message =578process.env.NODE_ENV === "production"579? "Internal server error"580: err.message;581582res.status(500).json({583status: "error",584message,585});586};587588// Async error wrapper589export const asyncHandler = (590fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,591) => {592return (req: Request, res: Response, next: NextFunction) => {593Promise.resolve(fn(req, res, next)).catch(next);594};595};596```597598## Database Patterns599600Node.js supports both SQL and NoSQL databases. Use connection pooling for all production databases.601602Key patterns covered in [references/advanced-patterns.md](references/advanced-patterns.md):603- **PostgreSQL with connection pool** — `pg` Pool configuration and graceful shutdown604- **MongoDB with Mongoose** — connection management and schema definition605- **Transaction pattern** — `BEGIN`/`COMMIT`/`ROLLBACK` with `pg` client606607## Authentication & Authorization608609JWT-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).610611## Caching Strategies612613Redis-backed `CacheService` with get/set/delete/invalidatePattern, plus a `@Cacheable` decorator for method-level caching. See [references/advanced-patterns.md](references/advanced-patterns.md).614615## API Response Format616617Standardized `ApiResponse` helper with `success`, `error`, and `paginated` static methods. See [references/advanced-patterns.md](references/advanced-patterns.md).618619## Best Practices6206211. **Use TypeScript**: Type safety prevents runtime errors6222. **Implement proper error handling**: Use custom error classes6233. **Validate input**: Use libraries like Zod or Joi6244. **Use environment variables**: Never hardcode secrets6255. **Implement logging**: Use structured logging (Pino, Winston)6266. **Add rate limiting**: Prevent abuse6277. **Use HTTPS**: Always in production6288. **Implement CORS properly**: Don't use `*` in production6299. **Use dependency injection**: Easier testing and maintenance63010. **Write tests**: Unit, integration, and E2E tests63111. **Handle graceful shutdown**: Clean up resources63212. **Use connection pooling**: For databases63313. **Implement health checks**: For monitoring63414. **Use compression**: Reduce response size63515. **Monitor performance**: Use APM tools636637## Testing Patterns638639See `javascript-testing-patterns` skill for comprehensive testing guidance.640