第一课:走进 NestJS — 理念、环境与第一个应用

3 阅读10分钟

覆盖文档:Introduction, First Steps, CLI Overview, CLI Scripts, CLI Workspaces 前置知识:Node.js 基础, TypeScript 基础 源码重点:packages/core/nest-factory.ts, packages/core/constants.ts


一、NestJS 是什么

1.1 一句话定义

NestJS 是一个基于 Node.js 的渐进式服务端框架,用 TypeScript 构建,融合了面向对象编程(OOP)、函数式编程(FP)和函数响应式编程(FRP)的思想。

1.2 NestJS 与 Express/Fastify 的关系

┌────────────────────────────────────────┐
│            NestJS(架构层)              │
│  模块系统 + DI 容器 + 装饰器 + 管线      │
│                                        │
│  ┌─────────────┐  ┌─────────────────┐  │
│  │   Express    │  │    Fastify      │  │
│  │  (HTTP 引擎) │  │   (HTTP 引擎)   │  │
│  └─────────────┘  └─────────────────┘  │
│        ↑  默认             ↑  可选      │
│        └───────────────────┘           │
│           AbstractHttpAdapter          │
└────────────────────────────────────────┘
  • Express 是一个极简的 HTTP 框架,处理请求/响应
  • Fastify 是一个高性能 HTTP 框架,约 2 倍于 Express 的吞吐
  • NestJS 不是 Express 的替代品,而是在 Express/Fastify 之上提供了企业级架构能力

NestJS 通过 AbstractHttpAdapter 抽象了底层 HTTP 引擎,核心代码不依赖任何具体 HTTP 库。

1.3 设计哲学

NestJS 的架构深受 Angular 启发:

概念Angular(前端)NestJS(后端)
模块@NgModule()@Module()
组件/控制器@Component()@Controller()
服务@Injectable()@Injectable()
依赖注入构造器注入构造器注入
装饰器大量使用大量使用

核心理念:约定优于配置,通过装饰器和模块系统提供可预测的代码组织方式。


二、环境准备与项目创建

2.1 环境要求

# 确认 Node.js 版本 >= 20
node --version

# 全局安装 Nest CLI
npm i -g @nestjs/cli

# 验证 CLI 安装
nest --version

2.2 创建项目

# 标准创建
nest new my-app

# 严格 TypeScript 模式(推荐)
nest new my-app --strict

# 选择包管理器:npm / yarn / pnpm

2.3 项目目录结构

my-app/
├── src/
│   ├── main.ts                 # 应用入口:创建并启动 NestJS 应用
│   ├── app.module.ts           # 根模块:整个应用的组织入口
│   ├── app.controller.ts       # 示例控制器:处理 HTTP 请求
│   ├── app.controller.spec.ts  # 控制器的单元测试
│   └── app.service.ts          # 示例服务:封装业务逻辑
├── test/
│   ├── app.e2e-spec.ts         # 端到端测试
│   └── jest-e2e.json           # E2E 测试配置
├── nest-cli.json               # Nest CLI 配置
├── tsconfig.json               # TypeScript 编译配置
├── tsconfig.build.json         # 构建用 TS 配置
├── package.json                # 项目依赖和脚本
└── eslint.config.mjs           # ESLint 配置

2.4 核心文件解析

main.ts — 应用入口

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  // 1. 创建 NestJS 应用实例
  const app = await NestFactory.create(AppModule);
  
  // 2. 监听端口
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

