Overview
The Node.js Backend Patterns skill provides comprehensive guidance for building scalable, maintainable, and production-ready Node.js backend applications. This skill covers modern frameworks, architectural patterns, middleware, error handling, authentication, and database integration.
Full-Stack Coverage : While TailStack focuses on React frontends, this skill ensures your backend follows production-grade patterns.
What This Skill Does
The skill guides AI agents through building robust Node.js backends:
Framework Setup - Express.js and Fastify configurations
Architecture - Layered architecture with controllers, services, repositories
Dependency Injection - Clean DI container patterns
Middleware - Authentication, validation, rate limiting, logging
Error Handling - Custom error classes and global error handlers
Database Integration - PostgreSQL, MongoDB with connection pooling
Authentication - JWT-based auth with refresh tokens
Caching - Redis integration and caching strategies
API Design - Consistent response formats and pagination
Location in TailStack
The Node.js Backend Patterns skill is available in the Node package:
# Agent skill files
packages/node/.agent/skills/nodejs-backend-patterns/
packages/node/.claude/skills/nodejs-backend-patterns/
packages/node/.opencode/skills/nodejs-backend-patterns/
Skill Structure
Skill Contents The skill provides:
SKILL.md - Complete patterns documentation (1049 lines)
Code Examples - Production-ready TypeScript implementations
Best Practices - 15 key guidelines for Node.js backends
Architecture Patterns - Layered architecture, DI containers
Database Patterns - Connection pooling, transactions, ORMs
Core Architectural Patterns
Layered Architecture
Controller Layer
Handles HTTP requests and responses. Delegates business logic to services. export class UserController {
constructor ( private userService : UserService ) {}
async createUser ( req : Request , res : Response , next : NextFunction ) {
try {
const user = await this . userService . createUser ( req . body )
res . status ( 201 ). json ( user )
} catch ( error ) {
next ( error )
}
}
}
Service Layer
Contains business logic, validation, and orchestration. export class UserService {
constructor ( private userRepository : UserRepository ) {}
async createUser ( userData : CreateUserDTO ) : Promise < User > {
// 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
return await this . userRepository . create ({
... userData ,
password: hashedPassword
})
}
}
Repository Layer
Data access layer - handles database queries. export class UserRepository {
constructor ( private db : Pool ) {}
async create ( userData : CreateUserDTO ) : Promise < UserEntity > {
const query = `
INSERT INTO users (name, email, password)
VALUES ($1, $2, $3)
RETURNING *
`
const { rows } = await this . db . query ( query , [
userData . name ,
userData . email ,
userData . password
])
return rows [ 0 ]
}
}
Dependency Injection Pattern
Container Setup
Registration
class Container {
private instances = new Map < string , any >()
singleton < T >( key : string , factory : () => T ) : void {
let instance : T
this . instances . set ( key , () => {
if ( ! instance ) {
instance = factory ()
}
return instance
})
}
resolve < T >( key : string ) : T {
const factory = this . instances . get ( key )
if ( ! factory ) throw new Error ( `No factory for ${ key } ` )
return factory ()
}
}
Framework Support
Express.js Minimalist Framework
Most popular Node.js framework
Extensive middleware ecosystem
Simple and flexible
Great for REST APIs
const app = express ()
app . use ( helmet ())
app . use ( cors ())
app . use ( express . json ())
Fastify High Performance
Built-in schema validation
Auto-generated documentation
2-3× faster than Express
Type-safe by default
const fastify = Fastify ({
logger: true
})
await fastify . register ( helmet )
await fastify . listen ({ port: 3000 })
Essential Middleware Patterns
Authentication Middleware
import jwt from 'jsonwebtoken'
export const authenticate = async (
req : Request ,
res : Response ,
next : NextFunction
) => {
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 ()
}
// Usage
router . get ( '/profile' , authenticate , profileController . getProfile )
Validation Middleware
import { z } 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 ) {
throw new ValidationError ( 'Validation failed' , error . errors )
}
next ( error )
}
}
}
// Usage with schema
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 . create )
Rate Limiting
import rateLimit from 'express-rate-limit'
import RedisStore from 'rate-limit-redis'
export const apiLimiter = rateLimit ({
store: new RedisStore ({
client: redis ,
prefix: 'rl:'
}),
windowMs: 15 * 60 * 1000 , // 15 minutes
max: 100 , // 100 requests per window
message: 'Too many requests'
})
// Stricter limit for auth endpoints
export const authLimiter = rateLimit ({
windowMs: 15 * 60 * 1000 ,
max: 5 ,
skipSuccessfulRequests: true
})
app . use ( '/api/' , apiLimiter )
app . use ( '/auth/' , authLimiter )
Error Handling
Custom Error Classes export class AppError extends Error {
constructor (
public message : string ,
public statusCode : number = 500 ,
public isOperational : boolean = true
) {
super ( message )
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 )
}
}
Global Error Handler
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
})
// 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 })
}
// Add at the end of middleware chain
app . use ( errorHandler )
Database Patterns
PostgreSQL Connection Pool Pattern import { Pool } from 'pg'
export const pool = new Pool ({
host: process . env . DB_HOST ,
database: process . env . DB_NAME ,
max: 20 ,
idleTimeoutMillis: 30000
})
// Graceful shutdown
export const closeDatabase = async () => {
await pool . end ()
}
MongoDB Mongoose Setup import mongoose from 'mongoose'
const connectDB = async () => {
await mongoose . connect (
process . env . MONGODB_URI ! ,
{
maxPoolSize: 10 ,
serverSelectionTimeoutMS: 5000
}
)
}
export { connectDB }
Transaction Pattern
export class OrderService {
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 )]
)
// Create order items
for ( const item of items ) {
await client . query (
'INSERT INTO order_items (order_id, product_id, quantity) VALUES ($1, $2, $3)' ,
[ orderResult . rows [ 0 ]. id , item . productId , item . quantity ]
)
}
await client . query ( 'COMMIT' )
return orderResult . rows [ 0 ]. id
} catch ( error ) {
await client . query ( 'ROLLBACK' )
throw error
} finally {
client . release ()
}
}
}
Using with AI Agents
Claude Desktop & Claude.ai
Access Skill
Navigate to packages/node/.claude/skills/nodejs-backend-patterns/SKILL.md
Request Implementation
Example: “Create an Express API with authentication following Node.js backend patterns”
OpenCode
Load Skill
Skill is available at packages/node/.opencode/skills/nodejs-backend-patterns/
Ask for Patterns
Example: “Build a user service with the repository pattern from nodejs-backend-patterns”
Generic Agents
Include Context
Provide the SKILL.md file to your AI agent as context
Reference Patterns
Ask for specific patterns like “layered architecture”, “DI container”, or “JWT authentication”
Best Practices Checklist
15 Essential Best Practices
Use TypeScript - Type safety prevents runtime errors
Implement proper error handling - Custom error classes
Validate input - Use Zod or Joi
Use environment variables - Never hardcode secrets
Implement logging - 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
Write tests - Unit, integration, E2E
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
When to Use This Skill
Building APIs
REST API development
GraphQL servers
Microservices
Real-time applications
Architecture
Setting up project structure
Implementing authentication
Database integration
Error handling patterns
Official Resources