Run Git operations — commits, branches, merges, rebases, conflict resolution, and recovery — with safety rules enforced.
Documents
Nodejs Patterns
Try itWHAT: Production-ready Node.js backend patterns - Express/Fastify setup, layered architecture, middleware, error handling, validation, database integration, authentication, and caching. WHEN: User is building REST APIs, setting up Node.js servers, implementing authentication, integrating databases, adding validation/caching, or structuring backend applications. KEYWORDS: nodejs, node, express, fastify, typescript, api, rest, middleware, authentication, jwt, validation, zod, postgres, mongodb, redis, caching, rate limiting, error handling
What it does
Patterns for building scalable, maintainable Node.js backend applications with TypeScript.
The skill document
Node.js Backend Patterns
Patterns for building scalable, maintainable Node.js backend applications with TypeScript.
NEVER
- NEVER store secrets in code - Use environment variables, never hardcode credentials
- NEVER skip input validation - Validate all input at the middleware layer with Zod/Joi
- NEVER expose error details in production - Return generic messages, log details server-side
- NEVER use
anytype - TypeScript types prevent runtime errors - NEVER skip error handling - Always wrap async handlers, use global error middleware
- NEVER use sync operations - Use async/await for I/O, never
fs.readFileSyncin handlers - NEVER trust client input - Sanitize, validate, and parameterize all queries
When to Use
- Building REST APIs with Express or Fastify
- Setting up middleware pipelines and error handling
- Implementing authentication and authorization
- Integrating databases with connection pooling and transactions
- Adding validation, caching, and rate limiting
Project Structure — Layered Architecture
src/
├── controllers/ # Handle HTTP requests/responses
├── services/ # Business logic
├── repositories/ # Data access layer
├── models/ # Data models and types
├── middleware/ # Auth, validation, logging, errors
├── routes/ # Route definitions
├── config/ # Database, cache, env configuration
└── utils/ # Helpers, custom errors, response formatting
Controllers handle HTTP concerns, services contain business logic, repositories abstract data access. Each layer only calls the layer below it.
Express Setup
import express from "express";
import helmet from "helmet";
import cors from "cors";
import compression from "compression";
const app = express();
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));
app.use(compression());
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
Fastify Setup
import Fastify from "fastify";
import helmet from "@fastify/helmet";
import cors from "@fastify/cors";
const fastify = Fastify({
logger: { level: process.env.LOG_LEVEL || "info" },
});
await fastify.register(helmet);
await fastify.register(cors, { origin: true });
// Type-safe routes with built-in schema validation
fastify.post<{ Body: { name: string; email: string } }>(
"/users",
{
schema: {
body: {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
},
},
},
},
async (request) => {
const { name, email } = request.body;
return { id: "123", name };
},
);
Error Handling
Custom Error Classes
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 = "Resource not found") { super(message, 404); }
}
export class UnauthorizedError extends AppError {
constructor(message = "Unauthorized") { super(message, 401); }
}
export class ForbiddenError extends AppError {
constructor(message = "Forbidden") { super(message, 403); }
}
Global Error Handler
import { Request, Response, NextFunction } from "express";
import { AppError, ValidationError } from "../utils/errors";
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 }),
});
}
// Don't leak details in production
const message = process.env.NODE_ENV === "production"
? "Internal server error"
: err.message;
res.status(500).json({ status: "error", message });
};
// Wrap async route handlers to forward errors
export const asyncHandler = (
fn: (req: Request, res: Response, next: NextFunction) => Promise,
) => (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
Validation Middleware (Zod)
import { AnyZodObject, ZodError } from "zod";
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((e) => ({
field: e.path.join("."),
message: e.message,
}));
next(new ValidationError("Validation failed", errors));
} else {
next(error);
}
}
};
};
// Usage
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);
Authentication — JWT
Auth Middleware
import jwt from "jsonwebtoken";
interface JWTPayload { userId: string; email: string; }
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");
req.user = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
next();
} catch {
next(new UnauthorizedError("Invalid token"));
}
};
export const authorize = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) return next(new UnauthorizedError("Not authenticated"));
if (!roles.some((r) => req.user?.roles?.includes(r))) {
return next(new ForbiddenError("Insufficient permissions"));
}
next();
};
};
Auth Service
export class AuthService {
constructor(private userRepository: UserRepository) {}
async login(email: string, password: string) {
const user = await this.userRepository.findByEmail(email);
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new UnauthorizedError("Invalid credentials");
}
return {
token: jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: "15m" },
),
refreshToken: jwt.sign(
{ userId: user.id },
process.env.REFRESH_TOKEN_SECRET!,
{ expiresIn: "7d" },
),
user: { id: user.id, name: user.name, email: user.email },
};
}
}
Database Patterns
PostgreSQL Connection Pool
import { Pool, PoolConfig } from "pg";
const pool = 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,
});
pool.on("error", (err) => {
console.error("Unexpected database error", err);
process.exit(-1);
});
export const closeDatabase = async () => { await pool.end(); };
Transaction Pattern
async createOrder(userId: string, items: OrderItem[]) {
const client = await this.db.connect();
try {
await client.query("BEGIN");
const { rows } = await client.query(
"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
[userId, calculateTotal(items)],
);
const orderId = rows[0].id;
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],
);
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();
}
}
Rate Limiting
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 });
export const apiLimiter = rateLimit({
store: new RedisStore({ client: redis, prefix: "rl:" }),
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
});
export const authLimiter = rateLimit({
store: new RedisStore({ client: redis, prefix: "rl:auth:" }),
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true,
});
Caching with Redis
import Redis from "ioredis";
const redis = new Redis({
host: process.env.REDIS_HOST,
retryStrategy: (times) => Math.min(times * 50, 2000),
});
export class CacheService {
async get(key: string): Promise {
const data = await redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: any, ttl?: number): Promise {
const serialized = JSON.stringify(value);
ttl ? await redis.setex(key, ttl, serialized) : await redis.set(key, serialized);
}
async delete(key: string): Promise { await redis.del(key); }
async invalidatePattern(pattern: string): Promise {
const keys = await redis.keys(pattern);
if (keys.length) await redis.del(...keys);
}
}
API Response Helpers
export class ApiResponse {
static success(res: Response, data: T, message?: string, statusCode = 200) {
return res.status(statusCode).json({ status: "success", message, data });
}
static paginated(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) },
});
}
}
Best Practices
- Use TypeScript — type safety prevents runtime errors
- Validate all input — Zod or Joi at the middleware layer
- Custom error classes — map to HTTP status codes, use global handler
- Never hardcode secrets — use environment variables
- Structured logging — Pino or Winston with request context
- Rate limiting — Redis-backed for distributed deployments
- Connection pooling — always for databases
- Dependency injection — constructor injection for testability
- Graceful shutdown — close DB pools, drain connections on SIGTERM
- Health checks —
/healthendpoint for liveness/readiness probes
Related skills
Read and write Excel workbooks, worksheets, ranges, tables, and charts in OneDrive through Microsoft Graph with managed OAuth.
Query and manage Linear issues, projects, teams, cycles, labels, and comments through a managed OAuth GraphQL endpoint.
doc-coauthoring
OfficialWalk through a three-stage workflow that turns rough context into a reader-tested document.
pptx
OfficialCreate, edit, and inspect PowerPoint .pptx files through scripts that handle packaging, validation, and template workflows.
xlsx
OfficialCreate, edit, and analyze spreadsheets with formula recalculation and financial-model conventions built in.
More from wpank
Browse all skillsSystematic code review patterns covering security, performance, maintainability, correctness, and testing — with severity levels, structured feedback guidance, review process, and anti-patterns to avoid. Use when reviewing PRs, establishing review standards, or improving review quality.
Pragmatic coding standards for writing clean, maintainable code — naming, functions, structure, anti-patterns, and pre-edit safety checks. Use when writing new code, refactoring existing code, reviewing code quality, or establishing coding standards.
Build reliable, fast E2E test suites with Playwright and Cypress. Critical user journey coverage, flaky test elimination, CI/CD integration.
Build scalable, themable Tailwind CSS component libraries using CVA for variants, compound components, design tokens, dark mode, and responsive grids.
Create software diagrams using Mermaid syntax. Use when users need to create, visualize, or document software through diagrams including class diagrams, sequence diagrams, flowcharts, ERDs, C4 architecture diagrams, state diagrams, git graphs, and other diagram types. Triggers include requests to diagram, visualize, model, map out, or show the flow of a system.
Provides backend architecture patterns (Clean Architecture, Hexagonal, DDD) for building maintainable, testable, and scalable systems with clear layering and...