NestFactory.create() 是整个框架的入口,它完成了以下工作:

  1. 创建 DI 容器(NestContainer
  2. 扫描模块树(DependenciesScanner
  3. 实例化所有依赖(InstanceLoader
  4. 注册路由(RoutesResolver

app.module.ts — 根模块

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],        // 导入其他模块
  controllers: [AppController],  // 注册控制器
  providers: [AppService],       // 注册服务(Provider)
})
export class AppModule {}

@Module() 装饰器定义了模块的四大属性:

  • imports:导入的依赖模块
  • controllers:该模块包含的控制器
  • providers:该模块包含的服务/提供者
  • exports:导出给其他模块使用的提供者

app.controller.ts — 控制器

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
  • @Controller() 声明这是一个路由控制器
  • @Get()getHello() 方法映射到 GET / 路由
  • constructor(private readonly appService: AppService) — 通过构造器注入 AppService

app.service.ts — 服务

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
  • @Injectable() 声明这个类可以被 NestJS 的 DI 容器管理和注入

2.5 启动与验证

# 开发模式(文件变更自动重启)
npm run start:dev

# 生产模式
npm run build
npm run start:prod

# 使用 SWC 加速编译(20x 提速)
npm run start -- -b swc

访问 http://localhost:3000,看到 Hello World! 即成功。


三、Nest CLI 工具链

3.1 核心命令

命令别名功能
nest new <name>nest n创建新项目
nest generate <type> <name>nest g代码生成
nest build编译项目
nest start启动项目
nest infonest i显示系统信息
nest add <library>添加外部库

3.2 代码生成器(18 种 Schematic)

# 常用生成命令
nest g module users        # 生成模块
nest g controller users    # 生成控制器
nest g service users       # 生成服务
nest g resource cats       # 一键生成完整 CRUD(模块+控制器+服务+DTO+实体)

# 其他可生成的类型
nest g class        # 普通类
nest g decorator    # 装饰器
nest g filter       # 异常过滤器
nest g gateway      # WebSocket 网关
nest g guard        # 守卫
nest g interceptor  # 拦截器
nest g interface    # 接口
nest g middleware   # 中间件
nest g pipe         # 管道
nest g provider     # 提供者
nest g resolver     # GraphQL 解析器
nest g library      # 共享库(Monorepo)
nest g app          # 子应用(Monorepo)

常用选项:

  • --dry-run:预览生成结果,不实际创建文件
  • --no-spec:不生成测试文件
  • --flat:不创建子目录

3.3 nest-cli.json 配置

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "builder": "tsc",        // 编译器:tsc | swc | webpack
    "typeCheck": true,        // SWC 模式下是否做类型检查
    "deleteOutDir": true,     // 编译前清空输出目录
    "assets": [],             // 需要复制的非 TS 文件
    "watchAssets": false      // watch 模式下是否监控 assets
  },
  "generateOptions": {
    "spec": true,             // 是否生成测试文件
    "flat": false             // 是否使用扁平目录结构
  }
}

3.4 脚本管理

推荐在 package.json 中使用本地安装的 CLI:

{
  "scripts": {
    "build": "nest build",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
    "test": "jest",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "devDependencies": {
    "@nestjs/cli": "^11.0.0"
  }
}

本地安装 CLI 的优势:团队成员使用统一版本,避免全局版本冲突。


四、平台选择与多平台抽象

4.1 Express 平台(默认)

// 无需额外配置,默认使用 Express
const app = await NestFactory.create(AppModule);

特点:

  • 生态最丰富,社区资源多
  • 大量 Express 中间件可直接使用
  • 适合绝大多数场景

4.2 Fastify 平台

npm i --save @nestjs/platform-fastify
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  // Fastify 默认只监听 localhost,需显式指定 0.0.0.0
  await app.listen(3000, '0.0.0.0');
}
bootstrap();

特点:

  • 约 2 倍于 Express 的吞吐量
  • 内置 JSON Schema 验证
  • 不兼容所有 Express 中间件

4.3 平台差异对照

特性ExpressFastify
性能中等高(~2x)
中间件生态极丰富较少,但有对应插件
文件上传Multer(内置支持)需要额外处理
装饰器@Res() → Express Response@Res() → Fastify Reply
JSON 解析body-parser内置
重定向res.redirect()res.status(302).redirect()

4.4 NestFactory 的三种创建方式

// 1. 创建完整 HTTP 应用(最常用)
const app = await NestFactory.create(AppModule);

// 2. 创建微服务
const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.TCP,
});

// 3. 创建应用上下文(无 HTTP,用于 CLI/CRON/Worker)
const ctx = await NestFactory.createApplicationContext(AppModule);

4.5 创建选项

const app = await NestFactory.create(AppModule, {
  // 出错时退出进程(默认 true)
  abortOnError: true,
  
  // 日志配置:false 禁用 | 日志级别数组
  logger: ['error', 'warn', 'log'],
  
  // 是否启用 CORS
  cors: true,
  
  // 在 useLogger() 前缓冲日志
  bufferLogs: false,
  
  // 获取原始请求体(用于 Webhook 签名验证)
  rawBody: false,
  
  // 强制关闭连接(优雅关闭时不等待 Keep-Alive)
  forceCloseConnections: false,
});

