| name | express |
| description | Express.js framework patterns including routing, middleware, request/response handling, and Express-specific APIs. Use when working with Express routes, middleware, or Express applications. |
Express.js Framework Patterns
Purpose
Essential Express.js patterns for building scalable backend APIs, emphasizing clean routing, middleware composition, and proper request/response handling.
When to Use This Skill
- Creating or modifying Express routes
- Building middleware (auth, validation, error handling)
- Working with Express Request/Response objects
- Implementing BaseController pattern
- Error handling in Express
Clean Route Pattern
Routes Only Route
Routes should ONLY:
- ✅ Define route paths
- ✅ Register middleware
- ✅ Delegate to controllers
Routes should NEVER:
- ❌ Contain business logic
- ❌ Access database directly
- ❌ Implement validation logic
- ❌ Format complex responses
import { Router } from 'express';
import { UserController } from '../controllers/UserController';
import { SSOMiddlewareClient } from '../middleware/SSOMiddleware';
const router = Router();
const controller = new UserController();
// Clean delegation - no business logic
router.get('/:id',
SSOMiddlewareClient.verifyLoginStatus,
async (req, res) => controller.getUser(req, res)
);
router.post('/',
SSOMiddlewareClient.verifyLoginStatus,
async (req, res) => controller.createUser(req, res)
);
export default router;
BaseController Pattern
Implementation
import * as Sentry from '@sentry/node';
import { Response } from 'express';
export abstract class BaseController {
protected handleError(
error: unknown,
res: Response,
context: string,
statusCode = 500
): void {
Sentry.withScope((scope) => {
scope.setTag('controller', this.constructor.name);
scope.setTag('operation', context);
Sentry.captureException(error);
});
res.status(statusCode).json({
success: false,
error: {
message: error instanceof Error ? error.message : 'An error occurred',
code: statusCode,
},
});
}
protected handleSuccess<T>(
res: Response,
data: T,
message?: string,
statusCode = 200
): void {
res.status(statusCode).json({
success: true,
message,
data,
});
}
protected async withTransaction<T>(
name: string,
operation: string,
callback: () => Promise<T>
): Promise<T> {
return await Sentry.startSpan({ name, op: operation }, callback);
}
protected addBreadcrumb(
message: string,
category: string,
data?: Record<string, any>
): void {
Sentry.addBreadcrumb({ message, category, level: 'info', data });
}
}
Using BaseController
import { Request, Response } from 'express';
import { BaseController } from './BaseController';
import { UserService } from '../services/userService';
import { createUserSchema } from '../validators/userSchemas';
export class UserController extends BaseController {
private userService: UserService;
constructor() {
super();
this.userService = new UserService();
}
async getUser(req: Request, res: Response): Promise<void> {
try {
this.addBreadcrumb('Fetching user', 'user_controller', {
userId: req.params.id
});
const user = await this.userService.findById(req.params.id);
if (!user) {
return this.handleError(
new Error('User not found'),
res,
'getUser',
404
);
}
this.handleSuccess(res, user);
} catch (error) {
this.handleError(error, res, 'getUser');
}
}
async createUser(req: Request, res: Response): Promise<void> {
try {
const validated = createUserSchema.parse(req.body);
const user = await this.withTransaction(
'user.create',
'db.query',
() => this.userService.create(validated)
);
this.handleSuccess(res, user, 'User created successfully', 201);
} catch (error) {
this.handleError(error, res, 'createUser');
}
}
}
Middleware Patterns
Authentication
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { config } from '../config/unifiedConfig';
export class SSOMiddlewareClient {
static verifyLoginStatus(req: Request, res: Response, next: NextFunction): void {
const token = req.cookies.refresh_token;
if (!token) {
return res.status(401).json({ error: 'Not authenticated' });
}
try {
const decoded = jwt.verify(token, config.tokens.jwt);
res.locals.claims = decoded;
res.locals.effectiveUserId = decoded.sub;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
}
Audit with AsyncLocalStorage
import { Request, Response, NextFunction } from 'express';
import { AsyncLocalStorage } from 'async_hooks';
import { v4 as uuidv4 } from 'uuid';
export interface AuditContext {
userId: string;
userName?: string;
requestId: string;
timestamp: Date;
}
export const auditContextStorage = new AsyncLocalStorage<AuditContext>();
export function auditMiddleware(req: Request, res: Response, next: NextFunction): void {
const context: AuditContext = {
userId: res.locals.effectiveUserId || 'anonymous',
userName: res.locals.claims?.preferred_username,
timestamp: new Date(),
requestId: req.id || uuidv4(),
};
auditContextStorage.run(context, () => next());
}
export function getAuditContext(): AuditContext | null {
return auditContextStorage.getStore() || null;
}
Error Boundary
import { Request, Response, NextFunction } from 'express';
import * as Sentry from '@sentry/node';
export function errorBoundary(
error: Error,
req: Request,
res: Response,
next: NextFunction
): void {
const statusCode = error.statusCode || 500;
Sentry.captureException(error);
res.status(statusCode).json({
success: false,
error: {
message: error.message,
code: error.name,
},
});
}
// Async wrapper
export function asyncErrorWrapper(
handler: (req: Request, res: Response, next: NextFunction) => Promise<any>
) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
await handler(req, res, next);
} catch (error) {
next(error);
}
};
}
Middleware Ordering
Critical Order
import express from 'express';
import * as Sentry from '@sentry/node';
const app = express();
// 1. Sentry request handler (FIRST)
app.use(Sentry.Handlers.requestHandler());
// 2. Body/cookie parsing
app.use(express.json());
app.use(cookieParser());
// 3. Routes
app.use('/api/users', userRoutes);
// 4. Error handler (AFTER routes)
app.use(errorBoundary);
// 5. Sentry error handler (LAST)
app.use(Sentry.Handlers.errorHandler());
Rules:
- Sentry request handler FIRST
- Body/cookie parsers before routes
- Error handlers AFTER all routes
- Sentry error handler LAST
Request/Response Handling
Typed Requests
interface CreateUserRequest {
email: string;
name: string;
password: string;
}
async function createUser(
req: Request<{}, {}, CreateUserRequest>,
res: Response
): Promise<void> {
const { email, name, password } = req.body; // Typed
}
Response Patterns
// Success (200)
res.json({ success: true, data: user });
// Created (201)
res.status(201).json({ success: true, data: user });
// Error (400/500)
res.status(400).json({ success: false, error: { message: 'Invalid input' } });
HTTP Status Codes
| Code | Use Case |
|---|---|
| 200 | Success (GET, PUT) |
| 201 | Created (POST) |
| 204 | No Content (DELETE) |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 500 | Server Error |
Common Mistakes
1. Business Logic in Routes
// ❌ Never do this
router.post('/submit', async (req, res) => {
// 100+ lines of logic
const user = await db.user.create(req.body);
const workflow = await processWorkflow(user);
res.json(workflow);
});
// ✅ Do this
router.post('/submit', (req, res) => controller.submit(req, res));
2. Wrong Middleware Order
// ❌ Error handler before routes
app.use(errorBoundary);
app.use('/api', routes); // Won't catch errors
// ✅ Error handler after routes
app.use('/api', routes);
app.use(errorBoundary);
3. No Error Handling
// ❌ Unhandled errors crash server
router.get('/user/:id', async (req, res) => {
const user = await userService.get(req.params.id); // May throw
res.json(user);
});
// ✅ Proper error handling
async getUser(req: Request, res: Response): Promise<void> {
try {
const user = await this.userService.get(req.params.id);
this.handleSuccess(res, user);
} catch (error) {
this.handleError(error, res, 'getUser');
}
}
Common Imports
// Express core
import express, { Request, Response, NextFunction, Router } from 'express';
// Middleware
import cookieParser from 'cookie-parser';
import cors from 'cors';
// Sentry
import * as Sentry from '@sentry/node';
// Utilities
import { AsyncLocalStorage } from 'async_hooks';
Best Practices
- Keep Routes Clean - Routes only route, delegate to controllers
- Use BaseController - Consistent error handling and response formatting
- Proper Middleware Order - Sentry → Parsers → Routes → Error handlers
- Type Everything - Use TypeScript for Request/Response types
- Handle All Errors - Use try-catch in controllers, error boundaries globally
Related Skills:
- nodejs - Core Node.js patterns and async handling
- backend-dev-guidelines - Complete backend architecture guide
- prisma - Database patterns with Prisma ORM