Express 分层架构:从路由到服务 | 拾音
单体项目为什么还要分层
个人博客的接口数量不多,但"能跑"和"好改"是两件事。早期我把 SQL 直接写在路由里,后来增加草稿、置顶、SEO 字段,路由函数迅速变成两百行的意大利面。
分层不是为了显得专业,而是为了在修改时知道该打开哪个文件。
三层职责
- routes:解析请求、校验参数、组织响应;
- services:业务规则,例如"发布文章时生成 HTML、计算阅读时长、写入标签关联";
- db:SQL 与连接管理,不包含业务判断。
// routes/articles.js
router.get('/:slug', asyncHandler(async (req, res) => {
const article = await articleService.getPublicBySlug(req.params.slug);
if (!article) return res.status(404).json({ message: '文章不存在' });
res.json(article);
}));
统一错误处理
Express 4 不会自动捕获 async 异常,需要包装器把 Promise 的 reject 转交给 next:
export const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
再注册一个四参数错误中间件,根据错误类型返回 400/401/404/500,并隐藏堆栈:
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
message: status === 500 ? '服务器内部错误' : err.message,
});
});
参数校验放在边界
只在系统边界做校验:请求体、查询参数、上传文件。内部函数信任调用方,避免大量重复判断。校验失败返回 400,并给出字段级信息,前端可以直接展示。
一个反例
我曾经为"以后可能支持多用户"提前设计了三层权限模型,结果整个项目只有作者一个人。不要为假设的需求增加抽象,等需求真的出现再重构,成本更低。
小结
分层的收益来自"修改时的定位成本"。对个人项目而言,三层已经足够:路由薄、服务厚、数据层纯。