五、Monorepo 与工作区

5.1 Standard 模式 vs Monorepo 模式

特性StandardMonorepo
项目数单个多个应用 + 多个库
编译器tsc(默认)webpack(默认)
目录结构src/apps/ + libs/
共享代码libs/ 中的共享库

5.2 创建 Monorepo

# 在现有标准项目中添加子应用(自动转为 Monorepo)
nest g app admin-api

# 创建共享库
nest g library shared

# 构建指定项目
nest build admin-api

# 启动指定项目
nest start admin-api

转换后的目录结构:

my-workspace/
├── apps/
│   ├── my-app/          # 主应用
│   │   └── src/
│   └── admin-api/       # 子应用
│       └── src/
├── libs/
│   └── shared/          # 共享库
│       └── src/
├── nest-cli.json        # 统一配置
└── tsconfig.json

5.3 共享库的使用

共享库通过 TypeScript path aliases 引用:

// 在任何应用中直接导入共享库
import { SharedModule } from '@app/shared';

共享库不能独立运行,必须被应用导入使用。


六、源码解读:NestFactory.create() 启动流程

[资深] 本节面向希望深入理解框架内部机制的读者。

6.1 入口文件

文件位置:packages/core/nest-factory.ts

// NestFactory 是一个单例静态类
export class NestFactoryStatic {
  private readonly logger = new Logger('NestFactory', { timestamp: true });
  private abortOnError = true;

  public async create<T extends INestApplication>(
    moduleCls: IEntryNestModule,
    serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
    options?: NestApplicationOptions,
  ): Promise<T> {
    // 1. 解析参数:区分传入的是 httpAdapter 还是 options
    const [httpServer, appOptions] = this.isHttpServer(serverOrOptions!)
      ? [serverOrOptions, options]
      : [this.createHttpAdapter(), serverOrOptions];

    // 2. 创建应用配置和 DI 容器
    const applicationConfig = new ApplicationConfig();
    const container = new NestContainer(applicationConfig, appOptions);

    // 3. 初始化(扫描 + 实例化 + 注册路由)
    await this.initialize(moduleCls, container, ...);

    // 4. 创建 NestApplication 实例
    const instance = new NestApplication(container, httpServer, ...);
    return this.createAdapterProxy<T>(target, httpServer);
  }
}

6.2 initialize() — 核心初始化流程

private async initialize(module, container, graphInspector, config, options, httpServer) {
  // 1. 创建依赖注入器
  const injector = new Injector({ preview: options.preview });
  
  // 2. 创建实例加载器
  const instanceLoader = new InstanceLoader(container, injector, graphInspector);
  
  // 3. 创建依赖扫描器
  const dependenciesScanner = new DependenciesScanner(container, metadataScanner, graphInspector, config);

  // 4. 在异常安全区内执行核心流程
  await ExceptionsZone.asyncRun(async () => {
    // 4a. 扫描模块树:递归解析 @Module() 的 imports
    await dependenciesScanner.scan(module);
    
    // 4b. 实例化所有依赖:按依赖顺序创建 provider 实例
    await instanceLoader.createInstancesOfDependencies();
    
    // 4c. 注册全局增强器:APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER
    dependenciesScanner.applyApplicationProviders();
  });
}

6.3 关键组件职责

NestFactory.create(AppModule)
       │
       ├─→ ApplicationConfig        配置管理(全局前缀、版本控制等)
       ├─→ NestContainer             DI 容器(管理所有模块和提供者)
       ├─→ DependenciesScanner       模块扫描器(解析 @Module 装饰器)
       ├─→ InstanceLoader            实例加载器(创建 provider 实例)
       ├─→ Injector                  依赖注入器(解析依赖关系)
       ├─→ GraphInspector            依赖图检查器(DevTools 用)
       ├─→ ExceptionsZone            异常安全区(全局 try-catch)
       └─→ NestApplication           应用实例(对外暴露 API)

6.4 ExceptionsZone 的作用

ExceptionsZone 是框架的全局异常保护层。create() 返回的 app 对象实际上是一个 Proxy,所有方法调用都被包裹在 ExceptionsZone 中:

// 文件:packages/core/nest-factory.ts
private createProxy(target: any) {
  const proxy = this.createExceptionProxy();
  return new Proxy(target, { get: proxy, set: proxy });
}

