一文讲清 NestJS 中 IoC、DI、AOP、DTO、Entity 等名词

上一篇《构建一个 NestJS 应用程序需要具备哪些基础元素?》里,我们把 Module / Controller / Provider / Guard / Pipe... 这些"组件角色"捋了一遍。

但很多人(包括我一开始)真正卡住的,其实是另一层:这些角色里面提到的名词到底表示啥?

这篇就专门把 NestJS 里常见的英文缩写/名词一次性讲清楚

先给一张速查表(用来对号入座)

缩写/名词 中文名 一句话记住 在 NestJS 里常落到哪
IoC (Inversion of Control) 控制反转 "对象怎么创建/怎么组合"交给框架 @nestjs/core 的容器/加载流程
DI (Dependency Injection) 依赖注入 "依赖不要自己 new,框架帮你注入" Provider 构造函数注入、@Inject()
AOP (Aspect-Oriented Programming) 面向切面编程 把日志/鉴权/校验这类横切逻辑"抽出去" Middleware / Guard / Pipe / Interceptor / Filter
DTO (Data Transfer Object) 数据传输对象 传输用的数据结构(尤其是入参) @Body() + DTO class + ValidationPipe
Entity 实体(实体类) 持久化模型(数据库表/集合的映射) TypeORM/Prisma/Mongoose 各自的实体/模型
ORM 对象关系映射 "对象 ↔ 表"映射(更广义:把数据库访问抽象成模型/接口) TypeORM / Prisma(Prisma 更像 ORM-like 的类型安全 DB Client)
ODM 对象文档映射 "对象 ↔ 文档"映射(Mongo) Mongoose
CRUD 增删改查 最常见的接口形态(创建/查询/更新/删除) Controller + Service + Repository
REST 表述性状态转移(常说 RESTful) 用"资源 + HTTP 方法"来组织 API Controller 的路由设计
HTTP 超文本传输协议 Web 接口最常见的传输方式 @Controller() / @Get() / @Post()
RPC 远程过程调用 更像"调用方法",常见于服务间通信 @nestjs/microservices
RxJS 响应式扩展 响应式编程库(NestJS 部分链路会用到) Interceptor 里的 pipe(map(...))
Observable 可观察对象/流 RxJS 的核心类型(可订阅的异步流) CallHandler.handle() 返回值
JWT JSON Web Token(JSON Web 令牌) 常见的无状态 token 方案 Guard + Passport Strategy
RBAC 基于角色的访问控制 "角色 → 权限"的经典权限模型 Guard(配合装饰器/元数据)
CQRS 命令查询职责分离 复杂业务下的读写分离组织方式 @nestjs/cqrs(可选,不是必需品)
CLI 命令行接口(脚手架) 生成模板代码、统一项目结构 @nestjs/cli
CORS 跨域资源共享 浏览器跨域访问控制 app.enableCors()
CSRF 跨站请求伪造 利用 Cookie 自动携带发起伪造请求 结合鉴权方式 + 中间件/策略设计

IoC:Inversion of Control(控制反转)

是什么

在软件工程里,IoC 指把"控制权"(对象创建、生命周期管理、依赖装配、调用时机等)从业务代码转移给框架/容器来统一管理。

它被提出的目的,是降低耦合集中扩展点 、让项目变大后依赖关系仍然可控(而不是到处 new、到处传参)。

一句话:对象的创建与组装不由你手写流程控制,而由框架在启动时统一完成。

适合干啥

  • 让项目变大后还能"有秩序地装配依赖"(否则满世界 import/new/传参)
  • 让模块边界更清晰:你用"声明"代替"到处调用"

何时使用

在 NestJS 里你基本"自动就在用 IoC"了,因为 NestJS 本身就是基于容器的框架。你需要做的更多是:别破坏它 (比如绕过容器到处 new)。

原理(抓重点就行)

  • 启动时,NestJS 会扫描模块(Module)、收集 Provider、构建依赖图(谁依赖谁)
  • 需要实例化某个类时,由容器按依赖图"自底向上"创建并缓存(默认单例)

伪代码示例

typescript 复制代码
// 你只声明"我需要什么"
@Injectable()
class UserService {
  constructor(private repo: UserRepo) {}
}

// 你只声明"这个模块包含哪些"
@Module({ providers: [UserRepo, UserService] })
class UserModule {}

// 剩下的"怎么创建 repo 再创建 service",框架搞定(IoC)

DI:Dependency Injection(依赖注入)

是什么

DI 是 IoC 的一种常见实现方式,它强调"依赖从外部注入",而不是在类内部主动创建依赖。

