mcpField
E
express

express

github.com/blencorp开发11198 次安装49 周前更新
代码仓库

关于 express

面向构建或维护后端 API 的开发者,提供 Express.js 实践指南,重点涵盖路由与控制器分离、中间件组合,以及请求和错误处理。

主要功能

  1. 01路由与控制器职责分离
  2. 02认证与审计中间件模式
  3. 03中间件顺序指南
  4. 04请求与响应类型规范
  5. 05统一错误处理模式

适用场景

  1. 01规划后端 API 路由结构
  2. 02添加认证或错误处理中间件
  3. 03处理并规范 API 响应类型

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/blencorp/claude-code-kit --skill express

安装后在 Claude Code 中输入/express确认已加载。

附带文件skill-rules-fragment.json

详细介绍

Express.js 框架模式

用途

用于构建可扩展后端 API 的核心 Express.js 模式,重点关注清晰的路由、中间件组合以及正确的请求/响应处理。

何时使用此技能

  • 创建或修改 Express 路由
  • 构建中间件(身份验证、验证、错误处理)
  • 使用 Express Request/Response 对象
  • 实现 BaseController 模式
  • 在 Express 中进行错误处理

清晰的路由模式

路由只负责路由

路由应该只:

  • ✅ 定义路由路径
  • ✅ 注册中间件
  • ✅ 委托给控制器

路由绝不应该:

  • ❌ 包含业务逻辑
  • ❌ 直接访问数据库
  • ❌ 实现验证逻辑
  • ❌ 格式化复杂的响应
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 模式

实现
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 });
    }
}
使用 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');
        }
    }
}

中间件模式

身份验证
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' });
        }
    }
}
使用 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;
}
错误边界
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);
        }
    };
}

中间件顺序

关键顺序
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());

规则:

  • Sentry 请求处理程序必须放在最前面
  • 请求体/ Cookie 解析器必须放在路由之前
  • 错误处理程序必须放在所有路由之后
  • Sentry 错误处理程序必须放在最后

请求/响应处理

类型化请求
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
}
响应模式
// 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 状态码
代码使用场景
200成功(GET、PUT)
201已创建(POST)
204无内容(DELETE)
400错误请求
401未授权
403禁止访问
404未找到
500服务器错误

常见错误

1. 在路由中编写业务逻辑
// ❌ 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. 中间件顺序错误
// ❌ 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. 未进行错误处理
// ❌ 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');
    }
}

常用导入

// 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';

最佳实践

  1. 保持路由简洁 - 路由只负责路由,并委托给控制器
  2. 使用 BaseController - 统一错误处理和响应格式
  3. 正确安排中间件顺序 - Sentry → 解析器 → 路由 → 错误处理程序
  4. 为所有内容添加类型 - 使用 TypeScript 为 Request/Response 指定类型
  5. 处理所有错误 - 在控制器中使用 try-catch,并在全局设置错误边界

相关技能:

  • nodejs - Node.js 核心模式和异步处理
  • backend-dev-guidelines - 完整的后端架构指南
  • prisma - 使用 Prisma ORM 的数据库模式

评论

支持 Markdown