NestJS 入门(1):先搞懂 Module、Controller、Service

NestJS 初学最容易懵的不是语法,而是:一个请求进来之后,代码到底怎么分层?

你可以先记住一句话:

Controller 接请求,Service 做业务,Module 把它们组装起来。

下面用一套真实后端代码,把这三层拆开看。


1. 入口:应用从哪里启动?

Nest 应用启动时,会创建一个 App,并把根模块挂上去:

typescript 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors();
  await app.listen(3000);
}
bootstrap();

真正业务代码不写在 main.ts 里。main.ts 只负责:

  1. 创建应用
  2. 挂全局配置(CORS、异常处理等)
  3. 监听端口

业务能力都在各个 Module 里。


2. Module:功能的「收纳盒」

根模块通常长这样:

typescript 复制代码
import { Module } from '@nestjs/common';
import { AuthModule } from './modules/auth/auth.module';
import { DocumentsModule } from './modules/documents/documents.module';
import { HealthController } from './health.controller';

@Module({
  imports: [
    AuthModule,
    DocumentsModule,
    // ... 其他业务模块
  ],
  controllers: [HealthController],
})
export class AppModule {}

读这段时抓住三点:

  • imports:引入其他模块(认证、文档......)
  • controllers:本模块对外暴露的路由
  • 每个业务能力尽量一个 Module,避免全塞进 AppModule

健康检查这种极简接口,也可以直接挂在根模块:

typescript 复制代码
import { Controller, Get } from '@nestjs/common';

@Controller()
export class HealthController {
  @Get('health')
  health() {
    return { status: 'ok', timestamp: new Date().toISOString() };
  }
}

访问 GET /health,就会走到这里。这是理解 Controller 的最小例子。


3. Controller:只负责「接请求」

再看一个更完整的认证 Controller:

typescript 复制代码
import { Controller, Post, Get, Body, Headers, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';

@Controller('api/auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  login(@Body() body: { email: string; password: string }) {
    return this.authService.login(body.email, body.password);
  }

  @Post('refresh')
  refresh(@Body() body: { refreshToken: string }) {
    return this.authService.refreshToken(body.refreshToken);
  }

  @UseGuards(JwtAuthGuard)
  @Get('me')
  me(@Headers('authorization') auth: string) {
    const token = auth?.replace('Bearer ', '');
    return this.authService.validateToken(token);
  }
}

这里有几个关键点:

写法 含义
@Controller('api/auth') 路由前缀
@Post('login') 完整路径是 POST /api/auth/login
@Body() 取请求体
constructor(private readonly authService: AuthService) 依赖注入:Nest 帮你创建并传入 Service
@UseGuards(JwtAuthGuard) 进这个接口前先做鉴权

注意:Controller 几乎不写业务细节

密码对不对、怎么发 token,全部丢给 AuthService

CRUD 场景也一样,比如文档接口:

typescript 复制代码
@Controller()
export class DocumentsController {
  constructor(private readonly documentsService: DocumentsService) {}

  @Get('api/projects/:projectId/documents')
  findAll(@Param('projectId') projectId: string) {
    return this.documentsService.findAll(projectId);
  }

  @Post('api/projects/:projectId/documents')
  create(
    @Param('projectId') projectId: string,
    @Body() data: { title: string; content: string }
  ) {
    return this.documentsService.create(projectId, data);
  }
}

模式始终是:取参数 → 调 Service → 返回结果


4. Service:业务真正发生的地方

Service 用 @Injectable() 标记,表示可被注入:

typescript 复制代码
import { Injectable, UnauthorizedException } from '@nestjs/common';

@Injectable()
export class AuthService {
  async login(email: string, password: string) {
    const user = this.findUserByEmail(email);

    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid credentials');
    }

    const { accessToken, refreshToken } = await this.generateTokens(user);
    await this.createSession(user.id, refreshToken);

    const { password: _pwd, ...userWithoutPassword } = user;
    return {
      accessToken,
      refreshToken,
      user: userWithoutPassword,
    };
  }
}

