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.
references/details.md
1# nodejs-backend-patterns — detailed patterns and worked examples23## Core Frameworks45### Express.js - Minimalist Framework67**Basic Setup:**89```typescript10import express, { Request, Response, NextFunction } from "express";11import helmet from "helmet";12import cors from "cors";13import compression from "compression";1415const app = express();1617// Security middleware18app.use(helmet());19app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));20app.use(compression());2122// Body parsing23app.use(express.json({ limit: "10mb" }));24app.use(express.urlencoded({ extended: true, limit: "10mb" }));2526// Request logging27app.use((req: Request, res: Response, next: NextFunction) => {28console.log(`${req.method} ${req.path}`);29next();30});3132const PORT = process.env.PORT || 3000;33app.listen(PORT, () => {34console.log(`Server running on port ${PORT}`);35});36```3738### Fastify - High Performance Framework3940**Basic Setup:**4142```typescript43import Fastify from "fastify";44import helmet from "@fastify/helmet";45import cors from "@fastify/cors";46import compress from "@fastify/compress";4748const fastify = Fastify({49logger: {50level: process.env.LOG_LEVEL || "info",51transport: {52target: "pino-pretty",53options: { colorize: true },54},55},56});5758// Plugins59await fastify.register(helmet);60await fastify.register(cors, { origin: true });61await fastify.register(compress);6263// Type-safe routes with schema validation64fastify.post<{65Body: { name: string; email: string };66Reply: { id: string; name: string };67}>(68"/users",69{70schema: {71body: {72type: "object",73required: ["name", "email"],74properties: {75name: { type: "string", minLength: 1 },76email: { type: "string", format: "email" },77},78},79},80},81async (request, reply) => {82const { name, email } = request.body;83return { id: "123", name };84},85);8687await fastify.listen({ port: 3000, host: "0.0.0.0" });88```8990## Architectural Patterns9192### Pattern 1: Layered Architecture9394**Structure:**9596```97src/98├── controllers/ # Handle HTTP requests/responses99├── services/ # Business logic100├── repositories/ # Data access layer101├── models/ # Data models102├── middleware/ # Express/Fastify middleware103├── routes/ # Route definitions104├── utils/ # Helper functions105├── config/ # Configuration106└── types/ # TypeScript types107```108109**Controller Layer:**110111```typescript112// controllers/user.controller.ts113import { Request, Response, NextFunction } from "express";114import { UserService } from "../services/user.service";115import { CreateUserDTO, UpdateUserDTO } from "../types/user.types";116117export class UserController {118constructor(private userService: UserService) {}119120async createUser(req: Request, res: Response, next: NextFunction) {121try {122const userData: CreateUserDTO = req.body;123const user = await this.userService.createUser(userData);124res.status(201).json(user);125} catch (error) {126next(error);127}128}129130async getUser(req: Request, res: Response, next: NextFunction) {131try {132const { id } = req.params;133const user = await this.userService.getUserById(id);134res.json(user);135} catch (error) {136next(error);137}138}139140async updateUser(req: Request, res: Response, next: NextFunction) {141try {142const { id } = req.params;143const updates: UpdateUserDTO = req.body;144const user = await this.userService.updateUser(id, updates);145res.json(user);146} catch (error) {147next(error);148}149}150151async deleteUser(req: Request, res: Response, next: NextFunction) {152try {153const { id } = req.params;154await this.userService.deleteUser(id);155res.status(204).send();156} catch (error) {157next(error);158}159}160}161```162163**Service Layer:**164165```typescript166// services/user.service.ts167import { UserRepository } from "../repositories/user.repository";168import { CreateUserDTO, UpdateUserDTO, User } from "../types/user.types";169import { NotFoundError, ValidationError } from "../utils/errors";170import bcrypt from "bcrypt";171172export class UserService {173constructor(private userRepository: UserRepository) {}174175async createUser(userData: CreateUserDTO): Promise<User> {176// Validation177const existingUser = await this.userRepository.findByEmail(userData.email);178if (existingUser) {179throw new ValidationError("Email already exists");180}181182// Hash password183const hashedPassword = await bcrypt.hash(userData.password, 10);184185// Create user186const user = await this.userRepository.create({187...userData,188password: hashedPassword,189});190191// Remove password from response192const { password, ...userWithoutPassword } = user;193return userWithoutPassword as User;194}195196async getUserById(id: string): Promise<User> {197const user = await this.userRepository.findById(id);198if (!user) {199throw new NotFoundError("User not found");200}201const { password, ...userWithoutPassword } = user;202return userWithoutPassword as User;203}204205async updateUser(id: string, updates: UpdateUserDTO): Promise<User> {206const user = await this.userRepository.update(id, updates);207if (!user) {208throw new NotFoundError("User not found");209}210const { password, ...userWithoutPassword } = user;211return userWithoutPassword as User;212}213214async deleteUser(id: string): Promise<void> {215const deleted = await this.userRepository.delete(id);216if (!deleted) {217throw new NotFoundError("User not found");218}219}220}221```222223**Repository Layer:**224225```typescript226// repositories/user.repository.ts227import { Pool } from "pg";228import { CreateUserDTO, UpdateUserDTO, UserEntity } from "../types/user.types";229230export class UserRepository {231constructor(private db: Pool) {}232233async create(234userData: CreateUserDTO & { password: string },235): Promise<UserEntity> {236const query = `237INSERT INTO users (name, email, password)238VALUES ($1, $2, $3)239RETURNING id, name, email, password, created_at, updated_at240`;241const { rows } = await this.db.query(query, [242userData.name,243userData.email,244userData.password,245]);246return rows[0];247}248249async findById(id: string): Promise<UserEntity | null> {250const query = "SELECT * FROM users WHERE id = $1";251const { rows } = await this.db.query(query, [id]);252return rows[0] || null;253}254255async findByEmail(email: string): Promise<UserEntity | null> {256const query = "SELECT * FROM users WHERE email = $1";257const { rows } = await this.db.query(query, [email]);258return rows[0] || null;259}260261async update(id: string, updates: UpdateUserDTO): Promise<UserEntity | null> {262const fields = Object.keys(updates);263const values = Object.values(updates);264265const setClause = fields266.map((field, idx) => `${field} = $${idx + 2}`)267.join(", ");268269const query = `270UPDATE users271SET ${setClause}, updated_at = CURRENT_TIMESTAMP272WHERE id = $1273RETURNING *274`;275276const { rows } = await this.db.query(query, [id, ...values]);277return rows[0] || null;278}279280async delete(id: string): Promise<boolean> {281const query = "DELETE FROM users WHERE id = $1";282const { rowCount } = await this.db.query(query, [id]);283return rowCount > 0;284}285}286```287288### Pattern 2: Dependency Injection289290Use a DI container to wire up repositories, services, and controllers. For a full container implementation, see [references/advanced-patterns.md](references/advanced-patterns.md).291292## Middleware Patterns293294### Authentication Middleware295296```typescript297// middleware/auth.middleware.ts298import { Request, Response, NextFunction } from "express";299import jwt from "jsonwebtoken";300import { UnauthorizedError } from "../utils/errors";301302interface JWTPayload {303userId: string;304email: string;305}306307declare global {308namespace Express {309interface Request {310user?: JWTPayload;311}312}313}314315export const authenticate = async (316req: Request,317res: Response,318next: NextFunction,319) => {320try {321const token = req.headers.authorization?.replace("Bearer ", "");322323if (!token) {324throw new UnauthorizedError("No token provided");325}326327const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;328329req.user = payload;330next();331} catch (error) {332next(new UnauthorizedError("Invalid token"));333}334};335336export const authorize = (...roles: string[]) => {337return async (req: Request, res: Response, next: NextFunction) => {338if (!req.user) {339return next(new UnauthorizedError("Not authenticated"));340}341342// Check if user has required role343const hasRole = roles.some((role) => req.user?.roles?.includes(role));344345if (!hasRole) {346return next(new UnauthorizedError("Insufficient permissions"));347}348349next();350};351};352```353354### Validation Middleware355356```typescript357// middleware/validation.middleware.ts358import { Request, Response, NextFunction } from "express";359import { AnyZodObject, ZodError } from "zod";360import { ValidationError } from "../utils/errors";361362export const validate = (schema: AnyZodObject) => {363return async (req: Request, res: Response, next: NextFunction) => {364try {365await schema.parseAsync({366body: req.body,367query: req.query,368params: req.params,369});370next();371} catch (error) {372if (error instanceof ZodError) {373const errors = error.errors.map((err) => ({374field: err.path.join("."),375message: err.message,376}));377next(new ValidationError("Validation failed", errors));378} else {379next(error);380}381}382};383};384385// Usage with Zod386import { z } from "zod";387388const createUserSchema = z.object({389body: z.object({390name: z.string().min(1),391email: z.string().email(),392password: z.string().min(8),393}),394});395396router.post("/users", validate(createUserSchema), userController.createUser);397```398399### Rate Limiting Middleware400401```typescript402// middleware/rate-limit.middleware.ts403import rateLimit from "express-rate-limit";404import RedisStore from "rate-limit-redis";405import Redis from "ioredis";406407const redis = new Redis({408host: process.env.REDIS_HOST,409port: parseInt(process.env.REDIS_PORT || "6379"),410});411412export const apiLimiter = rateLimit({413store: new RedisStore({414client: redis,415prefix: "rl:",416}),417windowMs: 15 * 60 * 1000, // 15 minutes418max: 100, // Limit each IP to 100 requests per windowMs419message: "Too many requests from this IP, please try again later",420standardHeaders: true,421legacyHeaders: false,422});423424export const authLimiter = rateLimit({425store: new RedisStore({426client: redis,427prefix: "rl:auth:",428}),429windowMs: 15 * 60 * 1000,430max: 5, // Stricter limit for auth endpoints431skipSuccessfulRequests: true,432});433```434435### Request Logging Middleware436437```typescript438// middleware/logger.middleware.ts439import { Request, Response, NextFunction } from "express";440import pino from "pino";441442const logger = pino({443level: process.env.LOG_LEVEL || "info",444transport: {445target: "pino-pretty",446options: { colorize: true },447},448});449450export const requestLogger = (451req: Request,452res: Response,453next: NextFunction,454) => {455const start = Date.now();456457// Log response when finished458res.on("finish", () => {459const duration = Date.now() - start;460logger.info({461method: req.method,462url: req.url,463status: res.statusCode,464duration: `${duration}ms`,465userAgent: req.headers["user-agent"],466ip: req.ip,467});468});469470next();471};472473export { logger };474```475476## Error Handling477478### Custom Error Classes479480```typescript481// utils/errors.ts482export class AppError extends Error {483constructor(484public message: string,485public statusCode: number = 500,486public isOperational: boolean = true,487) {488super(message);489Object.setPrototypeOf(this, AppError.prototype);490Error.captureStackTrace(this, this.constructor);491}492}493494export class ValidationError extends AppError {495constructor(496message: string,497public errors?: any[],498) {499super(message, 400);500}501}502503export class NotFoundError extends AppError {504constructor(message: string = "Resource not found") {505super(message, 404);506}507}508509export class UnauthorizedError extends AppError {510constructor(message: string = "Unauthorized") {511super(message, 401);512}513}514515export class ForbiddenError extends AppError {516constructor(message: string = "Forbidden") {517super(message, 403);518}519}520521export class ConflictError extends AppError {522constructor(message: string) {523super(message, 409);524}525}526```527528### Global Error Handler529530```typescript531// middleware/error-handler.ts532import { Request, Response, NextFunction } from "express";533import { AppError } from "../utils/errors";534import { logger } from "./logger.middleware";535536export const errorHandler = (537err: Error,538req: Request,539res: Response,540next: NextFunction,541) => {542if (err instanceof AppError) {543return res.status(err.statusCode).json({544status: "error",545message: err.message,546...(err instanceof ValidationError && { errors: err.errors }),547});548}549550// Log unexpected errors551logger.error({552error: err.message,553stack: err.stack,554url: req.url,555method: req.method,556});557558// Don't leak error details in production559const message =560process.env.NODE_ENV === "production"561? "Internal server error"562: err.message;563564res.status(500).json({565status: "error",566message,567});568};569570// Async error wrapper571export const asyncHandler = (572fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,573) => {574return (req: Request, res: Response, next: NextFunction) => {575Promise.resolve(fn(req, res, next)).catch(next);576};577};578```579580## Database Patterns581582Node.js supports both SQL and NoSQL databases. Use connection pooling for all production databases.583584Key patterns covered in [references/advanced-patterns.md](references/advanced-patterns.md):585- **PostgreSQL with connection pool** — `pg` Pool configuration and graceful shutdown586- **MongoDB with Mongoose** — connection management and schema definition587- **Transaction pattern** — `BEGIN`/`COMMIT`/`ROLLBACK` with `pg` client588589## Authentication & Authorization590591JWT-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).592593## Caching Strategies594595Redis-backed `CacheService` with get/set/delete/invalidatePattern, plus a `@Cacheable` decorator for method-level caching. See [references/advanced-patterns.md](references/advanced-patterns.md).596597## API Response Format598599Standardized `ApiResponse` helper with `success`, `error`, and `paginated` static methods. See [references/advanced-patterns.md](references/advanced-patterns.md).600