项目创建与结构
这一课你将能做到
- 用 Nest CLI 创建刷题平台项目
quiz-api,并通过 npm run start:dev 看到第一个响应
- 指认
main.ts、AppModule、AppController、AppService 各自管什么
- 修改返回内容与监听端口,验证热重载;看懂
package.json 里的脚本分工
为什么要学
Nest 官方推荐用 CLI 生成骨架,而不是从零手搭 tsconfig.json、Jest、目录约定。
人话:CLI 帮你铺好「能跑、能测、能编译」的地板,后面的课只往上加业务模块。不了解结构会出现三类典型问题:改错入口文件、不知道 Service 该登记在哪、生产环境直接用 ts-node 硬跑源码。花十分钟认清骨架,后面省几小时。
先建立图像
nest new quiz-api
│
▼
src/
main.ts ← 启动入口:创建应用、监听端口
app.module.ts ← 根模块:imports / controllers / providers
app.controller.ts ← 示例 HTTP 入口
app.service.ts ← 示例业务类
test/ ← e2e 测试目录
nest-cli.json ← CLI 编译与资源规则
tsconfig.json ← TypeScript 配置(含装饰器开关)
package.json
与 Spring Boot 的直觉对照:
| NestJS | Spring Boot |
|---|
main.ts 的 bootstrap() | public static void main |
AppModule | @SpringBootApplication 配置起点 |
npm run start:dev | mvn spring-boot:run |
dist/main.js | 打包后的可执行 jar |
nest generate | Spring Initializr / IDE 代码生成 |
跟着做
步骤 1:安装 CLI 并创建项目
在终端执行(Node.js 18+):
npm install -g @nestjs/cli
nest new quiz-api
包管理器提示时选择 npm;新版本 CLI 默认开启 strict TypeScript,直接保留。然后:
cd quiz-api
npm run start:dev
浏览器打开 http://localhost:3000,应看到 Hello World!;终端同时打印 Nest 的启动日志和路由映射表 RouterExplorer。
步骤 2:读懂 main.ts 启动链
打开 main.ts,CLI 生成的内容大致是:
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
// 人话:用根模块创建一个 Nest 应用实例
const app = await NestFactory.create(AppModule);
// 人话:监听端口,优先使用环境变量 PORT
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
这里没有任何业务逻辑,只有「创建容器 + 监听端口」。以后的全局前缀、全局 Pipe、全局过滤器也都注册在这个文件里。
步骤 3:改 Service,确认热重载生效
import { Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
getHello(): string {
// 人话:先返回纯文本,后续课程换成题目 JSON
return "刷题平台 API 已就绪";
}
}
保存后观察终端:start:dev 会自动重新编译并重启。刷新浏览器即可看到新文案。若没变化,确认你改的是 src/ 下的文件且 dev 进程仍在运行。
步骤 4:认清 AppModule 的装配清单
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [], // 引入其他模块
controllers: [AppController], // 本模块的 HTTP 入口
providers: [AppService], // 本模块可注入的类
})
export class AppModule {}
人话:根模块告诉框架「有哪些控制器、有哪些可注入的类」。AppController 能注入 AppService,正是因为两者都登记在同一个 Module 里。
步骤 5:换端口启动一次
$env:PORT=3001; npm run start:dev
PORT=3001 npm run start:dev
访问 http://localhost:3001,应看到同样的响应。说明端口由环境变量控制,而不是写死的 3000。关闭该进程后继续。
步骤 6:看懂装饰器相关的编译配置
打开 tsconfig.json,确认这两个关键选项:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2021",
"strict": true
}
}
experimentalDecorators 让 @Controller() 这种写法生效;emitDecoratorMetadata 让容器在运行时还能读到构造器参数的类型——依赖注入靠的就是它。TypeScript 5 虽然已支持新标准装饰器,但 NestJS 10 的生态仍使用这套实验性装饰器,不要自行关掉。
步骤 7:跑一次生产构建
npm run build
npm run start:prod
build 会先把 TypeScript 编译到 dist/,start:prod 运行编译后的 JS。开发阶段用 start:dev,部署阶段用 build 加 start:prod——不要用开发模式扛生产流量。
讲清楚要点
- 约定大于配置:优先用
nest new 生成骨架,少手搓工具链,把时间花在业务上。
- 入口只做 bootstrap:
main.ts 只负责创建应用和监听端口,业务逻辑下沉到模块树。
- 登记才能注入:Module 的
controllers / providers 是容器的装配清单,漏登记必然启动报错。
- dev 与 prod 分离:
start:dev 监听文件变化、快速反馈;生产运行 dist/ 编译产物。
- 端口随环境变化:用
process.env.PORT,写死 3000 只适合本机演示,部署到平台时端口通常由平台分配。
- 装饰器依赖编译选项:删掉
emitDecoratorMetadata,按类型自动注入就会失灵。
你可能会懵的地方
-
nest 命令找不到怎么办?
全局安装失败或 PATH 没刷新。可以直接用 npx @nestjs/cli new quiz-api,效果相同。
-
端口 3000 被占用?
换 PORT=3001 启动,或关掉占用 3000 的旧 Node 进程。Windows 可用 netstat -ano | findstr :3000 查进程。
-
改了代码浏览器仍旧?
看终端是否编译成功;存在 TS 错误时,热重载会停在上一次成功的产物,并不会强行启动坏代码。
-
AppController / AppService 能删吗?
可以,随业务模块逐步替换;但建议保留一个可响应的路由(如 /api/health)做存活探测。
-
strict 模式红线太多?
保留它。初学者可以把红线当成编译器在现场教学:它在逼你明确契约;到处用 any 逃避,只会把坑留到联调阶段。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 手动建空文件夹当项目 | 缺 Jest、tsconfig、CLI 配置 | nest new quiz-api |
生产环境跑 start:dev | 热重载开销、体积与安全风险 | npm run build + start:prod |
业务全写进 main.ts | 无法模块化、无法测试 | bootstrap 之外的逻辑全部下沉 |
Service 忘记放进 providers | 注入失败、启动报错 | 在 Module 中登记 |
关掉 emitDecoratorMetadata | 容器读不到参数类型 | 保留 CLI 生成的 tsconfig |
卡点急救
- 安装 CLI 卡住或超时:切换 npm 镜像(如
npm config set registry https://registry.npmmirror.com),或用 npx 免全局安装。
- 启动报
EADDRINUSE:端口被占,换 PORT 启动;或在任务管理器结束残留的 node 进程。
- 热重载不触发:确认使用的是
start:dev 而非 start;公司同步盘(OneDrive 等)可能干扰文件监听,把项目放到普通本地目录。
面试常问
-
Nest 应用的启动流程是什么?
main.ts 调用 NestFactory.create(AppModule),框架实例化 DI 容器、解析模块树、注册控制器与路由,最后 listen 端口。对应 Spring Boot 从主类启动并初始化 ApplicationContext。
-
为什么生产环境不直接跑 TypeScript 源码?
源码需要即时编译,启动更慢、内存更高,也依赖开发依赖。正式部署先编译为 JS 再用 Node 运行,产物确定、可复现。
-
start:dev 的热重载是怎么实现的?
CLI 内部用 watch 模式监听 src/ 文件变化,增量重编译后自动重启应用;它是开发效率工具,不用于生产。
自测三问
main.ts 和 AppModule 分别解决什么问题?
- 为什么修改
AppService 后,浏览器刷新就能看到新结果?
start:dev 和 start:prod 各适合什么阶段?差别在哪?
小结一句话
Nest CLI 生成可运行的工程骨架:main 负责启动,AppModule 负责装配,dev 热重载、prod 跑 dist。
延伸