目的很直接:让代码更解耦 、依赖更可替换(方便测试/替身实现)、也更容易在大型项目里统一管理。

一句话:把"依赖关系"从代码里的 new,变成声明式注入。

适合干啥

  • 让 service 更容易测试(可以替换依赖、注入 mock)
  • 避免强耦合(service 不需要知道 repo 如何创建)

何时使用

你写 NestJS 业务时,绝大多数依赖都推荐走 DI:

  • service 依赖 repo / http client / config / logger
  • guard 依赖 auth service
  • interceptor 依赖 cache service

原理

DI 的核心是"token → provider"的映射:

  • 最常见 token 就是 class 本身(UserService
  • 容器根据构造函数参数的类型/注入 token,找到对应 provider,注入实例

伪代码示例

typescript 复制代码
@Injectable()
class UserService {
  constructor(private readonly repo: UserRepo) {}
}

// 自定义 token(比如注入第三方库实例)
const REDIS = Symbol('REDIS');

@Module({
  providers: [
    { provide: REDIS, useValue: /* redisClient */ {} },
    { provide: UserRepo, useClass: UserRepo },
  ],
})
class InfraModule {}

@Injectable()
class CacheService {
  constructor(@Inject(REDIS) private redis: any) {}
}

AOP:Aspect-Oriented Programming(面向切面)

是什么

AOP 是一种把"横切关注点"(logging、auth、validation、metrics 等)从核心业务逻辑中分离出来的思想,常见手段是拦截/代理,在函数执行前后"织入"通用逻辑。

提出它的目的,是减少重复代码、统一策略,让业务代码更专注在"做业务"。

一句话:把"到处都要做"的横切逻辑(日志/鉴权/校验/统一返回)从业务代码里抽出来。

适合干啥

  • 统一做日志、耗时统计、异常格式、权限校验
  • 让 controller/service 更"干净",专注业务

何时使用(别上来就 AOP 过度)

当你发现同一段逻辑在 N 个接口里重复出现时,AOP 才开始值钱。

如果只是一个接口的特殊处理,直接写在 handler 里往往更直观。

原理(用 NestJS 的话来讲)

NestJS 不是"只有一种 AOP",它把 AOP 拆成几类工具,各司其职:

  • Middleware:请求刚进门(路由前)
  • Guard:能不能进(鉴权/权限)
  • Pipe:入参校验/转换
  • Interceptor:前后包一层(统一返回/缓存/耗时)
  • Exception Filter:统一错误输出

它们共同点是:不改业务函数签名,也能在请求链路上插入逻辑。

伪代码示例

typescript 复制代码
// 统一返回结构:{ code, data, traceId }
@Injectable()
class WrapResponseInterceptor implements NestInterceptor {
  intercept(ctx, next) {
    const traceId = /* get from request */ 'xxx';
    return next.handle().pipe(map(data => ({ code: 0, data, traceId })));
  }
}

// 鉴权:没 token 就不让进
@Injectable()
class AuthGuard implements CanActivate {
  canActivate(ctx) {
    const req = ctx.switchToHttp().getRequest();
    return Boolean(req.headers.authorization);
  }
}

DTO:Data Transfer Object(数据传输对象)

是什么

DTO 常用于分层架构/分布式系统的"边界处",用来描述数据在层与层(或服务与服务)之间传输的结构。它强调"只承载数据",不强调业务行为。

提出它的目的,是把外部输入/输出与内部模型隔离开:API 契约清晰不把内部 Entity/领域对象直接暴露出去

一句话:专门用来"接收/传输"的数据结构(尤其是"请求入参")。

适合干啥

  • 把"接口入参长啥样"讲清楚(更容易维护)
  • 配合校验:让脏数据在进业务之前就被拦住

何时使用

只要是对外接口(HTTP/RPC/GraphQL)基本都建议用 DTO:

  • CreateUserDtoUpdateUserDtoQueryUserDto

原理(别纠结细节,抓住链路)

典型链路是:@Body() 拿到原始对象 → ValidationPipe 校验/转换 → 传入 controller 方法参数。

常见组合是 class-validator + class-transformer(你会在项目里看到它们)。

伪代码示例

typescript 复制代码
class CreateUserDto {
  // @IsEmail()
  email: string;

  // @MinLength(8)
  password: string;
}

@Controller('users')
class UserController {
  @Post()
  create(@Body() dto: CreateUserDto) {
    return this.userService.create(dto);
  }
}

// main.ts 里常见开启方式
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));

