关于 express
面向构建或维护后端 API 的开发者,提供 Express.js 实践指南,重点涵盖路由与控制器分离、中间件组合,以及请求和错误处理。
主要功能
- 01路由与控制器职责分离
- 02认证与审计中间件模式
- 03中间件顺序指南
- 04请求与响应类型规范
- 05统一错误处理模式
适用场景
- 01规划后端 API 路由结构
- 02添加认证或错误处理中间件
- 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';
最佳实践
- 保持路由简洁 - 路由只负责路由,并委托给控制器
- 使用 BaseController - 统一错误处理和响应格式
- 正确安排中间件顺序 - Sentry → 解析器 → 路由 → 错误处理程序
- 为所有内容添加类型 - 使用 TypeScript 为 Request/Response 指定类型
- 处理所有错误 - 在控制器中使用 try-catch,并在全局设置错误边界
相关技能:
- nodejs - Node.js 核心模式和异步处理
- backend-dev-guidelines - 完整的后端架构指南
- prisma - 使用 Prisma ORM 的数据库模式
评论
支持 Markdown