登录流程都在 Service 里:

  1. 查用户
  2. 校验密码
  3. 签发 token / 建会话
  4. 返回数据(去掉密码字段)

Controller 只需要一行:

typescript 复制代码
return this.authService.login(body.email, body.password);

这也是 Nest 推荐的分层:

  • Controller:HTTP 细节(路径、方法、参数)
  • Service:业务规则(校验、持久化、发 token)

5. 再回到 Module:把零件装起来

有了 Controller 和 Service,还要告诉 Nest:「它们属于同一个功能包」:

typescript 复制代码
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';

@Module({
  imports: [
    // JwtModule、PassportModule、PrismaModule 等依赖
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtAuthGuard],
  exports: [AuthService, JwtAuthGuard],
})
export class AuthModule {}

对应关系可以这样记:

  • controllers:谁对外提供 HTTP 接口
  • providers:谁提供可注入能力(Service、Guard......)
  • exports:哪些东西允许被其他模块继续用
  • imports:本模块依赖哪些外部能力

文档模块更「典型」一点:

typescript 复制代码
@Module({
  imports: [PrismaModule],
  controllers: [DocumentsController],
  providers: [DocumentsService],
  exports: [DocumentsService],
})
export class DocumentsModule {}

读到这里,一张请求链路就清楚了:

text 复制代码
HTTP 请求
  → Controller(解析路由/参数)
    → Service(执行业务)
      → 数据库 / 缓存 / 外部服务
    ← 返回业务结果
  ← 返回 HTTP 响应

Module 负责把这条链路上的类注册好,Nest 的依赖注入再负责把它们「接上电」。


6. 一张图串起来

text 复制代码
AppModule
 ├─ HealthController          → GET /health
 ├─ AuthModule
 │   ├─ AuthController        → POST /api/auth/login
 │   └─ AuthService           → 校验账号、签发 token
 └─ DocumentsModule
     ├─ DocumentsController   → GET/POST /api/projects/:id/documents
     └─ DocumentsService      → 查库、创建、更新

学 Nest 时,建议每看到一个新功能,都先问三句:

  1. 哪个 Controller 接这个请求?
  2. 哪个 Service 处理业务?
  3. 它们被哪个 Module 组装?

能答出来,入门就过半了。


7. 小结

  • Nest 不是「把 Express 路由堆在一个文件里」,而是按模块组织。
  • Controller 薄、Service 厚:这是最值得先养成的习惯。
  • Module 是装配车间:声明依赖、暴露接口、控制边界。
  • 先读懂一个完整小模块(比如 auth),再扩到 CRUD,比上来堆装饰器有效得多。

系列导航

相关推荐
Elastic 中国社区官方博客2 分钟前
从建议到修复的 4 个阶段:使用 Elastic Workflows 实现人在回路中的自动化
运维·数据库·人工智能·后端·elasticsearch·ai·自动化
2601_962071572 分钟前
Java进阶(vue基础)
前端·javascript·vue.js
研☆香3 分钟前
数组方法 splice讲解 拓展
开发语言·前端·javascript
码视野4 分钟前
基于 Spring Boot + Vue3 的【城市地下燃气管网微泄漏感知与相邻地下空间燃爆预警中台】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端
淡海水6 分钟前
05-03-栈队列-PriorityQueue-TElement-TPriority-NET6优先队列语义与四叉堆实现
服务器·前端·c#·priorityqueue·clr·telement
峥嵘life21 分钟前
Android16 系统 APEX 模块说明
android·大数据·开发语言
2601_9620715725 分钟前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
————A27 分钟前
Agent 文件处理中间件
开发语言·javascript·ecmascript
梦想的旅途227 分钟前
如何高效调用企业微信通讯录API管理组织架构
java·开发语言·企业微信
whcyhhh44 分钟前
头歌实践教学平台:大数据存储2023(十三3)
大数据·开发语言·python