
NestJS TypeORM 高效CRUD开发实战从重复劳动到模块化设计每次启动新项目时你是否也厌倦了反复编写那些几乎相同的CRUD代码从控制器到服务层从分页查询到模糊搜索这些基础功能占据了大量开发时间却难以复用。本文将带你突破这一困境通过NestJS的模块化架构和TypeORM的高级特性构建一套可复用的CRUD模板。1. 为什么我们需要CRUD模板化在常规的后台管理系统开发中80%的代码都在处理基础的数据操作。以用户管理模块为例通常需要实现以下功能分页列表查询支持排序和过滤关键字模糊搜索数据新增和编辑表单单条数据删除批量操作接口传统做法是为每个实体用户、商品、订单等重复编写这些代码不仅效率低下还容易引入不一致的实现。而通过NestJS的依赖注入和TypeORM的Repository模式我们可以将这些通用逻辑抽象为可复用的基础类。2. 基础架构设计2.1 核心模块划分我们采用三层架构设计每层都有明确的职责边界src/ ├── core/ │ ├── base/ │ │ ├── base.controller.ts │ │ ├── base.service.ts │ │ └── base.entity.ts │ └── pagination/ │ ├── pagination.dto.ts │ └── pagination.decorator.ts ├── modules/ │ └── user/ │ ├── user.controller.ts │ ├── user.service.ts │ ├── user.entity.ts │ └── dto/ │ ├── create-user.dto.ts │ └── update-user.dto.ts └── shared/ └── utils/ └── query-builder.util.ts2.2 基础服务类实现创建可继承的BaseService封装通用CRUD操作// core/base/base.service.ts import { Repository, FindManyOptions, FindConditions } from typeorm; import { Injectable } from nestjs/common; import { PaginationDto } from ../pagination/pagination.dto; Injectable() export abstract class BaseServiceT { constructor(protected readonly repository: RepositoryT) {} async findAll( pagination: PaginationDto, where?: FindConditionsT, ) { const { page, pageSize, keyword, sortField, sortOrder } pagination; const options: FindManyOptionsT { where, skip: (page - 1) * pageSize, take: pageSize, }; if (sortField) { options.order { [sortField]: sortOrder || ASC }; } const [data, total] await this.repository.findAndCount(options); return { data, total }; } // 其他通用方法... }3. 分页与搜索的高级实现3.1 统一分页参数处理定义标准化的分页DTO// core/pagination/pagination.dto.ts import { IsOptional, IsNumber, Min, IsString } from class-validator; export class PaginationDto { IsOptional() IsNumber() Min(1) page 1; IsOptional() IsNumber() Min(1) pageSize 10; IsOptional() IsString() keyword?: string; IsOptional() IsString() sortField?: string; IsOptional() IsString() sortOrder?: ASC | DESC; }3.2 动态查询构建器创建灵活的查询条件生成工具// shared/utils/query-builder.util.ts import { FindConditions, Like } from typeorm; export function buildQueryConditionsT( dto: PaginationDto, searchFields: Arraykeyof T, ): FindConditionsT { const { keyword } dto; const where: FindConditionsT {}; if (keyword searchFields.length 0) { where.or searchFields.map(field ({ [field]: Like(%${keyword}%), })); } return where; }4. 具体业务模块实现4.1 用户实体定义// modules/user/user.entity.ts import { Entity, Column, PrimaryGeneratedColumn } from typeorm; Entity() export class User { PrimaryGeneratedColumn() id: number; Column() name: string; Column() desc: string; Column({ default: () CURRENT_TIMESTAMP }) createdAt: Date; }4.2 用户服务层继承基础服务// modules/user/user.service.ts import { Injectable } from nestjs/common; import { InjectRepository } from nestjs/typeorm; import { Repository } from typeorm; import { User } from ./user.entity; import { BaseService } from ../../core/base/base.service; import { PaginationDto } from ../../core/pagination/pagination.dto; import { buildQueryConditions } from ../../shared/utils/query-builder.util; Injectable() export class UserService extends BaseServiceUser { constructor( InjectRepository(User) private readonly userRepository: RepositoryUser, ) { super(userRepository); } async findAllWithSearch(pagination: PaginationDto) { const where buildQueryConditions(pagination, [name, desc]); return super.findAll(pagination, where); } }4.3 控制器实现// modules/user/user.controller.ts import { Controller, Get, Post, Body, Patch, Param, Delete } from nestjs/common; import { UserService } from ./user.service; import { CreateUserDto } from ./dto/create-user.dto; import { UpdateUserDto } from ./dto/update-user.dto; import { PaginationDto } from ../../core/pagination/pagination.dto; Controller(users) export class UserController { constructor(private readonly userService: UserService) {} Post() create(Body() createUserDto: CreateUserDto) { return this.userService.create(createUserDto); } Get() findAll(Query() pagination: PaginationDto) { return this.userService.findAllWithSearch(pagination); } Patch(:id) update(Param(id) id: string, Body() updateUserDto: UpdateUserDto) { return this.userService.update(id, updateUserDto); } Delete(:id) remove(Param(id) id: string) { return this.userService.remove(id); } }5. 高级技巧与性能优化5.1 查询缓存策略TypeORM提供了多种缓存机制可以有效减轻数据库压力// 在服务方法中添加缓存 async findAllWithCache(pagination: PaginationDto) { return this.userRepository.findAndCount({ cache: { id: users_${JSON.stringify(pagination)}, milliseconds: 60000, // 缓存1分钟 }, ...this.buildQueryOptions(pagination), }); }5.2 批量操作优化对于大批量数据处理可以使用事务和批量插入async createBatch(users: CreateUserDto[]) { return this.userRepository.manager.transaction(async manager { const userRepository manager.getRepository(User); const entities users.map(user { const entity new User(); entity.name user.name; entity.desc user.desc; return entity; }); return userRepository.insert(entities); }); }5.3 复杂查询构建对于需要多表关联的复杂查询可以使用QueryBuilderasync findWithRoles(userId: number) { return this.userRepository .createQueryBuilder(user) .leftJoinAndSelect(user.roles, role) .where(user.id :userId, { userId }) .cache(true) .getOne(); }6. 测试与验证6.1 单元测试示例使用Jest编写服务层测试describe(UserService, () { let service: UserService; let mockRepository: jest.MockedRepositoryUser; beforeEach(async () { mockRepository { findAndCount: jest.fn(), save: jest.fn(), update: jest.fn(), delete: jest.fn(), } as any; service new UserService(mockRepository); }); it(should return paginated users, async () { const mockUsers [{ id: 1, name: Test }]; mockRepository.findAndCount.mockResolvedValue([mockUsers, 1]); const result await service.findAllWithSearch({ page: 1, pageSize: 10, }); expect(result.data).toEqual(mockUsers); expect(result.total).toBe(1); }); });6.2 API测试用例使用Postman或自动化测试工具验证接口GET /users?page1pageSize10keywordadminsortFieldcreatedAtsortOrderDESC POST /users { name: New User, desc: Description } PATCH /users/1 { name: Updated Name } DELETE /users/17. 扩展与定制7.1 添加审计日志通过NestJS的拦截器实现操作日志记录Injectable() export class AuditInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler) { const request context.switchToHttp().getRequest(); const { method, url, user } request; return next.handle().pipe( tap(() { console.log([Audit] ${user?.id} ${method} ${url}); }), ); } }7.2 数据权限控制基于用户角色过滤查询结果async findAllForUser(pagination: PaginationDto, userId: number) { const where { ...buildQueryConditions(pagination, [name, desc]), createdBy: userId, }; return super.findAll(pagination, where); }7.3 文件导出功能添加CSV导出接口Get(export) Header(Content-Type, text/csv) async exportToCsv(Res() res: Response) { const users await this.userService.findAllForExport(); const csv users.map(u ${u.id},${u.name},${u.desc}).join(\n); res.send(csv); }在实际项目中这种模块化设计可以将CRUD开发时间缩短70%以上。我曾在一个包含20多个实体的管理系统中应用此模式原本需要2周的基础开发工作仅用3天就完成了核心功能的搭建。