1-1 nestjs用户认证:创建认证控制器及对接数据
本节将搭建用户认证(Authentication)模块的核心骨架,包括:创建独立的 Auth 模块、定义登录/注册接口、实现跨模块依赖注入,最终打通从 Controller 到数据库的完整调用链路。
架构设计:为什么需要独立的 Auth 模块?
在实际项目中,认证逻辑(登录、注册、JWT 验证等)与用户管理逻辑(查询、更新、删除用户信息)虽然都涉及用户数据,但职责不同。将两者拆分为独立模块有明确的工程收益:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 认证逻辑写在 UserController 中 | 结构简单,无需跨模块调用 | 职责耦合,后期维护困难 |
| 独立 Auth 模块(推荐) | 职责清晰,便于维护和扩展 | 需要处理跨模块依赖注入 |
模块职责划分:
- UserModule — 处理用户 CRUD 操作(查询、新增、更新)
- AuthModule — 处理认证相关逻辑(登录验证、注册、Token 签发)
两个模块之间通过 NestJS 的模块导入/导出机制实现跨模块通信。
关于 UserService 层:如果项目规模较小,可以将业务逻辑直接写在
UserRepository中,省去中间的 Service 层。当业务复杂度增加时,再拆分出独立的UserService。
使用 CLI 生成 Auth 模块骨架
NestJS CLI 可以快速生成模块、控制器和服务文件。使用 -d(dry-run)参数可预览将要创建和更新的文件:
# 生成 auth 模块
nest g module auth
# 生成 auth 控制器(跳过测试文件)
nest g controller auth --no-spec
# 生成 auth 服务
nest g service auth --no-spec
bash
执行后,NestJS 会自动更新 app.module.ts 中的 imports 数组,将 AuthModule 注册到根模块中。
定义登录与注册接口
在 auth.controller.ts 中创建两个 POST 端点:
// auth/auth.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signin')
signIn(@Body() dto: { username: string; password: string }) {
return this.authService.signIn(dto.username, dto.password);
}
@Post('signup')
signUp(@Body() dto: { username: string; password: string }) {
return this.authService.signUp(dto.username, dto.password);
}
}
typescript
关键点解析:
@Controller('auth')— 路由前缀为/auth,结合全局前缀/api/v1,最终路径为/api/v1/auth/signin和/api/v1/auth/signup@Body()装饰器 — 从 POST 请求体中提取参数constructor(private readonly authService: AuthService)— 构造函数注入,NestJS IoC 容器自动解析
对应的 Service 层:
// auth/auth.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AuthService {
async signIn(username: string, password: string) {
// TODO: 实现登录逻辑
return { username, password };
}
async signUp(username: string, password: string) {
// TODO: 实现注册逻辑
return { username, password };
}
}
typescript
Auth 模块需要将 AuthService 注册为 provider:
// auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
typescript
跨模块依赖注入:Auth 模块访问 UserRepository
认证逻辑需要查询数据库验证用户身份,因此 Auth 模块需要访问 UserRepository。这涉及 NestJS 的跨模块 Provider 共享机制。
核心原则:NestJS 以模块为单元管理 Provider 的生命周期。 即使两个模块注册了相同的 class,它们也会各自创建独立的实例。正确的做法是通过 exports + imports 共享单例。
第一步:UserModule 导出 UserRepository
// user/user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserRepository } from './user.repository';
@Module({
controllers: [UserController],
providers: [UserRepository],
exports: [UserRepository], // 导出,允许其他模块使用
})
export class UserModule {}
typescript
第二步:AuthModule 导入 UserModule
// auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UserModule } from '../user/user.module';
@Module({
imports: [UserModule], // 导入 UserModule,获取其 exports
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
typescript
第三步:在 AuthService 中注入 UserRepository
// auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UserRepository } from '../user/user.repository';
@Injectable()
export class AuthService {
constructor(private readonly userRepository: UserRepository) {}
async signIn(username: string, password: string) {
const user = await this.userRepository.findByUsername(username);
console.log('signIn dto:', { username, password });
return user;
}
async signUp(username: string, password: string) {
return { username, password };
}
}
typescript
数据库查询:扩展 Repository 方法
为了让 UserRepository 支持按用户名查询,需要扩展接口和实现:
// user/user.repository.ts(Prisma 版本示例)
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UserRepository {
constructor(private readonly prisma: PrismaService) {}
async findByUsername(username: string) {
return this.prisma.user.findUnique({
where: { username },
});
}
async findAll() {
return this.prisma.user.findMany();
}
}
typescript
同步数据库 schema 到 PostgreSQL:
npx prisma db push
bash
此命令将
prisma/schema.prisma中定义的数据模型同步到连接的 PostgreSQL 数据库,自动创建或更新对应的表结构。
接口测试
使用 Bruno / Postman 测试登录接口:
POST http://localhost:3030/api/v1/auth/signin
Content-Type: application/json
{
"username": "testuser",
"password": "123456"
}
text
环境变量配置(Bruno 示例):
在 Bruno 的环境配置中创建变量 baseUrl,值为 http://localhost:3030/api/v1,后续请求路径可直接写 {{baseUrl}}/auth/signin,避免重复输入。
完整数据流总结
客户端请求
│
▼
AuthController (接收 HTTP 请求,提取 Body 参数)
│
▼
AuthService (业务逻辑层,依赖注入 UserRepository)
│
▼
UserRepository (数据访问层,操作 Prisma/数据库)
│
▼
PostgreSQL 数据库
text
跨模块通信机制:
UserModule AuthModule
┌─────────────────┐ ┌─────────────────┐
│ providers: │ │ imports: │
│ UserRepository │──exports──────▶│ UserModule │
│ exports: │ │ providers: │
│ UserRepository │ │ AuthService │
└─────────────────┘ │ controllers: │
│ AuthController │
└─────────────────┘
text
本节要点
- 模块拆分原则:认证逻辑与用户管理逻辑分离,各自独立成模块
- CLI 快速生成:
nest g module/controller/service自动创建文件并更新模块注册 - 跨模块共享 Provider:通过
exports导出 +imports导入实现单例共享,避免重复实例化 - 依赖注入:使用
constructor(private readonly xxx: XxxService)声明依赖,由 IoC 容器自动解析 - 数据库同步:
npx prisma db push将 schema 变更同步到数据库
↑