NestJS 入门:从零到 CRUD 接口

20 阅读5分钟

NestJS 入门:从零到 CRUD 接口

本文基于一个真实的 NestJS + MongoDB 项目,带你理解 NestJS 的核心概念和目录结构,适合有 Node/TypeScript 基础、想上手 Nest 的读者。


一、话不多说,NestJS 是什么得搞清楚?

NestJS 是一个用于构建服务端应用的 Node 框架,底层默认基于 Express(也可切换为 Fastify)。支持用 TypeScript 编写,采用模块化依赖注入,结构类似 Angular,适合写中大型后端项目。

让我们简要概括一下特点:

  • 分层清晰:Controller → Service → Model,职责分离
  • 装饰器驱动:路由、参数、校验等用 @Get()@Body() 等声明
  • 模块化:按功能拆成 Module,便于扩展与复用
  • 开箱即用:与 TypeORM、Mongoose、Config、Validation 等集成简单

二、项目结构一览

src/
├── main.ts                    # 应用入口,创建实例、全局配置、监听端口
├── app.module.ts              # 根模块,汇总各功能模块与全局依赖
├── app.controller.ts          # 根控制器(如健康检查)
├── app.service.ts
├── example/                  # 示例功能模块(按业务划分)
│   ├── example.module.ts     # 示例模块:注册模型、控制器、服务
│   ├── example.controller.ts # 示例相关 HTTP 接口
│   ├── example.service.ts    # 示例业务逻辑与数据库操作
│   ├── dto/
│   │   └── example.dto.ts    # 入参/出参的数据结构
│   └── schemas/
│       └── example.schemas.ts # Mongoose 文档模型
├── tansform/
│   └── tansform.interceptor.ts  # 全局响应包装(统一返回格式)
└── http-exeption/
    └── http-exception.filter.ts # 全局异常捕获与错误返回格式

先建立整体印象:入口 → 根模块 → 各功能模块(Module + Controller + Service + Schema/DTO),再配合拦截器、过滤器做统一格式和错误处理。


三、首先看一下入口文件:main.ts

大都为固定写法,应用从这里启动:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TansformInterceptor } from './tansform/tansform.interceptor';
import { HttpExceptionFilter } from './http-exeption/http-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');                           // 所有路由加 /api 前缀
  app.useGlobalInterceptors(new TansformInterceptor()); // 统一成功响应格式
  app.useGlobalFilters(new HttpExceptionFilter());      // 统一错误响应格式
  await app.listen(process.env.PORT ?? 3005);
}
bootstrap();

简单解释一下:

  • NestFactory.create(AppModule):根据根模块创建应用。
  • setGlobalPrefix('api'):例如 /example 会变成 /api/example
  • 全局拦截器:在控制器返回后,把结果包成 { errno: 0, data }
  • 全局异常过滤器:捕获 HttpException,返回 { error: -1, message, ... }
  • 端口从环境变量 PORT 读,默认 3005。

四、再看根模块:app.module.ts

根模块负责「拼装」整个应用:配置、数据库、各业务模块。

