回顾与引子:从 API 到完整模块
在上一期我们创建了第一个 RESTful API,但这只是冰山一角。当你面对真实的用户系统需求时:
// 真实项目需要的用户模块功能
createUser() // 创建用户
getUserById() // 获取用户信息
updateUserProfile() // 更新资料
deleteUser() // 删除账号
listUsers() // 分页查询
login() // 登录验证
resetPassword() // 密码重置
...如何避免代码变成一锅炖? 这就是本期要解决的架构设计问题。
一、NestJS 架构核心:模块化设计哲学
1. 什么是模块化?
将相关联的功能(控制器、服务、数据访问)封装为独立单元,通过清晰接口对外交互
graph TD
A[AppModule] --> B[UserModule]
A --> C[ProductModule]
A --> D[OrderModule]
B --> E[UserController]
B --> F[UserService]
B --> G[UserRepository]2. @Module 装饰器解剖
@Module({
imports: [TypeOrmModule.forFeature([User])], // 导入依赖模块
controllers: [UserController], // 本模块的控制器
providers: [UserService, UserRepository], // 服务与数据层
exports: [UserService] // 暴露给其他模块
})
export class UserModule {}3. 黄金分层法则:Controller/Service/Repository
| 层级 | 职责 | 代码特征 |
|---|---|---|
| Controller | 处理HTTP请求 | @Get(),@Post()装饰器 |
| Service | 业务逻辑封装 | @Injectable()装饰器 |
| Repository | 数据库操作 | ORM查询方法封装 |
二、实战:用户系统 CRUD 全实现(使用MySQL)
步骤0 - 环境准备
安装TypeORM及MySQL驱动
npm install @nestjs/typeorm typeorm mysql2配置数据库连接(.env)
# .env
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=nest_tutorial配置TypeORM动态模块(app.module.ts)
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // 开发环境自动同步结构
}),
UserModule, // 引入用户模块
],
})
export class AppModule {}步骤1 - 创建用户实体(Entity)
// user/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
@IsEmail()
email: string;
@Column()
@MinLength(6)
password: string;
@Column({ nullable: true })
nickname: string;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
}步骤2 - 实现Repository层
// user/user.repository.ts
import { DataSource, Repository } from 'typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
@Injectable()
export class UserRepository extends Repository<User> {
constructor(@InjectDataSource() dataSource: DataSource) {
super(User, dataSource.createEntityManager());
}
// 自定义查询方法
findByEmail(email: string): Promise<User | undefined> {
return this.findOne({ where: { email } });
}
}步骤3 - Service层业务逻辑
// user/user.service.ts
import * as bcrypt from 'bcrypt';
@Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepository) {}
async create(createDto: CreateUserDto): Promise<User> {
// 密码加密
const hashedPassword = await bcrypt.hash(createDto.password, 10);
return this.userRepo.save({ ...createDto, password: hashedPassword });
}
async findById(id: number): Promise<User | null> {
return this.userRepo.findOneBy({ id });
}
async updateProfile(id: number, updateDto: UpdateUserDto) {
await this.userRepo.update(id, updateDto);
return this.findById(id);
}
async deleteUser(id: number) {
return this.userRepo.delete(id);
}
}步骤4 - Controller层API实现
// user/user.controller.ts
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
async register(@Body() createDto: CreateUserDto) {
return this.userService.create(createDto);
}
@Get(':id')
async getUser(@Param('id', ParseIntPipe) id: number) {
return this.userService.findById(id);
}
@Patch(':id')
async updateUser(
@Param('id', ParseIntPipe) id: number,
@Body() updateDto: UpdateUserDto
) {
return this.userService.updateProfile(id, updateDto);
}
@Delete(':id')
@HttpCode(204)
async deleteUser(@Param('id', ParseIntPipe) id: number) {
await this.userService.deleteUser(id);
}
}三、关键能力:使用DTO实现数据验证与转换
1. 创建用户DTO
// dto/create-user.dto.ts
export class CreateUserDto {
@IsEmail()
email: string;
@MinLength(6)
password: string;
@IsOptional()
@MaxLength(20)
nickname?: string;
}2. 更新用户DTO
// dto/update-user.dto.ts
export class UpdateUserDto {
@IsOptional()
@MaxLength(20)
nickname?: string;
@IsOptional()
@IsUrl()
avatar?: string;
}四、避坑指南:数据库操作常见错误
❌ 错误1:"Repository not found" 陷阱
解决方案:确保在模块中导入实体
// user.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])], // 关键!!!
controllers: [UserController],
providers: [UserService, UserRepository]
})
export class UserModule {}❌ 错误2:密码明文存储
解决方案:务必使用bcrypt加密
// 正确密码处理
async create(userDto: CreateUserDto) {
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(userDto.password, salt);
return this.userRepo.save({ ...userDto, password: hashedPassword });
}❌ 错误3:循环依赖问题
当UserService和AuthService相互依赖时:
// 解决方案:使用forwardRef
@Module({
imports: [forwardRef(() => AuthModule)],
})
export class UserModule {}五、进阶技巧:事务处理实战
保证用户创建与初始化操作原子性:
async createWithProfile(createDto: CreateUserDto) {
return this.dataSource.transaction(async manager => {
// 1. 创建用户
const user = await manager.save(User, {
email: createDto.email,
password: hashedPassword
});
// 2. 创建关联资料
const profile = await manager.save(UserProfile, {
userId: user.id,
nickname: createDto.nickname || '新用户'
});
return {...user, profile};
});
}六、测试你的CRUD系统
使用Postman体验完整流程:
创建用户
POST /users{"email":"user@example.com","password":"strongPass123"}- 查询用户
GET /users/1 更新资料
PATCH /users/1{"nickname": "Nest大师"}- 删除用户
DELETE /users/1
七、下期预告:请求处理高级技巧
《管道与拦截器:打造健壮的API网关》 将深入:
➠ 自定义管道实现数据清洗与验证
➠ 拦截器统一封装API响应格式
➠ 异常过滤器标准化错误处理
➠ 实战:敏感词过滤系统
问题挑战:
✍️ 如何实现用户列表分页查询?在评论区分享你的实现方案!
从零到一完成用户模块的架构设计与实现,你现在已经掌握:
✅ 模块化架构设计原理
✅ TypeORM集成MySQL数据库
✅ 三层架构实战模式
✅ 事务管理等进阶技巧
关键成长点:从孤立的API到完整业务模块的系统性思维转变!
相关推荐
- NestJS 从入门到精通 11:综合实战 - 构建知乎风格的全栈社区平台(下) 2025-06-24
- NestJS 从入门到精通 10:综合实战 - 构建知乎风格的全栈社区平台(上) 2025-06-24
- NestJS 从入门到精通 09:性能调优大师 - 百万并发下的极致优化策略 2025-06-24
- NestJS 从入门到精通 08:安全架构实战 - 构建金融级应用的8重防护门 2025-06-24
- NestJS 从入门到精通 07:配置管理与部署实战 - 多环境与容器化方案 2025-06-24
评论0
暂时没有评论