用 NestJS + Fastify 重写 Koa 博客后端:架构取舍与收益
后端工程28 阅读约 5 分钟
我的博客后端最早是一套基于 Koa 的轻量服务:一堆 router、一层 middleware、controller 里直接写业务。能跑,但随着功能变多(文章、分类、标签、评论、留言、友链、鉴权、文件上传……),它开始显出问题:依赖关系靠 require 隐式串联,公共逻辑(鉴权、分页、响应包装)到处复制,写测试要手动 mock 一大堆东西。
于是我用 NestJS + Fastify 把它完整重写了一遍。这篇文章记录这次重写的取舍。
为什么是 NestJS
Koa 给的是「自由」,NestJS 给的是「约束」。对一个会长期演进的项目来说,约束往往更值钱:
- 模块化:每个领域(article / category / tag / author …)是一个
Module,自带 controller、service、model,边界清晰。 - 依赖注入:service 之间通过构造函数注入,依赖关系是显式的、可替换的,写单测时直接注入 mock。
- 声明式横切关注点:鉴权用
@Auth()装饰器、响应包装用拦截器、异常用过滤器,业务代码里看不到这些噪音。
一个典型的 controller 干净到几乎只剩「路由 → service」的映射:
@Controller('articles')
export class ArticleController {
constructor(
private readonly articleService: ArticleService,
private readonly archiveService: ArchiveService,
) {}
@Post()
@Auth()
@SuccessMessage('发布文章成功')
create(@Body() dto: CreateArticleDTO) {
return this.articleService.create(dto)
}
}
@Auth() 负责鉴权,@SuccessMessage() 负责成功提示,CreateArticleDTO 配合全局 ValidationPipe 负责入参校验——controller 本身只表达「做什么」。
为什么是 Fastify 而不是 Express
NestJS 默认跑在 Express 上,但它也官方支持 Fastify 适配器。我选了 Fastify:
- 更高的吞吐与更低的开销,schema-based 的序列化对 JSON API 很友好;
- 内置的
@fastify/multipart处理文件上传,配合我对象存储那层很顺手; - 对个人项目而言,单机资源有限,能省一点是一点。
切换成本几乎为零——把 NestFactory.create 换成 FastifyAdapter 即可,业务层完全无感,这正是 Nest「平台无关」抽象的价值。
统一响应与分层
重写时我立了一条规矩:所有出口响应长一个样。通过一个全局拦截器把任意返回值包装成:
{ "status": "success", "message": "发布文章成功", "result": { } }
错误则由全局异常过滤器统一成同样的信封 + 语义化错误码。前端再也不用为「这个接口返回的是裸数组、那个接口返回 {data}」写一堆兼容代码。
分层上我保持了克制:controller(路由 + 校验)→ service(业务)→ model(数据)。没有为了「整洁架构」硬塞 repository / usecase 层——个人博客的复杂度不配那么多层,过度分层只会让人来回跳文件。
收益
重写后最直观的变化:
- 加功能变快了。新增一个「友链」模块就是
nest g module/controller/service三连 + 一个 model,公共能力(鉴权、分页、响应、异常)全都白嫖。 - 测试能写了。service 依赖通过 DI 注入,单测里 mock 一个 model 就能跑,
*.service.spec.ts覆盖了核心分支。 - 认知负担降了。任何一个接口出问题,顺着
module → controller → service三步就能定位,不用在 middleware 链里大海捞针。
代价是引入了装饰器、DI 容器这套心智模型,初次上手有学习曲线。但对一个要长期维护、还要持续接新东西(比如后来接的 AI 知识库 Webhook)的项目,这点前期成本非常划算。
小结
Koa 适合「小而美、生命周期短」的服务;一旦项目要长期演进、模块会持续变多,NestJS 的结构化约束 + Fastify 的性能是更稳的组合。重写不是为了追新,而是为了让「明年的我」还能轻松改动「今年的代码」。
相关文章
评论 (6)
fffan
NestJS 重写的架构取舍讲得很坦诚,受用
Vincent
守卫和拦截器执行顺序我老搞混,这篇帮大忙
demo_user
换 Fastify 之后性能提升明显吗?
Ryan
受教了,之前一直一知半解
小项目我个人更倾向轻量方案,按需演进就好
Quentin
JWT 双令牌的边界这篇说透了