@Module({
  imports: [
    ConfigModule.forRoot(),   // 必须最先加载,才能读到 process.env
    MongooseModule.forRoot(
      `mongodb://${process.env.MONGO_HOST}:${process.env.MONGO_PORT}/${process.env.MONGO_DATABASE}`,
    ),
    ExampleModule,
    UserModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

注意几个点:

  • ConfigModule.forRoot() 要放在最前面,否则后面的 process.env.* 可能是 undefined(例如 MongoDB 连接会变成 undefined:undefined)。
  • MongooseModule.forRoot() 连接 MongoDB,一条连接多模块复用。
  • ExampleModule、UserModule 等是功能模块,各自内部再注册 Controller、Service 和 forFeature 的模型。

五、功能模块:以 Example 为例

一个完整功能通常包含:Module + Controller + Service + Schema(+ DTO)

5.1 模块:example.module.ts

模块把「该功能需要的模型、控制器、服务」捆绑在一起:

@Module({
  imports: [
    MongooseModule.forFeature([
      { name: Example.name, schema: ExampleSchema },
    ]),
  ],
  controllers: [ExampleController],
  providers: [ExampleService],
})
export class ExampleModule {}
  • MongooseModule.forFeature:在当前模块内注册 Mongoose 模型,这样在 Service 里才能 @InjectModel(Example.name)
  • controllers / providers:声明本模块的控制器和可注入服务。

5.2 控制器:example.controller.ts

控制器只做「接收请求、调 Service、返回结果」,不写具体业务和数据库逻辑(类似于java中的controller去调用Facade,其实就是一个分层,使逻辑更清晰):

@Controller('example')
export class ExampleController {
  constructor(private readonly exampleService: ExampleService) {}

  @Post()
  create() {
    return this.exampleService.create();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.exampleService.findOne(id);
  }

  @Patch(':id')
  UpdateOne(@Param('id') id: string, @Body() exampleDto: ExampleDto) {
    return this.exampleService.update(id, exampleDto);
  }

  @Delete(':id')
  deleteOne(@Param('id') id: string) {
    return this.exampleService.delete(id);
  }
}

注意一下装饰器语法,常用的有:

  • @Controller('example'):路径前缀,得到 /api/example/...
  • @Get() / @Post() / @Patch() / @Delete():HTTP 方法。
  • @Query():查询参数;@Param():路径参数;@Body():请求体。
  • Service 通过构造函数注入,由 Nest 自动创建并注入实例。

5.3 服务:example.service.ts

业务逻辑和数据库访问都放在 Service 里,通过注入的 Model 调用 Mongoose API:

@Injectable()
export class ExampleService {
  constructor(
    @InjectModel(Example.name)
    private readonly exampleModel,
  ) {}

  async create() {
    const example = new this.exampleModel({ title: 'test' + Date.now(), name: 'nest.js' });
    return await example.save();
  }

  async findOne(id: string) {
    return await this.exampleModel.findById(id);
  }


  async update(id: string, updateData) {
    return await this.exampleModel.updateOne({ _id: id }, updateData);
  }

  async delete(id: string) {
    return await this.exampleModel.findByIdAndDelete(id);
  }
}
  • @InjectModel(Example.name):注入 Mongoose Model,findByIdupdateOnefindByIdAndDelete 等都是 Model 自带方法。

5.4 文档模型:schemas/example.schemas.ts

用 Nest 的 @Schema 和 Mongoose 定义集合结构:

@Schema({ timestamps: true })
export class Example {
  @Prop({ required: true })
  title: string;

  @Prop()
  name: string;
}

export const ExampleSchema = SchemaFactory.createForClass(Example);
  • timestamps: true 会自动加 createdAtupdatedAt
  • ExampleSchemaexample.module.ts 里通过 forFeature 注册,对应 MongoDB 里的一个集合。

5.5 DTO:dto/example.dto.ts

DTO 描述「请求体或某段数据的形状」,便于类型约束和后续加校验(如 class-validator):

export class ExampleDto {
  readonly title: string;
  readonly name: string;
}

在 Controller 里用 @Body() exampleDto: ExampleDto 即可获得类型提示和统一结构。


六、全局拦截器与异常过滤器

6.1 统一成功响应:TansformInterceptor

所有正常返回都会先经过拦截器,被包装成:

{ "errno": 0, "data": { ... } }

实现方式:在 next.handle() 的 Observable 上 map 一层即可,这样前端可以统一根据 errno === 0data 处理成功结果。

6.2 统一错误响应:HttpExceptionFilter

当控制器或 Service 抛出 HttpException(或 Nest 内置的 BadRequestException、NotFoundException 等)时,会被该过滤器捕获,返回例如:

{
  "error": -1,
  "message": "错误信息",
  "timestamp": "2025-01-30T...",
  "path": "/api/example/xxx"
}

同时可以保留正确的 HTTP 状态码(如 400、404)。这样成功走拦截器、失败走过滤器,前后端约定清晰。


七、环境变量与 MongoDB

根目录 .env 示例:

MONGO_HOST=127.0.0.1
MONGO_PORT=27017
MONGO_DATABASE=nestdb
PORT=3005

务必保证 ConfigModule.forRoot() 在 AppModule 的 imports 里排在最前,否则 process.env.MONGO_HOST 等可能未定义,导致连接失败。


八、小结与常用命令

概念作用
Module把 Controller、Service、Mongoose forFeature 等组织在一起
Controller定义路由,使用 @Query / @Param / @Body,调用 Service
Service业务逻辑 + 注入 Model,做增删改查
SchemaMongoose 文档结构,对应一个集合
DTO请求/响应数据结构,便于类型与校验
Interceptor在响应返回前统一包装(如 errno + data)
ExceptionFilter捕获异常,统一错误返回格式

常用命令(与常规前端应用基本一致):

npm run start:dev   # 开发模式,热重载
npm run build       # 编译
npm run start:prod  # 生产运行(需先 build)

按「入口 → 根模块 → 功能模块(Module/Controller/Service/Schema/DTO)→ 拦截器与过滤器」这条线把项目走一遍,再动手增删改几条示例接口,就能快速入门 NestJS啦。
后续也可以在此基础上加入校验(ValidationPipe)、鉴权(Guard)等,逐步完善项目。
如有错误,敬请指正~