express
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExpress.js Framework Patterns
Express.js框架模式
Purpose
用途
Essential Express.js patterns for building scalable backend APIs, emphasizing clean routing, middleware composition, and proper request/response handling.
构建可扩展后端API的核心Express.js模式,强调清晰的路由、中间件组合以及规范的请求/响应处理。
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
- 创建或修改Express路由
- 构建中间件(认证、校验、错误处理)
- 操作Express Request/Response对象
- 实现BaseController模式
- 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
typescript
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;路由应该只负责:
- ✅ 定义路由路径
- ✅ 注册中间件
- ✅ 委托给控制器处理
路由绝不应该:
- ❌ 包含业务逻辑
- ❌ 直接访问数据库
- ❌ 实现校验逻辑
- ❌ 格式化复杂响应
typescript
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
BaseController模式
Implementation
实现代码
typescript
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 });
}
}typescript
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 : '发生未知错误',
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
使用BaseController
typescript
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');
}
}
}typescript
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('获取用户信息', 'user_controller', {
userId: req.params.id
});
const user = await this.userService.findById(req.params.id);
if (!user) {
return this.handleError(
new Error('用户不存在'),
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, '用户创建成功', 201);
} catch (error) {
this.handleError(error, res, 'createUser');
}
}
}Middleware Patterns
中间件模式
Authentication
认证中间件
typescript
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' });
}
}
}typescript
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: '未认证' });
}
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
基于AsyncLocalStorage的审计中间件
typescript
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;
}typescript
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 || '匿名用户',
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
错误边界中间件
typescript
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);
}
};
}typescript
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, next: NextFunction) => {
try {
await handler(req, res, next);
} catch (error) {
next(error);
}
};
}Middleware Ordering
中间件执行顺序
Critical Order
关键顺序示例
typescript
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
typescript
import express from 'express';
import * as Sentry from '@sentry/node';
const app = express();
// 1. Sentry请求处理器(第一个)
app.use(Sentry.Handlers.requestHandler());
// 2. 请求体/ Cookie解析器
app.use(express.json());
app.use(cookieParser());
// 3. 路由
app.use('/api/users', userRoutes);
// 4. 错误处理器(在路由之后)
app.use(errorBoundary);
// 5. Sentry错误处理器(最后一个)
app.use(Sentry.Handlers.errorHandler());规则:
- Sentry请求处理器必须放在第一位
- 请求体/Cookie解析器要在路由之前
- 错误处理器要在所有路由之后
- Sentry错误处理器必须放在最后
Request/Response Handling
请求/响应处理
Typed Requests
类型化请求
typescript
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
}typescript
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
响应模式
typescript
// 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' } });typescript
// 成功响应(200)
res.json({ success: true, data: user });
// 创建成功响应(201)
res.status(201).json({ success: true, data: user });
// 错误响应(400/500)
res.status(400).json({ success: false, error: { message: '输入无效' } });HTTP Status Codes
HTTP状态码说明
| 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 |
| 状态码 | 适用场景 |
|---|---|
| 200 | 成功响应(GET、PUT请求) |
| 201 | 创建成功(POST请求) |
| 204 | 无内容响应(DELETE请求) |
| 400 | 请求参数错误 |
| 401 | 未授权 |
| 403 | 禁止访问 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
Common Mistakes
常见错误
1. Business Logic in Routes
1. 路由中包含业务逻辑
typescript
// ❌ 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));typescript
// ❌ 禁止这样做
router.post('/submit', async (req, res) => {
// 100+行业务逻辑
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
2. 中间件顺序错误
typescript
// ❌ 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);typescript
// ❌ 错误处理器放在路由之前
app.use(errorBoundary);
app.use('/api', routes); // 无法捕获路由中的错误
// ✅ 错误处理器放在路由之后
app.use('/api', routes);
app.use(errorBoundary);3. No Error Handling
3. 未处理错误
typescript
// ❌ 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');
}
}typescript
// ❌ 未处理的错误会导致服务器崩溃
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
常见导入
typescript
// 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';typescript
// Express核心模块
import express, { Request, Response, NextFunction, Router } from 'express';
// 中间件
import cookieParser from 'cookie-parser';
import cors from 'cors';
// Sentry
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
- 保持路由简洁 - 路由仅负责路由转发,业务逻辑委托给控制器
- 使用BaseController - 统一错误处理和响应格式
- 规范中间件顺序 - Sentry → 解析器 → 路由 → 错误处理器
- 全类型化 - 使用TypeScript定义Request/Response类型
- 处理所有错误 - 控制器中使用try-catch,全局配置错误边界
相关技能:
- nodejs - 核心Node.js模式与异步处理
- backend-dev-guidelines - 完整后端架构指南
- prisma - 基于Prisma ORM的数据库模式