创建日期:2026-09-08 | 最近更新:2026-09-08 事实核对基于 NestJS 12(
@nestjs/core12.0.1 /@nestjs/cli12.0.0,Node ≥ 20)在 本机 Node 24 实测:脚手架为npx @nestjs/cli生成,下方启动日志与 curl 返回均为真实运行输出。
NestJS 入门指南:Node 界的“Spring”
一句话:NestJS 是 TypeScript 写的后端框架,把 Angular 的模块化 + 依赖注入 + 装饰器那套架构搬到了服务端。它不像 Express 那样“给个函数自己拼”,而是用模块(Module)→ 控制器(Controller)→ 提供者(Provider) 的结构把应用组织起来——如果你刚看完本站的 Spring 入门,会强烈地感到“这俩是同一个爹”:
概念 Spring(Java) NestJS(TS) 装饰器声明 Bean @Component/@Service@Injectable()HTTP 控制器 @RestController@Controller()依赖注入 构造器注入 构造器注入(几乎一模一样) 组装模块 @Configuration/ 包@Module({ controllers, providers, imports })两边都信一句话:别
new,让容器把依赖给你。
本文定位
本系列第 0 篇(入门),目标:装好 → 看懂脚手架 → 跑起第一个接口 → 看懂 Controller/Provider/Module 怎么协作,让你对“Nest 到底长什么样”有实感(本文代码已在本机跑通,输出见 §5)。
系列目录(全部完成):
| 篇 | 主题 | 状态 |
|---|---|---|
| 0 | 入门:装好 + 解剖脚手架 + 第一个接口(本文) | ✅ |
| 1 | 模块化与 Provider:作用域、生命周期、自定义 provider | ✅ |
| 2 | 数据层:TypeORM / Prisma 怎么选、怎么接进 Nest | ✅ |
| 3 | DTO / ValidationPipe:把参数校验做成规范(实测) | ✅ |
| 4 | 鉴权 Guard / JWT / RBAC(实测) | ✅ |
| 5 | 测试与部署:e2e、Docker、生产 checklist | ✅ |
1. 为什么不是“又一个 Express 封装”
Express 的问题是:太自由。路由散落、无结构约定、鉴权/校验各写各的——项目一大人人难受。Nest 提供的是架构:
- TypeScript 一等公民:装饰器描述路由与元数据,类型贯穿到 DTO 校验;
- 模块化:每个业务域一个
@Module,可复用、可测试、可 lazy; - DI 容器:构造器注入,跟 Spring 同款心智;
- 平台无关:默认跑在 Express 上,一个
NestFactory.create(AppModule, new FastifyAdapter())就能切 Fastify; - 同款全家桶:CLI 生成、内置 ValidationPipe、Guard/Interceptor/Pipe、WebSocket、微服务、GraphQL 都有官方模块。
代价(诚实说):样板和抽象比 Express 重,写超小工具杀鸡用牛刀;适合的是“要长期维护的正式服务”。
2. 环境与安装
| 项 | 要求 |
|---|---|
| Node | ≥ 20(Nest 12;CLI 要求 ≥ 20.11) |
| 包管理器 | npm / yarn / pnpm 均可 |
| TS | CLI 生成,无需手配 |
node -v # 本机:v24.14.1
npx --yes @nestjs/cli@12 new my-app -p npm --skip-git
cd my-app
npm run start:dev # watch 模式开发
CLI 也可以只装全局:
npm i -g @nestjs/cli。生成时-p npm指定包管理器,避免交互提问。
3. 解剖脚手架(Nest 12 生成的工程长这样)
Nest 12 默认项目是 ESM + NodeNext 风格——注意源码里 import 都带 .js 后缀(./app.service.js),main.ts 用顶层 await 直接启动,别按老 CJS 习惯手删后缀。
my-app/
├─ package.json
├─ nest-cli.json # CLI 配置(sourceRoot、compilerOptions)
├─ tsconfig.json
├─ src/
│ ├─ main.ts # 入口:NestFactory.create → listen
│ ├─ app.module.ts # 根模块:把 controllers/providers 组装起来
│ ├─ app.controller.ts # 控制器:路由 + HTTP 方法装饰器
│ ├─ app.controller.spec.ts # 单元测试
│ └─ app.service.ts # 提供者:业务逻辑(@Injectable)
└─ test/
└─ app.e2e-spec.ts # e2e 测试(supertest)
生成的最小 package.json 依赖(就这几个,不臃肿):
{
"dependencies": {
"@nestjs/common": "^12.0.1", // 装饰器、Controller/Module/Injectable
"@nestjs/core": "^12.0.1", // 容器与启动
"@nestjs/platform-express": "^12.0.1",// HTTP 平台适配器(Express)
"reflect-metadata": "^0.2.2", // 装饰器元数据运行时
"rxjs": "^7.8.1" // 响应式(Nest 内部依赖)
}
}
三个核心文件对照着看,就是 Nest 的最小世界观:
// app.module.ts —— 组装一切的地方
@Module({
imports: [], // 引入其它模块
controllers: [AppController], // 本模块的控制器
providers: [AppService], // 本模块的提供者(可被注入)
})
export class AppModule {}
// app.controller.ts —— 只做「路由 → 调 service」
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service.js';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {} // ← DI:容器把 service 注入进来
@Get()
getHello(): string {
return this.appService.getHello();
}
}
// app.service.ts —— 业务逻辑(对 controller 而言只是“可注入的依赖”)
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
对照 Spring:
@Module(providers)≈ Spring 容器里的 Bean 声明,@Injectable()≈@Service,@Controller()≈@RestController,构造器注入更是原封不动。看懂了 Spring 那一篇,Nest 的代码九成不用教。
4. 动手:加一个带参数的接口
在脚手架上加一个 GET /hello?name=Lin(改成 app.controller.ts + app.service.ts 各加一个方法):
// app.controller.ts(新增)
import { Controller, Get, Query } from '@nestjs/common';
// …
@Get('hello') // 路由前缀拼接:@Controller() 空 + /hello
greet(@Query('name') name: string = 'world'): { message: string } {
return this.appService.greet(name);
}
// app.service.ts(新增)
greet(name: string): { message: string } {
return { message: `Hello, ${name}!` };
}
5. 跑起来(本机实测输出)
npm run build # 产物在 dist/
node dist/main.js # 或 npm run start
启动日志(真实输出,注意它把每个路由都“Mapped”出来了——这就是装饰器元数据被框架扫描的痕迹):
[Nest] 65830 - 2026/09/08 08:42:38 LOG [NestFactory] Starting Nest application...
[Nest] 65830 - 2026/09/08 08:42:38 LOG [InstanceLoader] AppModule dependencies initialized +7ms
[Nest] 65830 - 2026/09/08 08:42:38 LOG [RoutesResolver] AppController {/}: +7ms
[Nest] 65830 - 2026/09/08 08:42:38 LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 65830 - 2026/09/08 08:42:38 LOG [RouterExplorer] Mapped {/hello, GET} route +1ms
[Nest] 65830 - 2026/09/08 08:42:38 LOG [NestApplication] Nest application successfully started
实测请求(默认端口 3000,可用 PORT 环境变量改):
$ curl http://localhost:3000/
Hello World! ← 默认 GET /
$ curl "http://localhost:3000/hello?name=Lin"
{"message":"Hello, Lin!"} ← @Query 读到 name
$ curl http://localhost:3000/hello
{"message":"Hello, world!"} ← 缺省值 world 生效
三个观察点:
- 返回对象会被自动 JSON 化(默认 Express 序列化),所以 service 返回
{ message }直接就是 JSON; @Query('name') name: string = 'world'—— 装饰器把 query 参数按名字取,TS 默认值兜底;- 启动日志里
AppModule dependencies initialized说明容器已经创建了 AppService 并注入给了 AppController——你全程没写过一个new AppService()。
6. 常用 CLI(生成即架构)
npx nest --help
npx nest g controller cats # 生成 controller(可带目录:module/cats)
npx nest g service cats
npx nest g module cats # 每个业务域一个 module,再 controller/service 挂进去
npx nest g resource cats # 一把梭:module+controller+service+DTO+e2e(CRUD 模板)
nest g resource 尤其适合 REST 起步——它把「controller → service → DTO」整套 CRUD 都生成好,省去手动接线。
7. 进阶路径(本系列已全部补齐)
入门到能跑之后,按下面顺序把服务做成「正式形态」(每篇都已是完整文章):
- Provider 与作用域:为什么 service 默认单例、
@Injectable作用域、自定义 provider → 篇 1; - 数据层:TypeORM / Prisma 各自的 Nest Module 接法 → 篇 2;
- 校验:class-validator +
ValidationPipe,把DTO变成声明式校验 → 篇 3; - 鉴权:
Guard+ JWT → 篇 4; - 收尾:e2e 测试、
nest build产物、Docker → 篇 5。
参考
- 官方文档:docs.nestjs.com
- 快速上手(中文版):docs.nestjs.com/first-steps
- 源码:github.com/nestjs/nest
- 关联:本站 Spring 入门(同一套 DI/模块心智的 Java 版)
- 许可:MIT
- 本文脚手架与运行基于 Nest 12 实测(Node 24,2026-09-08);版本演进后以官方文档与
nest --help为准。