Nest.js

47 阅读12分钟

一个渐进式 Node.js 框架,用于构建高效、可靠且可扩展的服务器端应用

Nest(NestJS)是一个用于构建高效、可扩展 Node.js 服务器端应用的框架。它使用渐进式 JavaScript,构建并完全支持 TypeScript(同时仍允许开发者纯 JavaScript 编程),并结合了面向对象编程(OOP)、函数式编程(FP)和函数式反应式编程(FRP)的元素。

在底层,Nest 利用了强大的 HTTP Server 框架,比如 Express(默认),并且可以选择配置使用 Fastify

三层架构和MVC

三层架构

三层架构是软件架构设计的分层思想,核心是将系统按职责划分为三个独立层次,目的是解耦、提高复用性和可维护性,是后端开发的基础架构模式。

1. 核心分层(从下到上)
层次核心职责典型技术 / 示例
数据访问层(DAL)仅负责和数据库 / 文件等持久化存储交互(CRUD),不包含业务逻辑MyBatis、JPA、SQL 语句、数据映射
业务逻辑层(BLL)核心层,处理业务规则、流程、校验、事务等,调用 DAL 获取 / 操作数据服务类(Service)、业务规则引擎
表示层(UI/PL)负责和用户 / 前端交互,接收请求、返回响应,调用 BLL 处理业务(不做业务逻辑)Controller、API 接口、前
2. 核心特点
  • 单一职责:每层只做一件事(DAL 管数据、BLL 管业务、UI 管交互);
  • 解耦:修改某一层不影响其他层(比如换数据库,只需改 DAL,BLL 和 UI 不动);
  • 复用性:BLL 可被多个 UI 层复用(比如 PC 端、移动端共用一套业务逻辑)。
3. 典型执行流程

用户请求 → 表示层(Controller)接收 → 调用业务逻辑层(Service)处理 → 业务逻辑层调用数据访问层(DAO)操作数据库 → 结果逐层返回 → 表示层响应给用户。

三、三层架构 vs MVC:核心区别与关联

1. 核心区别
维度三层架构MVC
定位整体软件的分层架构(后端为主)界面交互的设计模式(前后端)
关注点业务逻辑的分层解耦界面交互的职责分离
覆盖范围后端全流程(数据→业务→交互)主要覆盖 “交互层 + 数据展示”
核心目标提高后端代码的复用性、可维护性解决界面与数据的耦合
2. 关联(最易混淆的点)
  • MVC 的Model ≈ 三层架构的BLL + DAL(Model 封装业务逻辑和数据,对应三层的业务层 + 数据层);
  • MVC 的Controller + View ≈ 三层架构的表示层(UI) (Controller 处理请求、View 展示界面,共同构成三层的表示层);
  • 实际开发中,两者常结合使用:比如 SpringMVC(MVC 模式)+ 三层架构(Service+DAO+Controller)。

总结

  • 三层架构是按 “数据 - 业务 - 交互” 职责分层,解决后端整体解耦问题;
  • MVC 是按 “数据 - 展示 - 调度” 职责拆分,解决界面交互的解耦问题;
  • 三层架构是 “宏观分层”,MVC 是 “局部模式”,两者互补,而非对立,实际开发中常结合使用(比如 Spring Boot 项目中,Controller=MVC 的 C,Service = 三层的 BLL+MVC 的 Model,DAO = 三层的 DAL,前端页面 = MVC 的 V)

IOC (Inversion of Control)

IOC(控制反转)是软件工程中的一种设计思想 / 原则,核心是颠覆 “对象创建和依赖管理” 的控制权 —— 从 “开发者手动控制” 转移到 “容器自动控制”,目的是降低代码耦合、提高可维护性和扩展性,是 Spring 框架的核心思想之一。

1. 先理解:没有 IOC 的 “传统模式”(控制权在开发者)

在传统代码中,对象的创建、依赖的注入完全由开发者手动编写,比如:


// 数据层实现类
public class UserDAOImpl implements UserDAO {
    public void queryUser() {
        System.out.println("查询用户数据");
    }
}

