首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >2024 版:Node.js+Express+Koa2+Nest.js 开发服务端

2024 版:Node.js+Express+Koa2+Nest.js 开发服务端

原创
作者头像
用户12109578
修改于 2026-09-17 19:20:19
修改于 2026-09-17 19:20:19
1220
举报

Nest.js 服务端开发:当“架构纪律”成为生产力

一个尴尬的现实

2017 年 NestJS 首次发布时,社区的第一反应普遍是怀疑。“Angular 那一套搬到服务端?”“装饰器语法太啰嗦了。”“Express 够用了,为什么要多一层?”

八年过去,情况变了。NestJS 的 GitHub 星标突破 6 万,进入全球 Top 5 后端框架行列-1。更重要的是,它的采用者不是初学者,而是那些被架构失控折磨过的团队。

为什么?因为在 Node.js 服务端领域,工具从来不是问题,架构才是。Express 给了你一个 app.get(),然后呢?十个开发者写出十种项目结构,新人接手需要两周才能搞清楚“用户登录逻辑到底在哪一层”。这不是 Express 的错,但它是一个真实的工程成本。

NestJS 的核心价值,不是“更好的 Express”,而是用框架的约束力,替代团队的自觉性。

模块:不只是文件夹

每个 NestJS 项目的入口都是一个 @Module() 装饰器。这个设计看起来简单,但它的含义比“分组”深得多。

模块在 NestJS 中是一个封装边界。默认情况下,模块内的 provider 是私有的。只有显式加入 exports 数组的 provider,才能被其他模块注入-3-5。这意味着模块的 exports 数组,就是它对外的公共 API。

这个约束带来的工程收益是直接的:一个 UsersModule 导出了 UsersService,但把内部的 UserRepository 和 PasswordHasher 藏在模块内。当 OrdersModule 需要查询用户信息时,它只能通过 UsersService 的公开方法,而不是直接操作数据库。依赖关系变得显式且可控。

模块的另一个关键特性是单例共享。在 NestJS 中,模块默认是单例的,一个 CatsService 实例可以在所有导入 CatsModule 的模块之间共享-3-7。这避免了“每个模块自己 new 一个 Service”导致的内存浪费和状态不一致问题。

官方文档把模块的设计动机说得很清楚:模块化组织代码,建立清晰的边界,这“随着应用或团队的增长尤其重要,并且与 SOLID 原则一致”-7。

分层:控制器不做业务,服务不碰 HTTP

NestJS 的分层架构经常被简化成“Controller → Service → Repository”的箭头图,但真正重要的是每一层的职责边界。

控制器只做三件事:解析 HTTP 请求、调用服务、格式化响应-2。一个 UsersController 里的 create 方法,本质上就是一行 return this.usersService.create(dto)。没有业务逻辑,没有数据库操作,没有条件分支。

服务层承载所有业务逻辑,但它的依赖只能通过构造函数注入。这意味着一个 UsersService 不会“突然”从某个全局变量里读配置,也不会“顺手”调用 fs.readFileSync。它的所有依赖都是显式的、可替换的-15。

这种纪律在单元测试中体现得最明显。因为依赖通过构造函数注入,测试时可以直接传入 mock 对象:

代码语言:javascript
复制
const mockRepo = { findByEmail: jest.fn(), save: jest.fn() };
const service = new UsersService(mockRepo, mockEvents);

不需要加载模块,不需要启动应用,不需要 mock HTTP。架构的可测试性,来自于依赖关系的显式化。

管道、守卫、拦截器:横切关注点的正确放置位置

传统 Node.js 项目里,日志、鉴权、参数校验这些“横切关注点”通常以中间件的形式散布在各处。NestJS 把它们拆成了三个有明确语义的抽象。

管道(Pipes) 处理输入。ParseUUIDPipe 自动验证路径参数是否是合法 UUID,ValidationPipe 配合 class-validator 在 DTO 层面完成校验-2。校验逻辑从控制器方法体里消失了,变成了声明式的装饰器。

守卫(Guards) 处理授权。JwtAuthGuard 挂在控制器或方法上,在请求进入业务逻辑之前完成 token 验证-2。守卫可以组合,@UseGuards(JwtAuthGuard, RolesGuard) 意味着先验身份再查角色。

拦截器(Interceptors) 处理“前后环绕”的逻辑。日志记录、响应转换、缓存、超时控制,这些都是拦截器的领地。一个 LoggingInterceptor 可以统一记录每个请求的耗时和状态码,而不需要在每个控制器方法里手写 console.log。

这三层加上中间件(处理原始请求预处理),构成了 NestJS 的请求生命周期链。每一层有明确的职责,不存在“这段逻辑到底该放中间件还是控制器”的模糊地带。

生产级项目的结构长什么样

一个被验证过的生产级 NestJS 项目结构大致是这样的-2-6:

代码语言:javascript
复制
src/
├── core/           # 全局基础设施:异常过滤器、日志、配置
├── common/         # 无状态工具:DTO、装饰器、哈希工具
├── modules/        # 业务领域
│   ├── auth/
│   ├── users/
│   └── orders/
└── app.module.ts

core 模块包含全局的异常过滤器和拦截器。common 包含可复用的工具函数和 DTO 基类。每个业务模块在自己的目录里完整自洽:users.module.ts、users.controller.ts、users.service.ts、users.repository.ts、dto/、entities/。

这个结构的价值不在于“好看”,而在于新人上手的速度。一个之前没接触过这个项目的开发者,看到 orders/ 目录就知道订单相关的所有代码都在里面。他不需要在 controllers 文件夹里翻找,也不需要猜测 service 层的命名规范。

值得认真对待的“代价”

NestJS 不是没有成本。

学习曲线是真实的。装饰器、依赖注入、模块系统,这些概念对于没有 Angular 或 Spring 背景的开发者来说,需要时间消化。一个 Express 项目从零到跑通一个路由是 5 分钟,NestJS 可能需要 30 分钟。

装饰器的调试体验一般。当依赖注入出错时,错误信息有时不够直观。“Nest can‘t resolve dependencies of the UsersService”背后可能是一个忘记在模块中注册的 provider,但错误信息不会直接告诉你。

类型体操在某些场景下会过度。泛型、条件类型、映射类型在 NestJS 生态中大量使用,一个 TypeORM 的 repository 类型可能嵌套三层。这提高了类型安全性,但也增加了阅读成本。

最后的判断

选择 NestJS,本质上是在选择用前期的结构成本,换取后期的维护效率。

对于一个 5 个接口的微服务,NestJS 可能显得笨重。对于一个 50 个接口、3 个开发者、预计维护 18 个月的项目,它的价值会迅速显现。根据 NestJS 官方 2024 年的开发者调查,采用该框架后代码可维护性提升超过 40%,新成员上手时间平均缩短 50%-1。

这个数字背后是一个简单的逻辑:当团队规模超过 2 人,或者项目生命周期超过 3 个月,“自由”就会变成“混乱”的同义词。NestJS 的约束不是限制,而是把架构决策从“每个开发者每天要做的事”变成了“框架层面已经解决的事”。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • Nest.js 服务端开发:当“架构纪律”成为生产力
    • 一个尴尬的现实
    • 模块:不只是文件夹
    • 分层:控制器不做业务,服务不碰 HTTP
    • 管道、守卫、拦截器:横切关注点的正确放置位置
    • 生产级项目的结构长什么样
    • 值得认真对待的“代价”
    • 最后的判断
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档