| name | nodejs-backend-patterns |
| description | 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. Use when this capability is needed. |
| metadata | {"author":"AI-Foundry-Core"} |
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:
import express, { Request, Response, NextFunction } 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" }));
app.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:
import 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 },
},
},
});
await fastify.register(helmet);
await fastify.register(cors, { origin: true });
await fastify.register(compress);
fastify.post<{
Body: { name: string; email: string };
Reply: { id: string; name: string };
}>(
"/users",
{
schema: {
body: {
type: "object",
: [, ],
: {
: { : , : },
: { : , : },
},
},
},
},
(request, reply) => {
{ name, email } = request.;
{ : , name };
},
);
fastify.({ : , : });
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:
import { 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: , : , : ) {
{
{ id } = req.;
user = ..(id);
res.(user);
} (error) {
(error);
}
}
() {
{
{ id } = req.;
: = req.;
user = ..(id, updates);
res.(user);
} (error) {
(error);
}
}
() {
{
{ id } = req.;
..(id);
res.().();
} (error) {
(error);
}
}
}
Service Layer:
import { 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> {
const existingUser = await this.userRepository.findByEmail(userData.email);
if (existingUser) {
throw new ValidationError("Email already exists");
}
const hashedPassword = await bcrypt.hash(userData.password, );
user = ..({
...userData,
: hashedPassword,
});
{ password, ...userWithoutPassword } = user;
userWithoutPassword ;
}
(: ): <> {
user = ..(id);
(!user) {
();
}
{ password, ...userWithoutPassword } = user;
userWithoutPassword ;
}
(: , : ): <> {
user = ..(id, updates);
(!user) {
();
}
{ password, ...userWithoutPassword } = user;
userWithoutPassword ;
}
(: ): <> {
deleted = ..(id);
(!deleted) {
();
}
}
}
Repository Layer:
import { 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 } = ..(query, [id]);
rows[] || ;
}
(: ): < | > {
query = ;
{ rows } = ..(query, [email]);
rows[] || ;
}
(: , : ): < | > {
fields = .(updates);
values = .(updates);
setClause = fields
.( )
.();
query = ;
{ rows } = ..(query, [id, ...values]);
rows[] || ;
}
(: ): <> {
query = ;
{ rowCount } = ..(query, [id]);
rowCount > ;
}
}
Pattern 2: Dependency Injection
DI Container:
import { Pool } from "pg";
import { UserRepository } from "./repositories/user.repository";
import { UserService } from "./services/user.service";
import { UserController } from "./controllers/user.controller";
import { AuthService } from "./services/auth.service";
class Container {
private instances = new Map<string, any>();
register<T>(key: string, factory: () => T): void {
this.instances.set(key, factory);
}
resolve<T>(key: string): T {
const factory = this.instances.get(key);
if (!factory) {
throw new Error(`No factory registered for ${key}`);
}
return factory();
}
singleton<T>(key: , : T): {
: T;
..(key, {
(!instance) {
instance = ();
}
instance;
});
}
}
container = ();
container.(
,
({
: process..,
: (process.. || ),
: process..,
: process..,
: process..,
: ,
: ,
: ,
}),
);
container.(
,
(container.()),
);
container.(
,
(container.()),
);
container.(
,
(container.()),
);
container.(
,
(container.()),
);
Middleware Patterns
Authentication Middleware
import { 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.(token, process..!) ;
req. = payload;
();
} (error) {
( ());
}
};
= () => {
(: , : , : ) => {
(!req.) {
( ());
}
hasRole = roles.( req.?.?.(role));
(!hasRole) {
( ());
}
();
};
};
Validation Middleware
import { 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(),
: err.,
}));
( (, errors));
} {
(error);
}
}
};
};
{ z } ;
createUserSchema = z.({
: z.({
: z.().(),
: z.().(),
: z.().(),
}),
});
router.(, (createUserSchema), userController.);
Rate Limiting Middleware
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,
port: parseInt(process.env.REDIS_PORT || "6379"),
});
export const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: "rl:",
}),
windowMs: 15 * 60 * 1000,
max: 100,
message: "Too many requests from this IP, please try again later",
standardHeaders: true,
legacyHeaders: false,
});
export const authLimiter = rateLimit({
store: new RedisStore({
: redis,
: ,
}),
: * * ,
: ,
: ,
});
Request Logging Middleware
import { 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();
res.on("finish", () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
url: req.url,
status: res.statusCode,
duration: `${duration}ms`,
: req.[],
: req.,
});
});
();
};
{ logger };
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, );
}
}
{
() {
(message, );
}
}
{
() {
(message, );
}
}
{
() {
(message, );
}
}
Global Error Handler
import { 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 }),
});
}
logger.error({
error: err.message,
stack: err.stack,
url: req.url,
method: req.method,
});
message =
process.. ===
?
: err.;
res.().({
: ,
message,
});
};
= () => {
{
.((req, res, next)).(next);
};
};
Database Patterns
PostgreSQL with Connection Pool
import { Pool, PoolConfig } from "pg";
const poolConfig: PoolConfig = {
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,
};
export const pool = new Pool(poolConfig);
pool.on("connect", () => {
console.log("Database connected");
});
pool.on("error", (err) => {
console.error("Unexpected database error", err);
process.exit(-1);
});
= () => {
pool.();
.();
};
MongoDB with Mongoose
import mongoose from "mongoose";
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI!, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
});
console.log("MongoDB connected");
} catch (error) {
console.error("MongoDB connection error:", error);
process.exit(1);
}
};
mongoose.connection.on("disconnected", () => {
console.log("MongoDB disconnected");
});
mongoose.connection.on("error", (err) => {
console.error("MongoDB error:", err);
});
export { connectDB };
import { Schema, model, Document } from "mongoose";
{
: ;
: ;
: ;
: ;
: ;
}
userSchema = <>(
{
: { : , : },
: { : , : , : },
: { : , : },
},
{
: ,
},
);
userSchema.({ : });
= model<>(, userSchema);
Transaction Pattern
import { Pool } from "pg";
export class OrderService {
constructor(private db: Pool) {}
async createOrder(userId: string, items: any[]) {
const client = await this.db.connect();
try {
await client.query("BEGIN");
const orderResult = await client.query(
"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
[userId, calculateTotal(items)],
);
const orderId = orderResult.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],
);
client.(
,
[item., item.],
);
}
client.();
orderId;
} (error) {
client.();
error;
} {
client.();
}
}
}
Authentication & Authorization
JWT Authentication
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
import { UserRepository } from "../repositories/user.repository";
import { UnauthorizedError } from "../utils/errors";
export class AuthService {
constructor(private userRepository: UserRepository) {}
async login(email: string, password: string) {
const user = await this.userRepository.findByEmail(email);
if (!user) {
throw new UnauthorizedError("Invalid credentials");
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new UnauthorizedError("Invalid credentials");
}
const token = this.({
: user.,
: user.,
});
refreshToken = .({
: user.,
});
{
token,
refreshToken,
: {
: user.,
: user.,
: user.,
},
};
}
() {
{
payload = jwt.(
refreshToken,
process..!,
) { : };
user = ..(payload.);
(!user) {
();
}
token = .({
: user.,
: user.,
});
{ token };
} (error) {
();
}
}
(: ): {
jwt.(payload, process..!, {
: ,
});
}
(: ): {
jwt.(payload, process..!, {
: ,
});
}
}
Caching Strategies
import Redis from "ioredis";
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || "6379"),
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
});
export class CacheService {
async get<T>(key: string): Promise<T | null> {
const data = await redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: any, ttl?: number): Promise<void> {
const serialized = JSON.stringify(value);
(ttl) {
redis.(key, ttl, serialized);
} {
redis.(key, serialized);
}
}
(: ): <> {
redis.(key);
}
(: ): <> {
keys = redis.(pattern);
(keys. > ) {
redis.(...keys);
}
}
}
() {
() {
originalMethod = descriptor.;
descriptor. = () {
cache = ();
cacheKey = ;
cached = cache.(cacheKey);
(cached) {
cached;
}
result = originalMethod.(, args);
cache.(cacheKey, result, ttl);
result;
};
descriptor;
};
}
API Response Format
import { Response } from "express";
export class ApiResponse {
static success<T>(
res: Response,
data: T,
message?: string,
statusCode = 200,
) {
return res.status(statusCode).json({
status: "success",
message,
data,
});
}
static error(res: Response, message: string, statusCode = 500, errors?: any) {
return res.status(statusCode).json({
status: "error",
message,
...(errors && { errors }),
});
}
static paginated<T>(
res: Response,
data: T[],
page: number,
limit: number,
total: number,
) {
return res.json({
status: "success",
data,
pagination: {
page,
limit,
total,
: .(total / limit),
},
});
}
}
Best Practices
- Use TypeScript: Type safety prevents runtime errors
- Implement proper error handling: Use custom error classes
- Validate input: Use libraries like Zod or Joi
- Use environment variables: Never hardcode secrets
- Implement logging: Use structured logging (Pino, Winston)
- Add rate limiting: Prevent abuse
- Use HTTPS: Always in production
- Implement CORS properly: Don't use
* in production
- Use dependency injection: Easier testing and maintenance
- Write tests: Unit, integration, and E2E tests
- Handle graceful shutdown: Clean up resources
- Use connection pooling: For databases
- Implement health checks: For monitoring
- Use compression: Reduce response size
- Monitor performance: Use APM tools
Testing Patterns
See javascript-testing-patterns skill for comprehensive testing guidance.
Resources
Source: AI-Foundry-Core/ril-agents — distributed by TomeVault.