Entity:实体(通常指持久化实体)

是什么

在不同语境里 Entity 有两层含义:

  • 在 DDD 里,Entity 是"有持续身份(identity)的对象",即便属性变了,它仍然是同一个实体
  • 在 ORM 里,Entity 往往指"表/集合的映射模型"(实体类/模型定义)
    提出它的目的,是用更结构化的方式表达持久化数据(字段/关系/约束),让数据访问与演进更可维护。

一句话:"数据库里的数据结构"在代码里的对应物

注意:Entity 本身不是 NestJS 独有,它更多来自你选的 ORM/ODM。

适合干啥

  • 在数据库层做结构化建模(表字段、索引、关系)
  • 让查询/写入更可维护(至少比字符串拼 SQL 好维护一些)

何时使用

取决于你选型:

  • 用 TypeORM:你会写 @Entity() 这类实体类
  • 用 Prisma:你写的是 schema.prisma 的模型(更像"模型定义 + 类型安全访问层",不一定叫 Entity,但在项目里的角色类似)
  • 用 Mongoose:你会写 Schema/Model(更偏 ODM)

原理(大方向)

Entity/Model 提供"结构 + 映射 + 生命周期(可选)",最终目标是:把 DB 操作封装成更可控的接口。

伪代码示例(以 TypeORM 思路举例)

typescript 复制代码
@Entity('users')
class UserEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  email: string;
}

@Injectable()
class UserRepo {
  constructor(/* inject repository */) {}
}

ORM(对象关系映射)/ ODM(对象文档映射):到底差在哪?

ORM(Object-Relational Mapping,对象关系映射)

ORM 通过把"关系型数据库的表/行/关系"映射为代码里的对象/模型,让你用更高层的接口去读写数据。

提出它的目的,是减少手写 SQL 的重复劳动,让数据访问更结构化、更易演进(当然也会带来一定抽象成本)。

  • 面向关系型数据库(MySQL/PostgreSQL...)
  • 关键词:表、行、关联、事务

ODM(Object-Document Mapping,对象文档映射)

ODM 把"文档数据库的文档/集合/嵌套结构"映射为代码里的对象/模型,帮助你更一致地读写 MongoDB 等文档型存储。

目的类似 ORM:让数据结构和访问方式更可控,只是底层存储模型不同。

  • 面向文档型数据库(MongoDB...)
  • 关键词:文档、集合、嵌套结构

何时用哪一个

别纠结名词,按存储选型走:你用 MySQL/PG 就看 ORM,你用 Mongo 就看 ODM。


CRUD(增删改查)/ REST(表述性状态转移):为什么老看到它俩一起出现?

CRUD 是什么

CRUD 是持久化系统里最常见的一组基础操作集合(Create / Read / Update / Delete)。

提出它的目的,是用一套通用词汇把"增删改查"这类需求说清楚,方便分工、设计接口与评审。

一句话:Create / Read / Update / Delete,增删改查。

REST 是什么

REST(Representational State Transfer)最初是 Roy Fielding 在博士论文里提出的一种架构风格,用"资源 + 表述 + 约束"来组织系统交互。

它的目的,是让 API 的语义更统一、可被 HTTP 的缓存/状态码等机制更好地利用。

一句话:用"资源 + HTTP 方法"来组织 API (比如 /users/:id + GET/POST/PUT/DELETE)。

在 NestJS 里怎么体现

CRUD 更像"你要做的事",REST 更像"你怎么设计接口"。

NestJS 的 Controller 很适合写 REST 风格的 CRUD(但你也可以写 RPC 风格,不强制)。

typescript 复制代码
@Controller('users')
class UserController {
  @Post() create(@Body() dto) {}
  @Get(':id') findOne(@Param('id') id) {}
  @Put(':id') update(@Param('id') id, @Body() dto) {}
  @Delete(':id') remove(@Param('id') id) {}
}

JWT(JSON Web Token)/ RBAC(基于角色的访问控制):鉴权和权限常见两套缩写

JWT(JSON Web Token,JSON Web 令牌)

JWT 是一种开放标准(RFC 7519)定义的令牌格式,用 JSON 结构承载声明(claims),常见形式是 header.payload.signature

它被广泛使用的目的,是让身份信息在服务之间可传递、可验证(尤其适合前后端分离/多服务场景)。

一句话:一种无状态 token(常用来承载登录态/身份声明)。

什么时候用:前后端分离、移动端、需要跨服务验证身份时很常见。

什么时候别硬上:你如果只是内部小系统、单体应用,Session 也完全可以。

