关于 api-framework-express
面向使用 Express.js 构建 REST API 的开发者,提供可复用的中间件与路由实践。重点涵盖模块化路由、集中式错误处理、请求验证,以及 Express 4 和 5 的差异。
主要功能
- 01中间件链配置
- 02使用 express.Router() 模块化路由
- 03集中式错误处理
- 04适配 Express 4 和 5 的异步错误转发
- 05请求验证与路由守卫
- 06中间件排序与响应辅助函数
适用场景
- 01按资源组织 API 端点
- 02统一处理各路由中的错误
- 03验证请求并保护路由
- 04适配不同 Express 版本的异步处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill api-framework-express安装后在 Claude Code 中输入/api-framework-express确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/core.mdmiddleware.mdrouting.md详细介绍
使用 Express.js 开发 API
快速指南: Express 使用基于中间件的请求处理方式。三个不可妥协的模式:通过
express.Router()实现模块化路由、使用 4 参数中间件(err, req, res, next)集中处理错误,以及正确安排中间件顺序(安全相关中间件优先,错误处理器最后)。Express 5(现已稳定,并且是 npm 上的默认版本)会自动转发异步错误;Express 4 则需要手动调用next(err)或使用包装器。
<critical_requirements>
重要:使用此技能之前
(必须使用 4 个参数 (err, req, res, next) 定义错误处理中间件——Express 通过参数数量识别错误处理器)
(必须在所有路由和其他中间件之后注册错误处理器)
(在 Express 4 中,必须调用 next(err) 转发异步错误——Express 5 会自动转发被拒绝的 Promise)
(必须使用 express.json() 和 express.urlencoded() 解析请求体——没有它们,req.body 的值为 undefined)
</critical_requirements>
自动检测: Express.js、express、app.use、app.get、app.post、app.put、app.delete、express.Router、req.params、req.query、req.body、res.json、res.status、中间件、next()、错误处理器、router.use、express.static、express.json、express.urlencoded
适用场景:
- 使用可组合的中间件模式构建 REST API
- 需要通过
express.Router()组织模块化路由 - 需要集中处理所有路由的错误
- 构建需要解析请求体、提供静态文件或处理 Cookie 的 API
- 创建用于身份验证/授权的路由守卫
不适用场景:
- 需要根据架构自动生成 OpenAPI 文档
- 构建对冷启动时间敏感的边缘/无服务器函数
- 需要通过架构验证实现严格的端到端类型安全
- GraphQL API(请使用专用的 GraphQL 服务端)
涵盖的关键模式:
- 使用
app.use()和next()构建中间件链 - 使用
express.Router()创建模块化路由 - 使用 4 参数中间件处理错误
- 异步错误转发(Express 4 与 Express 5)
- 请求验证中间件
- 路由参数和查询字符串处理
- 用于身份验证/授权的路由守卫
- 中间件顺序(安全、CORS、速率限制、解析、路由、错误处理)
详细资源:
- examples/core.md - 应用设置、错误处理器、异步处理器、请求体解析
- examples/middleware.md - 日志记录、验证、身份验证守卫、中间件顺序
- examples/routing.md - 模块化路由、参数、响应辅助函数、版本化 API
- reference.md - 决策框架、反模式、生产环境检查清单
设计理念
中间件优先架构。 Express 通过一系列中间件函数处理请求。每个中间件都可以修改请求/响应对象、结束响应,或调用 next() 继续执行链。Express 中的一切都是中间件——请求体解析器、身份验证守卫、日志记录器、错误处理器。
Express 4 与 Express 5: Express 5(自 2025 年起稳定,现在是 npm 上的默认版本)会自动转发异步处理器中被拒绝的 Promise 所产生的错误。Express 4 需要显式使用 try/catch + next(err),或使用包装器工具函数。两个版本的错误处理器都必须使用 4 参数签名。
核心模式
模式 1:应用设置
尽早注册请求体解析器,挂载路由模块,最后注册错误处理器。完整实现请参见 examples/core.md。
const app: Express = express();
// Body parsing
app.use(express.json({ limit: JSON_LIMIT }));
app.use(express.urlencoded({ extended: true }));
// Mount route modules
app.use("/api/users", userRoutes);
app.use("/api/products", productRoutes);
// Error handler MUST be last
app.use(errorHandler);
export { app };
优点: 在路由之前注册请求体解析器,以填充 req.body;最后注册错误处理器,以捕获所有错误;模块化挂载路由
模式 2:使用 express.Router() 创建模块化路由
每个资源使用一个 Router,并挂载在路径前缀下。带参数的 CRUD 示例请参见 examples/routing.md。
// src/routes/user-routes.ts
const router = Router();
router.get("/", async (req, res, next) => {
try {
const users = await getUsersFromDatabase();
res.status(HTTP_OK).json({ data: users });
} catch (error) {
next(error);
}
});
export { router as userRoutes };
优点: Router 隔离相关路由,使用命名导出,显式转发错误
模式 3:错误处理中间件(4 个参数)
Express 根据 4 个参数的签名 (err, req, res, next) 来识别错误处理程序。这是最关键的 Express 模式,必须正确实现。完整实现请参阅 examples/core.md。
// CRITICAL: Must have exactly 4 arguments
const errorHandler = (
err: AppError,
req: Request,
res: Response,
next: NextFunction,
): void => {
if (res.headersSent) {
next(err);
return;
}
const statusCode = err.statusCode || HTTP_INTERNAL_ERROR;
res.status(statusCode).json({
error: { message: err.message, code: err.code || "INTERNAL_ERROR" },
});
};
优点: 使用 4 个参数让 Express 将其识别为错误处理程序,检查 headersSent 以避免重复响应,错误格式一致
常见错误: 3 个参数的函数 (err, req, res) 会被视为常规中间件——err 会变成 req,导致行为完全错误
模式 4:异步错误处理
Express 5 会自动转发被拒绝的 Promise。Express 4 需要显式转发。asyncHandler 包装器请参阅 examples/core.md。
// Express 5: async errors auto-forwarded
router.get("/:id", async (req, res) => {
const product = await getProductById(req.params.id);
res.status(HTTP_OK).json({ data: product });
});
// Express 4: MUST forward manually
router.get("/:id", async (req, res, next) => {
try {
const product = await getProductById(req.params.id);
res.status(HTTP_OK).json({ data: product });
} catch (error) {
next(error); // Required in Express 4
}
});
重要性: 在 Express 4 中,未处理的异步拒绝会导致请求一直挂起,直到超时。Express 5 修复了这一问题,但许多项目仍在使用 Express 4。
模式 5:请求验证中间件
在路由处理程序处理 req.body 之前,先在中间件中对其进行验证。完整的验证模式请参阅 examples/middleware.md。
const validateUserCreate = (
req: Request,
res: Response,
next: NextFunction,
): void => {
const { name, email } = req.body;
const errors: string[] = [];
if (!name || name.length < MIN_NAME_LENGTH) errors.push("Name is required");
if (!email || !email.includes("@")) errors.push("Valid email is required");
if (errors.length > 0) {
res
.status(HTTP_BAD_REQUEST)
.json({ error: { message: "Validation failed", details: errors } });
return;
}
next();
};
// Apply: router.post("/", validateUserCreate, createHandler);
优点: 验证与业务逻辑分离,验证失败时提前返回,可在多个路由中复用
模式 6:路由守卫(身份验证/授权)
使用验证访问权限的中间件保护路由。完整的身份验证守卫实现请参阅 examples/middleware.md。
// Extend Request with user data
interface AuthenticatedRequest extends Request {
user?: { id: string; role: string };
}
const requireAuth = (
req: AuthenticatedRequest,
res: Response,
next: NextFunction,
): void => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
res
.status(HTTP_UNAUTHORIZED)
.json({ error: { message: "Authentication required" } });
return;
}
req.user = verifyToken(token);
next();
};
// Apply to all routes in router: router.use(requireAuth);
// Apply to specific route: router.delete("/:id", requireAuth, requireRole("admin"), handler);
优点: 身份验证中间件可复用,角色守卫可配置,扩展 Request 类型以确保类型安全
模式 7:中间件顺序
顺序很重要。先处理安全,再放置错误处理程序。完整的顺序示例请参阅 examples/middleware.md。
1. Security headers (helmet)
2. CORS
3. Rate limiting (before body parsing to save resources)
4. Body parsing (express.json, express.urlencoded)
5. Request logging
6. Routes
7. 404 handler (after all routes)
8. Error handler (LAST)
此顺序的原因: 安全中间件会尽早拒绝不良请求。在解析之前进行速率限制,可以减少滥用请求消耗的 CPU。错误处理程序必须放在最后,以捕获路由中的所有错误。
模式 8:响应辅助函数
使用带类型的辅助函数统一 API 响应。完整实现请参阅 examples/routing.md。
const sendSuccess = <T>(res: Response, data: T, statusCode = HTTP_OK): void => {
res.status(statusCode).json({ success: true, data });
};
const sendNotFound = (res: Response, resource = "Resource"): void => {
res
.status(HTTP_NOT_FOUND)
.json({ success: false, error: { message: `${resource} not found` } });
};
优点: 所有路由的响应格式一致,带类型的辅助函数可减少样板代码
Express 5 迁移说明
自 2025 年 3 月起,Express 5 已成为 npm 上的默认版本。与 Express 4 相比的主要变化:
| 变更 | Express 4 | Express 5 |
|---|---|---|
| 异步错误 | 手动调用 next(err) | 自动转发 |
req.body(未解析) | {} | undefined |
req.query | 可写 | 只读 getter |
| 通配符路由 | /* | /*splat(不含根路径)或 /{*splat}(含根路径) |
| 可选参数 | /:file.:ext? | /:file{.:ext} |
urlencoded 默认值 | extended: true | extended: false |
req.host | 去除端口 | 包含端口 |
| Node.js 最低版本 | 任意版本 | 18+ |
Express 5 中已移除: req.param()、res.send(body, status)、res.send(status)(使用 res.sendStatus())、res.json(obj, status)、res.redirect(url, status)、res.redirect('back')(使用 req.get('Referrer') || '/')、res.sendfile()(使用 res.sendFile())、app.del()(使用 app.delete())。
<red_flags>
危险信号
高优先级:
- 错误处理程序只有 3 个参数 - Express 会将其视为普通中间件,错误会被静默忽略
- 错误处理程序注册在路由之前 - 永远无法捕获路由错误
- 异步处理程序中缺少
next(error)(Express 4) - 导致未处理的 Promise 拒绝,请求挂起 - 未使用
express.json()中间件 - 对于 JSON 请求,req.body为 undefined - HTTP 状态码使用魔法值 - 使用具名常量(
HTTP_OK = 200、HTTP_NOT_FOUND = 404)
中优先级:
- 所有路由都放在同一个文件中 - 会形成难以维护的巨型文件,应使用
express.Router() - 错误处理程序中未检查
res.headersSent- 会导致“headers already sent”崩溃 - 路由模块使用默认导出 - 违反项目约定
- 启用凭据时使用通配符 CORS - 浏览器会拒绝
origin: "*"且credentials: true的配置 - 公共 API 缺少速率限制 - 容易遭到滥用
注意事项与边缘情况:
next('route')与next(error)的区别 - 字符串'route'会跳过当前处理程序并继续匹配下一条路由;其他任何值都会触发错误处理程序req.query的值始终是字符串 - 使用parseInt(val, 10)解析数字- 未添加身份验证的
express.static- 文件可被公开访问,除非有中间件对其进行保护 - 路由器的
mergeParams: true- 在嵌套路由器中访问父路由参数时必须启用 - Express 5:未解析请求体时,
req.body为undefined- Express 4 中该值为{},可能导致if (!req.body)检查出错
</red_flags>
<critical_reminders>
重要提醒
实现任何 Express 路由之前,务必确认已满足以下要求:
(必须定义含 4 个参数的错误处理中间件:(err, req, res, next) —— Express 根据参数个数识别错误处理程序)
(必须在所有路由和其他中间件之后注册错误处理程序)
(必须在 Express 4 中调用 next(err) 转发异步错误 —— Express 5 会自动转发被拒绝的 Promise)
(必须使用 express.json() 和 express.urlencoded() 解析请求体 —— 不使用它们时,req.body 为 undefined)
不遵循这些规则会导致未处理的错误和中间件链中断。
</critical_reminders>