express

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Express.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状态码说明

CodeUse Case
200Success (GET, PUT)
201Created (POST)
204No Content (DELETE)
400Bad Request
401Unauthorized
403Forbidden
404Not Found
500Server 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

最佳实践

  1. Keep Routes Clean - Routes only route, delegate to controllers
  2. Use BaseController - Consistent error handling and response formatting
  3. Proper Middleware Order - Sentry → Parsers → Routes → Error handlers
  4. Type Everything - Use TypeScript for Request/Response types
  5. 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
  1. 保持路由简洁 - 路由仅负责路由转发,业务逻辑委托给控制器
  2. 使用BaseController - 统一错误处理和响应格式
  3. 规范中间件顺序 - Sentry → 解析器 → 路由 → 错误处理器
  4. 全类型化 - 使用TypeScript定义Request/Response类型
  5. 处理所有错误 - 控制器中使用try-catch,全局配置错误边界

相关技能:
  • nodejs - 核心Node.js模式与异步处理
  • backend-dev-guidelines - 完整后端架构指南
  • prisma - 基于Prisma ORM的数据库模式