伪代码(NestJS 常见落点):

typescript 复制代码
// 认证:验证 token 的逻辑一般放 Guard / Strategy
@UseGuards(JwtAuthGuard)
@Get('me')
me(@Req() req) {
  return req.user;
}

RBAC(Role-Based Access Control,基于角色的访问控制)

RBAC 是经典的访问控制模型,用"角色(Role)"作为权限分配与授权的中间层(用户 ↔ 角色 ↔ 权限)。

目的,是让权限管理更可维护:你通常给用户分配角色,而不是给每个用户逐条配置权限。

一句话:按角色控制权限(admin/user...)。

落点通常是 Guard + 装饰器元数据:

typescript 复制代码
@SetMetadata('roles', ['admin'])
@UseGuards(AuthGuard, RolesGuard)
@Delete(':id')
remove() {}

CQRS:Command Query Responsibility Segregation(命令查询职责分离,常被简化成"读写分离")

是什么

CQRS 提倡把"改变系统状态的命令(Command)"与"读取系统状态的查询(Query)"分离成不同的模型/路径。

提出它的目的,是在复杂业务里隔离读写关注点:写侧强调规则与一致性,读侧强调展示与查询效率(但也会增加架构复杂度)。

一句话:把"写操作(Command)"和"读操作(Query)"拆成不同模型/流程

适合干啥

  • 复杂业务、读写模型差异巨大时,能让代码更清晰
  • 事件驱动/审计需求强时更常见

何时使用

别上来就 CQRS。通常是项目复杂到:

  • service 越写越像一坨"超级函数"
  • 写操作需要强规则/强审计
  • 读操作需要高度定制的视图模型 再考虑引入(NestJS 有 @nestjs/cqrs 可选)。

HTTP(超文本传输协议)/ RPC(远程过程调用):请求进来到底是哪条路?

是什么

HTTP 是应用层网络协议,用于客户端与服务端之间的请求/响应通信;RPC 是一种远程调用模型,强调"像调用本地函数一样调用远端能力"。

把它们放在一起讲的目的,是让你明确 NestJS 不只做 HTTP:它既能做 Web API,也能做微服务通信。

  • HTTP:最常见的 Web 接口方式(REST/JSON 都算在这里面)
  • RPC:更像"调用一个方法",不强制资源风格(常见于微服务通信)

适合干啥

  • 你做 BFF / Web API:基本就是 HTTP
  • 你做服务拆分、服务间通信:RPC(或者消息队列)会更常见

何时使用(说人话)

小中型系统,先把 HTTP 写顺就行;

只有当你确实遇到"服务间调用多、边界清晰、协议要统一"时,再去考虑 RPC/Microservices 那套。

原理(在 NestJS 里怎么体现)

  • HTTP:@Controller() + @Get()/@Post() 这一套装饰器,走的是平台适配层(Express/Fastify)
  • RPC:通常用 @nestjs/microservices,通过 transport(TCP/Redis/NATS/Kafka...)收发消息

伪代码示例

typescript 复制代码
// HTTP(你已经很熟了)
@Controller('users')
class UserController {
  @Get(':id')
  findOne(@Param('id') id: string) {}
}

// RPC(示意):message pattern 触发一个 handler
// @MessagePattern({ cmd: 'user.findOne' })
// findOne(payload) {}

RxJS(响应式扩展)/ Observable(可观察对象):为啥 NestJS 老出现 rxjs?

是什么

RxJS 是基于 Observable 的响应式编程库,用来表达"时间维度上的数据流";Observable 则是可订阅的异步流抽象。

它们出现的目的,是更自然地处理"流式、可组合、可取消/重试"的异步场景(NestJS 的部分执行链路也选择了它作为抽象)。

  • RxJS:响应式编程库
  • Observable:RxJS 的核心数据类型(可以理解成"可订阅的异步流")

适合干啥

你不用为了 NestJS 去"强行学响应式编程"。它主要在两块很常见:

  • 拦截器/管道链路next.handle().pipe(map(...)) 这种写法(你在 Interceptor 里已经见过)
  • 流式/事件式场景:SSE、WebSocket、某些需要持续推送的接口

何时使用

  • 你只写普通 CRUD:Promise/async-await 够用,别硬上 Observable
  • 你需要"组合多个异步来源""做流式处理/取消/重试":Observable 就很香

原理(够用版)

NestJS 允许 controller 返回:

  • primitive / object(直接返回)
  • Promise<T>(等 promise resolve)
  • Observable<T>(内部订阅,取最终值/流)

