Skip to content

Module、Controller 与依赖注入

Nest 应用由根模块开始构建依赖图。模块默认封装其 Provider;其他模块只能注入本模块的 Provider,或已由导入模块显式导出的 Provider。

按业务域组织

一个业务域通常包含模块、控制器、服务和 DTO。控制器负责协议层,服务负责业务逻辑;数据库访问可置于服务或独立的 Repository/Provider 中。

bash
src
├── app.module.ts
└── users
    ├── dto
   └── create-user.dto.ts
    ├── users.controller.ts
    ├── users.module.ts
    └── users.service.ts

Module

imports 引入其他模块导出的能力,providers 注册可注入对象,controllers 注册 HTTP 控制器,exports 定义模块对外暴露的 Provider。

ts
import { Module } from '@nestjs/common'

import { UsersController } from './users.controller'
import { UsersService } from './users.service'

@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

全局模块会隐藏依赖来源,应只用于配置、日志等基础设施;业务模块优先显式导入。

根模块只会发现已导入的功能模块。新增 UsersModule 后,必须将它加入 AppModuleimports,否则其中的 Controller 和 Provider 不会生效。

ts
import { Module } from '@nestjs/common'

import { UsersModule } from './users/users.module'

@Module({
  imports: [UsersModule],
})
export class AppModule {}

Controller

控制器使用装饰器匹配路由。@Get()@Post()@Put()@Patch()@Delete() 分别对应常见 HTTP 方法;@Query()@Param()@Body() 用于提取请求输入。

保持控制器轻薄:接收输入、调用服务、返回结果;不在其中编写业务规则或数据库查询。静态路由必须写在参数路由前面,否则 /users/all 会被 :id 当作 id = 'all' 匹配。

ts
import { Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common'

import { CreateUserDto } from './dto/create-user.dto'
import { UsersService } from './users.service'

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get('all')
  findAll(@Query('page', ParseIntPipe) page: number) {
    return this.usersService.findAll(page)
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.findOne(id)
  }

  @Post()
  create(@Body() input: CreateUserDto) {
    return this.usersService.create(input)
  }
}

DTO 必须是 class,不能只写 TypeScript interface:接口在运行时会被擦除,ValidationPipe 无法读取 class-validator 的装饰器元数据。更新 DTO 可由创建 DTO 派生:

ts
import { PartialType } from '@nestjs/mapped-types'

import { CreateUserDto } from './create-user.dto'

export class UpdateUserDto extends PartialType(CreateUserDto) {}

Provider 与依赖注入

Provider 是由 Nest 容器创建和管理的依赖。类 Provider 以类本身为 token;构造函数参数按 token 注入。不要在 Controller 或 Service 中直接 new UsersService()new PrismaClient();这样会绕开容器,无法共享配置、替换依赖或测试 Mock。

ts
import { Injectable, NotFoundException } from '@nestjs/common'

import { CreateUserDto } from './dto/create-user.dto'

@Injectable()
export class UsersService {
  private readonly users = new Map<number, CreateUserDto>()

  findOne(id: number) {
    const user = this.users.get(id)

    if (!user) throw new NotFoundException('用户不存在')

    return { id, ...user }
  }

  create(input: CreateUserDto) {
    const id = this.users.size + 1
    this.users.set(id, input)
    return { id, ...input }
  }
}

@Injectable() 让 Nest 能读取构造函数依赖并创建实例。依赖链由容器解析,例如 UsersController → UsersService → AppLogger;Logger 等依赖也要在 Module 的 providers 中注册。

ts
import { Injectable, Logger } from '@nestjs/common'

@Injectable()
export class UsersService {
  constructor(private readonly logger: Logger) {}

  findAll() {
    this.logger.log('读取用户列表')
    return []
  }
}

涉及接口、常量或多个实现时,使用显式 token 与 @Inject(token);token 应集中定义,避免字符串散落在业务代码中。

基于 MIT 许可发布