这意味着:

  • 框架初始化过程中的任何异常都会被捕获
  • abortOnError: true(默认)时,未捕获异常导致 process.abort()
  • abortOnError: false 时,异常会被重新抛出供调用者处理

6.5 MESSAGES 常量

文件位置:packages/core/constants.ts

export const MESSAGES = {
  APPLICATION_START: `Starting Nest application...`,
  APPLICATION_READY: `Nest application successfully started`,
  MICROSERVICE_READY: `Nest microservice successfully started`,
  UNKNOWN_EXCEPTION_MESSAGE: 'Internal server error',
};

// 全局增强器注册 token
export const APP_INTERCEPTOR = 'APP_INTERCEPTOR';
export const APP_PIPE = 'APP_PIPE';
export const APP_GUARD = 'APP_GUARD';
export const APP_FILTER = 'APP_FILTER';

启动时看到的 Starting Nest application...Nest application successfully started 日志就来自这里。


七、技术选型与架构定位

[架构] 本节面向技术负责人和架构师。

7.1 NestJS 适用场景

场景适合度说明
企业级 REST API★★★★★核心场景,模块化 + DI + 管线
微服务集群★★★★★内置 7 种传输层 + 自定义传输
BFF 层(Backend for Frontend)★★★★★GraphQL Federation + REST 网关
全栈应用(MVC)★★★★支持模板渲染,但不是主打
WebSocket 实时应用★★★★内置 WebSocket Gateway
CLI 工具★★★nest-commander 支持
Serverless 函数★★★冷启动开销需要优化
极简单页 API★★过度设计,Express/Fastify 更合适

7.2 与其他框架对比

特性NestJSExpressFastifySpring Boot
语言TypeScriptJavaScriptJavaScriptJava
架构约定式(模块+DI)自由式自由式约定式(模块+DI)
学习曲线中等
DI 容器内置内置(Spring IoC)
微服务内置需自建需自建内置(Spring Cloud)
性能中(Express)/高(Fastify)
生态丰富且增长快最丰富增长中最成熟

7.3 团队接入成本评估

有 Angular 经验的团队:几乎零学习成本,概念完全对应 有 Spring Boot 经验的团队:1-2 周适应,核心概念相同 纯 Express 背景的团队:2-4 周适应,主要是 DI 和模块化思维的转变 全新 Node.js 团队:4-6 周,需同时学习 TypeScript + NestJS


八、课后实践

练习 1:创建并运行项目(基础)

nest new cats-api --strict
cd cats-api
npm run start:dev
# 访问 http://localhost:3000 确认 Hello World

练习 2:切换到 Fastify 平台(中阶)

npm i --save @nestjs/platform-fastify

修改 main.ts

import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000, '0.0.0.0');
}
bootstrap();

验证:同样访问 http://localhost:3000 看到 Hello World。

练习 3:使用 SWC 加速编译(中阶)

npm i --save-dev @swc/cli @swc/core
npm run start -- -b swc --watch

对比 tsc 和 SWC 的启动速度差异。

练习 4:探索 CLI 生成器(基础)

# 预览生成结果(不实际创建)
nest g resource cats --dry-run

# 实际生成
nest g resource cats

观察生成的文件结构,理解 NestJS 推荐的代码组织方式。

练习 5:阅读 NestFactory 源码(资深)

打开 packages/core/nest-factory.ts,阅读 create()initialize() 方法,回答:

  1. DependenciesScanner.scan() 在什么时候被调用?
  2. InstanceLoader.createInstancesOfDependencies() 做了什么?
  3. ExceptionsZone.asyncRun() 为什么要包裹初始化过程?
  4. 为什么返回的 app 对象是一个 Proxy

九、本课知识点总结

知识点要点
NestJS 定位架构层框架,构建在 Express/Fastify 之上
核心文件main.ts(入口)→ app.module.ts(根模块)→ Controller → Service
NestFactory.create()创建容器 → 扫描模块 → 实例化依赖 → 注册路由
CLInest new(创建), nest g(生成), nest build(编译), nest start(运行)
平台选择Express(默认,生态丰富)vs Fastify(高性能)
Monoreponest g app(子应用), nest g library(共享库)
源码入口packages/core/nest-factory.tsinitialize()

下一课预告:第二课将深入 Controller,学习路由系统、请求参数提取、DTO 定义、版本控制等核心内容。