// 业务层依赖数据层
public class UserService {
    // 手动创建依赖的DAO对象
    private UserDAO userDAO = new UserDAOImpl(); 
    
    public void getUser() {
        userDAO.queryUser();
    }
}

// 调用层
public class Test {
    public static void main(String[] args) {
        // 手动创建Service对象
        UserService service = new UserService(); 
        service.getUser();
    }
}

问题

  • 耦合极高:如果UserDAOImpl需要替换(比如换UserDAOMySQLImpl),必须修改UserService的代码;
  • 维护成本高:所有对象的创建、依赖都要开发者手动管理,系统复杂后极易出错;
  • 可测试性差:无法灵活替换依赖(比如测试时想模拟UserDAO,必须改代码)。
2. IOC 模式:控制权反转给 “容器”

IOC 的核心是:开发者只定义 “需要什么对象”,而对象的创建、依赖注入由 IOC 容器(比如 Spring 容器)完成,即 “开发者不 new 对象,容器来 new;开发者不注入依赖,容器来注入”。

(1)核心概念拆解
角色职责
IOC 容器负责对象的创建、存储、依赖注入、生命周期管理(比如 Spring 容器)
被管理的对象(Bean)交由容器管理的对象(比如UserServiceUserDAOImpl
依赖注入(DI)IOC 的实现方式:容器将对象的依赖自动注入到目标对象中(DI 是 IOC 的具体落地手段)
(2)IOC 模式的代码示例(Spring 框架)

① 定义 Bean(告诉容器 “要管理这些对象”):

// 数据层:标注为Bean,交由容器管理
@Repository
public class UserDAOImpl implements UserDAO {
    public void queryUser() {
        System.out.println("查询用户数据");
    }
}

// 业务层:标注为Bean,依赖UserDAO
@Service
public class UserService {
    // 声明依赖:容器自动注入UserDAO对象(无需new)
    @Autowired
    private UserDAO userDAO; 
    
    public void getUser() {
        userDAO.queryUser();
    }
}

② 容器初始化并获取对象:

public class Test {
    public static void main(String[] args) {
        // 启动Spring容器,容器自动创建所有标注的Bean,并注入依赖
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        
        // 从容器中获取UserService对象(而非手动new)
        UserService service = context.getBean(UserService.class);
        service.getUser();
    }
}

核心变化

  • 开发者不再手动new UserDAOImpl()new UserService()
  • 依赖(UserDAO)由容器通过@Autowired自动注入到UserService中;
  • 若要替换UserDAO实现(比如UserDAOMySQLImpl),只需修改注解 / 配置,无需改UserService代码。
3. IOC 的核心价值
  • 解耦:对象之间不再直接依赖具体实现,而是依赖抽象(比如UserService依赖UserDAO接口,而非UserDAOImpl),符合 “依赖倒置原则”;
  • 可维护性:对象的创建、依赖集中由容器管理,修改依赖只需调整配置,无需改业务代码;
  • 可扩展性:新增实现类(比如UserDAORedisImpl)时,只需配置容器,原有业务代码无需改动;
  • 可测试性:测试时可通过容器注入模拟对象(Mock),无需修改源码。
4. IOC 与三层架构 / MVC 的关系

IOC 是跨架构 / 模式的通用思想,与三层架构、MVC 互补,是实现 “解耦” 的核心手段:

  • 在三层架构中:IOC 容器管理DAOServiceController等所有层的 Bean,自动注入层间依赖(比如Controller依赖ServiceService依赖DAO);
  • 在 MVC 中:IOC 容器管理Controller(MVC 的 C)、Service(MVC 的 Model)等 Bean,解决 MVC 组件间的依赖耦合问题;
  • 简单说:三层架构 / MVC 是 “代码分层 / 拆分的规则”,IOC 是 “让分层后的代码更低耦合的实现手段”。
5. 通俗类比
  • 传统模式:你(开发者)自己买菜、切菜、炒菜(手动创建所有对象 + 管理依赖);
  • IOC 模式:你去餐厅(IOC 容器),只需告诉服务员 “我要鱼香肉丝”(声明需要的对象 / 依赖),后厨(容器)自动准备食材、做菜、端上来(容器创建对象 + 注入依赖),你只管吃(使用对象)。

关键补充:IOC ≠ DI

很多人会混淆 IOC 和 DI(Dependency Injection,依赖注入):

  • IOC:是 “思想 / 原则”—— 控制权反转(对象创建权从开发者到容器);
  • DI:是 “实现方式”—— 容器将依赖对象注入到目标对象中(Spring 通过@Autowired、XML 配置等实现 DI);
  • 一句话总结:DI 是 IOC 的具体落地手段,IOC 是 DI 要实现的最终目标

IOC 在nestjs中的应用

NestJS 是基于 Node.js/TypeScript 的后端框架,IOC 是其核心设计理念(底层依赖 TypeDI 实现),贯穿整个框架的组件管理、依赖注入、模块拆分等核心能力。与 Spring 的 IOC 思想一致,但结合 TypeScript 的装饰器、类型系统做了更贴合前端 / Node 生态的设计

1. NestJS 中 IOC 的核心概念

概念对应 Spring 概念核心作用
IOC 容器(Container)Spring 容器管理所有组件(Controller、Service、Repository 等)的生命周期、依赖注入
Provider(提供者)Bean交由 IOC 容器管理的对象(Service、Repository、Factory 等)
装饰器(@Injectable/@Controller 等)@Service/@Controller标记 “需要被容器管理的对象”,是容器识别 Provider 的核心方式
依赖注入(DI)@Autowired容器自动将依赖的 Provider 注入到目标组件中(支持构造器、属性、方法注入)
Module(模块)配置类 / 包封装一组相关的 Provider,控制 Provider 的作用域和可见性

2. NestJS 中 IOC 的核心使用场景

(1)基础:声明 Provider 并注入依赖

NestJS 通过 @Injectable() 标记 “可被容器管理的服务”,通过构造器注入(推荐)或 @Inject() 完成依赖注入,核心是 “开发者只声明依赖,容器自动创建并注入”。

示例:Service + Controller 依赖注入
// 1. 数据访问层:UserRepository(Provider)
@Injectable() // 标记为 Provider,交由 IOC 容器管理
export class UserRepository {
  findUserById(id: number) {
    return { id, name: "NestJS IOC", age: 20 };
  }
}

// 2. 业务逻辑层:UserService(依赖 UserRepository)
@Injectable()
export class UserService {
  // 构造器注入:容器自动将 UserRepository 实例注入
  constructor(private readonly userRepository: UserRepository) {}

  getUserInfo(id: number) {
    return this.userRepository.findUserById(id);
  }
}

// 3. 表示层:UserController(依赖 UserService)
@Controller("users")
export class UserController {
  // 构造器注入:容器自动注入 UserService 实例
  constructor(private readonly userService: UserService) {}

  @Get(":id")
  getUser(@Param("id", ParseIntPipe) id: number) {
    return this.userService.getUserInfo(id);
  }
}

// 4. 模块:将 Provider 注册到 IOC 容器
@Module({
  controllers: [UserController], // 注册控制器(自动加入容器)
  providers: [UserService, UserRepository], // 注册服务(加入容器)
})
export class UserModule {}
核心逻辑:
  • @Injectable()/@Controller() 告诉 IOC 容器:“这个类需要被你管理”;
  • 控制器 / 服务的构造器参数(userService/userRepository)无需手动 new,容器会自动创建实例并注入;
  • 若替换 UserRepository 的实现(比如从数据库改为 Redis),只需修改 providers 配置,无需改动 UserService 代码(解耦核心)。
3. NestJS IOC 与三层架构 / MVC 的结合

NestJS 天然结合了 “三层架构 + MVC + IOC”,IOC 是实现解耦的核心:

分层 / 模式NestJS 组件IOC 作用
三层架构 - 数据层RepositoryIOC 管理 Repository 实例,注入到 Service 中,支持灵活替换数据源
三层架构 - 业务层ServiceIOC 管理 Service 实例,自动注入 Repository/Config 等依赖
MVC - 控制器ControllerIOC 管理 Controller 实例,自动注入 Service,处理请求并调度业务逻辑
MVC - 视图前端 / 响应体Controller 依赖 Service(Model)处理数据,返回结果(View)

@Injectable () 和 @Controller ()

@Controller() 和 @Injectable() 最终目的都是让类被 IOC 容器接管(统一管理实例、实现依赖注入),但二者的定位、附加能力、设计意图有本质区别,不能简单等同。

我们可以用「“通用员工” vs “专属岗位员工”」的类比,把这个逻辑拆透:

一、核心共识:二者的共性(你理解的部分)

  1. 都交给 IOC 容器管理:无论标记 @Injectable() 还是 @Controller(),类都会被 Nest 的 IOC 容器实例化、托管生命周期(默认单例),开发者无需手动 new
  2. 都支持依赖注入:二者标记的类都能在构造器中注入其他 @Injectable() 标记的 Provider(比如 Controller 注入 Service,Service 注入 Repository);
  3. 都需要在 Module 中声明:Controller 要放在 controllers 数组,Injectable 要放在 providers 数组(容器才会识别)。

二、核心差异:二者的 “专属使命”(补充理解的部分)

维度@Injectable()@Controller()
核心定位通用服务提供者(IOC 容器的 “基础组件”)HTTP 请求处理专属组件(MVC 的 C 层)
附加核心能力仅标记 “可被注入 / 管理”,无其他附加能力1. 注册 HTTP 路由前缀(如 /users);2. 绑定 HTTP 方法(@Get/@Post 等);3. 对接请求 / 响应上下文;
设计意图封装业务逻辑、数据操作、通用工具(“干活的组件”)封装请求接收、路由分发、响应返回(“对接前端的组件”)
依赖关系通常作为 “被注入方”(Service 被 Controller 注入)通常作为 “注入方”(注入 Service,自己不被其他组件注入)
模块注册后的效果仅在容器中生成实例,无外部可见效果除了容器实例,还会被注册到 Nest 路由系统,对外暴露 HTTP 接口

三、通俗拆解:为什么需要两个装饰器?

假设 Nest 是一家公司,IOC 容器是 “人事部门”:

  • @Injectable():相当于给员工贴 “通用岗位” 标签(厨师、财务、库管)—— 人事部门(IOC)管理你的入职 / 离职(生命周期),其他岗位可以调用你(依赖注入),但你不直接对接客户;
  • @Controller():相当于给员工贴 “前台 / 服务员” 标签 —— 人事部门同样管理你,但你还有专属职责:对接客户(接收 HTTP 请求)、记录需求(解析参数)、喊后厨干活(调用 Service)、给客户反馈(返回响应),同时公司会把你的工位(路由)公示给客户(前端)。

四、关键反例:验证 “附加能力” 的区别

  1. 给 Service 只加 @Injectable(),不加 @Controller():✅ 容器会创建实例,Controller 能注入它;❌ 不会生成任何 HTTP 接口(因为没有路由能力);
  2. 给 Controller 只加 @Controller(),不加 @Injectable():✅ 容器仍会管理它(@Controller() 内置了 “可被容器管理” 的能力),能注入 Service;✅ 会生成 HTTP 接口(核心能力不受影响);
  3. 给一个类既加 @Injectable() 又加 @Controller():❌ 语法允许,但完全没必要 ——@Controller() 已满足 “被容器管理 + 依赖注入”,额外加 @Injectable() 无任何增益。

五、最终结论

  • 「交给 IOC 容器管理 + 依赖注入」是二者的底层共性(都是 IOC 体系的一部分);
  • 「是否具备 HTTP 路由 / 请求处理能力」是二者的核心差异@Controller() 是 “带路由能力的容器组件”,@Injectable() 是 “纯通用容器组件”);
  • 设计上的分工:@Injectable() 负责 “内部业务逻辑”,@Controller() 负责 “外部请求交互”,二者配合实现 “交互层与业务层解耦”(符合三层架构思想)。