Interceptor 里之所以经常用 RxJS,是因为 CallHandler.handle() 返回的就是 Observable。

伪代码示例

typescript 复制代码
// 典型 interceptor:对返回值做 map
intercept(ctx, next) {
  return next.handle().pipe(
    map(data => ({ code: 0, data })),
  );
}

// controller 返回 Observable(示意)
@Get()
list() {
  // return from([1, 2, 3]);
}

CLI:Command Line Interface(命令行接口/脚手架)

是什么

CLI(Command Line Interface)是通过命令行与工具交互的一种方式;在 NestJS 语境下通常特指官方脚手架,用于生成模板代码与维护约定结构。

它的目的,是减少重复劳动、统一项目结构,让团队协作更顺。

一句话:帮你生成模板代码、少手敲一些重复文件

适合干啥

  • 新建 module/controller/service
  • 一键生成资源(CRUD 模板),并把文件结构按约定摆好

何时使用

你如果是团队项目,建议统一用 CLI 生成骨架,代码风格更一致;

个人练手也可以不用,但熟悉一下常用命令挺省事。

原理(不深究)

CLI 本质是代码生成器(schematics),按模板生成文件 + 更新模块引用。

伪代码示例(命令示意)

bash 复制代码
nest g module user
nest g controller user
nest g service user
# nest g resource user  # 想要 CRUD 模板时再用

CORS(跨域资源共享)/ CSRF(跨站请求伪造):Web 安全里最常被混淆的两个缩写

它们是什么

CORS 是浏览器同源策略下的一套"跨域放行机制"(通过响应头协商);CSRF 是一种利用浏览器自动携带 Cookie 的攻击方式。

把它俩放一起的目的,是提醒你:跨域(CORS)和伪造请求(CSRF)是两件事,不要混着处理。

  • CORS(Cross-Origin Resource Sharing):浏览器的跨域访问控制("能不能从别的域来调用我")
  • CSRF(Cross-Site Request Forgery):利用浏览器自动带 Cookie 的特性发起伪造请求("我是不是被借刀了")

适合干啥

  • CORS:前后端分离、不同域名端口开发时必须处理
  • CSRF:当你用 Cookie 维持登录态 且接口会产生副作用(转账/下单/改资料)时,需要重点考虑

何时使用(简化判断)

  • 你用 Authorization: Bearer <token>(JWT)这类 header 携带 token:CSRF 风险通常更低(但不代表"完全不用管安全")
  • 你用 Cookie + Session:CSRF 基本要纳入设计(同站策略、token、双重提交等)

原理(够用版)

  • CORS:是浏览器限制,你服务器得返回合适的响应头
  • CSRF:是攻击方式,你得让"跨站伪造请求"失效

伪代码示例

typescript 复制代码
// CORS:NestJS 常见开启方式(示意)
app.enableCors({
  origin: ['https://your-frontend.example'],
  credentials: true,
});

最后

如果你现在只想快速上手 NestJS,我建议按这个顺序消化:

  1. 先把 IoC / DI 吃透(这决定你后面写代码是不是舒服)
  2. 再把 AOP 在 NestJS 的落点(Guard/Pipe/Interceptor/Filter)对上请求链路
  3. 然后用 DTO + ValidationPipe 把"入参质量"先稳住
  4. 至于 Entity/ORM/ODM、JWT/RBAC、CQRS,就按项目需要逐步加,不用一次性把工具箱搬回家

以上是我学习 NestJS 过程中的一些整理与理解,欢迎在评论区补充/讨论;如果哪里有偏差,也欢迎直接指出,我会及时修正。

相关推荐
Mr_li1 小时前
一次讲透 NestJS 里“绑定”(全局 vs 局部)
node.js
Mr_li3 小时前
构建一个 NestJS 应用程序需要具备哪些基础元素?
node.js
UIUV6 小时前
AI Agent 开发实战:从原理到最小化实现
后端·langchain·node.js
2301_816997886 小时前
Webpack基础
前端·webpack·node.js
Qinana7 小时前
解构 LangChain Tool Calling:从 Schema 定义到 Agent 执行循环的深度解析
前端·javascript·node.js
朝朝暮暮an8 小时前
Day 4|Node.js 文件系统、路径与进程管理
node.js
朝朝暮暮an8 小时前
Day 3|Node.js 事件循环 & 异步模型深入(Part 2,3)
node.js
章丸丸1 天前
Tube - Video Reactions
react.js·node.js·next.js
折七1 天前
2026 年 Node.js 后端技术选型,为什么我选了 Hono 而不是 NestJS
前端·后端·node.js