| 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();
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: ,
: T,
?: ,
statusCode =
): {
res.(statusCode).({
: ,
message,
data,
});
}
withTransaction<T>(
: ,
: ,
: <T>
): <T> {
.({ name, : operation }, callback);
}
(
: ,
: ,
?: <, >
): {
.({ message, category, : , 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..(req..);
(!user) {
.(
(),
res,
,
);
}
.(res, user);
} (error) {
.(error, res, );
}
}
(: , : ): <> {
{
validated = createUserSchema.(req.);
user = .(
,
,
..(validated)
);
.(res, user, , );
} (error) {
.(error, res, );
}
}
}
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: });
}
}
}
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: (),
: req. || (),
};
auditContextStorage.(context, ());
}
(): | {
auditContextStorage.() || ;
}
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,
},
});
}
export function asyncErrorWrapper(
handler: (req: Request, res: Response, next: NextFunction) => Promise<any>
) {
return async (req: Request, res: Response, : ) => {
{
(req, res, next);
} (error) {
(error);
}
};
}
Middleware Ordering
Critical Order
import express from 'express';
import * as Sentry from '@sentry/node';
const app = express();
app.use(Sentry.Handlers.requestHandler());
app.use(express.json());
app.use(cookieParser());
app.use('/api/users', userRoutes);
app.use(errorBoundary);
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;
}
Response Patterns
res.json({ success: true, data: user });
res.status(201).json({ success: true, data: user });
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
router.post('/submit', async (req, res) => {
const user = await db.user.create(req.body);
const workflow = await processWorkflow(user);
res.json(workflow);
});
router.post('/submit', (req, res) => controller.submit(req, res));
2. Wrong Middleware Order
app.use(errorBoundary);
app.use('/api', routes);
app.use('/api', routes);
app.use(errorBoundary);
3. No Error Handling
router.get('/user/:id', async (req, res) => {
const user = await userService.get(req.params.id);
res.json(user);
});
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
import express, { Request, Response, NextFunction, Router } from 'express';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import * as Sentry from '@sentry/node';
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