🎯

nodejs-backend-patterns

🎯Skill

from ovachiever/droid-tings

VibeIndex|
What it does

Implements robust Node.js backend architectures with Express/Fastify, covering middleware, authentication, database integration, and scalable API design best practices.

πŸ“¦

Part of

ovachiever/droid-tings(370 items)

nodejs-backend-patterns

Installation

git cloneClone repository
git clone https://github.com/ovachiever/droid-tings.git
πŸ“– Extracted from docs: ovachiever/droid-tings
17Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

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.

Overview

# 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:

```typescript

import express, { Request, Response, NextFunction } from 'express';

import helmet from 'helmet';

import cors from 'cors';

import compression from 'compression';

const app = express();

// Security middleware

app.use(helmet());

app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));

app.use(compression());

// Body parsing

app.use(express.json({ limit: '10mb' }));

app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// Request logging

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:

```typescript

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 }

}

}

});

// Plugins

await fastify.register(helmet);

await fastify.register(cors, { origin: true });

await fastify.register(compress);

// Type-safe routes with schema validation

fastify.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.ts

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: 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.ts

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 {

// 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 {

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 {

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 {

const deleted = await this.userRepository.delete(id);

if (!deleted) {

throw new NotFoundError('User not found');

}

}

}

```

Repository Layer:

```typescript

// repositories/user.repository.ts

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 {

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 {

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 {

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 {

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 {

const query = 'DELETE FROM users WHERE id = $1';

const { rowCount } = await this.db.query(query, [id]);

return rowCount > 0;

}

}

```

Pattern 2: Dependency Injection

DI Container:

```typescript

// di-container.ts

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();

register(key: string, factory: () => T): void {

this.instances.set(key, factory);

}

resolve(key: string): T {

const factory = this.instances.get(key);

if (!factory) {

throw new Error(No factory registered for ${key});

}

return factory();

}

singleton(key: string, factory: () => T): void {

let instance: T;

this.instances.set(key, () => {

if (!instance) {

instance = factory();

}

return instance;

});

}

}

export const container = new Container();

// Register dependencies

container.singleton('db', () => 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,

}));

container.singleton('userRepository', () =>

new UserRepository(container.resolve('db'))

);

container.singleton('userService', () =>

new UserService(container.resolve('userRepository'))

);

container.register('userController', () =>

new UserController(container.resolve('userService'))

);

container.singleton('authService', () =>

new AuthService(container.resolve('userRepository'))

);

```

Middleware Patterns

Authentication Middleware

```typescript

// middleware/auth.middleware.ts

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.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.ts

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('.'),

message: err.message

}));

next(new ValidationError('Validation failed', errors));

} else {

next(error);

}

}

};

};

// Usage with Zod

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);

```

Rate Limiting Middleware

```typescript

// middleware/rate-limit.middleware.ts

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, // 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.ts

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();

// 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.ts

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: 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.ts

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 })

});

}

// 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 wrapper

export const asyncHandler = (

fn: (req: Request, res: Response, next: NextFunction) => Promise

) => {

return (req: Request, res: Response, next: NextFunction) => {

Promise.resolve(fn(req, res, next)).catch(next);

};

};

```

Database Patterns

PostgreSQL with Connection Pool

```typescript

// config/database.ts

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);

// Test connection

pool.on('connect', () => {

console.log('Database connected');

});

pool.on('error', (err) => {

console.error('Unexpected database error', err);

process.exit(-1);

});

// Graceful shutdown

export const closeDatabase = async () => {

await pool.end();

console.log('Database connection closed');

};

```

MongoDB with Mongoose

```typescript

// config/mongoose.ts

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 };

// Model example

import { Schema, model, Document } from 'mongoose';

interface IUser extends Document {

name: string;

email: string;

password: string;

createdAt: Date;

updatedAt: Date;

}

const userSchema = new Schema({

name: { type: String, required: true },

email: { type: String, required: true, unique: true },

password: { type: String, required: true },

}, {

timestamps: true

});

// Indexes

userSchema.index({ email: 1 });

export const User = model('User', userSchema);

```

Transaction Pattern

```typescript

// services/order.service.ts

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');

// Create order

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;

// Create order items

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]

);

// Update inventory

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();

}

}

}

```

Authentication & Authorization

JWT Authentication

```typescript

// services/auth.service.ts

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.generateToken({

userId: user.id,

email: user.email

});

const refreshToken = this.generateRefreshToken({

userId: user.id

});

return {

token,

refreshToken,

user: {

id: user.id,

name: user.name,

email: user.email

}

};

}

async refreshToken(refreshToken: string) {

try {

const payload = jwt.verify(

refreshToken,

process.env.REFRESH_TOKEN_SECRET!

) as { userId: string };

const user = await this.userRepository.findById(payload.userId);

if (!user) {

throw new UnauthorizedError('User not found');

}

const token = this.generateToken({

userId: user.id,

email: user.email

});

return { token };

} catch (error) {

throw new UnauthorizedError('Invalid refresh token');

}

}

private generateToken(payload: any): string {

return jwt.sign(payload, process.env.JWT_SECRET!, {

expiresIn: '15m'

});

}

private generateRefreshToken(payload: any): string {

return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, {

expiresIn: '7d'

});

}

}

```

Caching Strategies

```typescript

// utils/cache.ts

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(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);

if (ttl) {

await redis.setex(key, ttl, serialized);

} else {

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 > 0) {

await redis.del(...keys);

}

}

}

// Cache decorator

export function Cacheable(ttl: number = 300) {

return function (

target: any,

propertyKey: string,

descriptor: PropertyDescriptor

) {

const originalMethod = descriptor.value;

descriptor.value = async function (...args: any[]) {

const cache = new CacheService();

const cacheKey = ${propertyKey}:${JSON.stringify(args)};

const cached = await cache.get(cacheKey);

if (cached) {

return cached;

}

const result = await originalMethod.apply(this, args);

await cache.set(cacheKey, result, ttl);

return result;

};

return descriptor;

};

}

```

API Response Format

```typescript

// utils/response.ts

import { Response } from 'express';

export class ApiResponse {

static success(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(

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

  1. Use TypeScript: Type safety prevents runtime errors
  2. Implement proper error handling: Use custom error classes
  3. Validate input: Use libraries like Zod or Joi
  4. Use environment variables: Never hardcode secrets
  5. Implement logging: Use structured logging (Pino, Winston)
  6. Add rate limiting: Prevent abuse
  7. Use HTTPS: Always in production
  8. Implement CORS properly: Don't use * in production
  9. Use dependency injection: Easier testing and maintenance
  10. Write tests: Unit, integration, and E2E tests
  11. Handle graceful shutdown: Clean up resources
  12. Use connection pooling: For databases
  13. Implement health checks: For monitoring
  14. Use compression: Reduce response size
  15. Monitor performance: Use APM tools

Testing Patterns

See javascript-testing-patterns skill for comprehensive testing guidance.

Resources

  • Node.js Best Practices: https://github.com/goldbergyoni/nodebestpractices
  • Express.js Guide: https://expressjs.com/en/guide/
  • Fastify Documentation: https://www.fastify.io/docs/
  • TypeScript Node Starter: https://github.com/microsoft/TypeScript-Node-Starter