news 2026/5/15 17:05:07

小满nestjs(第二十七章 实战:从零构建一个带分页与搜索的NestJS CRUD后台)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小满nestjs(第二十七章 实战:从零构建一个带分页与搜索的NestJS CRUD后台)

1. 从零搭建NestJS CRUD后台系统

最近在做一个后台管理系统,发现很多新手朋友对如何实现一个完整的CRUD功能比较困惑。今天我就用最接地气的方式,手把手带你实现一个带分页和搜索的NestJS后台系统。这个系统会包含用户管理模块,实现增删改查全套功能,还会加入数据校验和模糊搜索这些实用特性。

先说说为什么选择NestJS。作为一个基于Node.js的后端框架,NestJS完美结合了面向对象编程和函数式编程的优点。它内置的依赖注入、模块化设计让代码结构特别清晰,特别适合构建企业级应用。而且它原生支持TypeScript,类型检查能帮我们避免很多低级错误。

这个实战项目我们会用到这些核心技术:

  • TypeORM:处理数据库操作
  • class-validator:做数据校验
  • 分页查询:处理大量数据展示
  • 模糊搜索:提升用户体验

2. 项目初始化与基础配置

2.1 创建NestJS项目

首先确保你安装了Node.js(建议16.x以上版本),然后全局安装Nest CLI:

npm i -g @nestjs/cli nest new crud-demo

安装完成后,进入项目目录安装必要依赖:

npm install @nestjs/typeorm typeorm mysql2 class-validator class-transformer

这里我选择MySQL作为数据库,你也可以换成PostgreSQL或其他TypeORM支持的数据库。

2.2 配置TypeORM连接

打开app.module.ts,配置数据库连接:

import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'yourpassword', database: 'crud_demo', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, // 开发环境可以用,生产环境要关掉 }), ], }) export class AppModule {}

注意synchronize: true会在应用启动时自动同步实体到数据库,开发时很方便,但生产环境一定要设为false。

3. 用户模块开发

3.1 创建用户实体

先定义用户实体user.entity.ts

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column({ length: 50 }) name: string; @Column({ length: 500, nullable: true }) desc: string; @Column({ default: () => 'CURRENT_TIMESTAMP' }) createTime: Date; }

这里定义了用户的基本字段,TypeORM的装饰器让实体定义非常直观。@PrimaryGeneratedColumn表示自增主键,@Column可以设置字段长度、是否可为空等属性。

3.2 创建DTO对象

DTO(Data Transfer Object)用于数据传输和验证,我们先创建创建用户的DTO:

import { IsString, IsNotEmpty, Length } from 'class-validator'; export class CreateUserDto { @IsString() @IsNotEmpty() @Length(2, 50) name: string; @IsString() @Length(0, 500) desc?: string; }

这里使用了class-validator的装饰器来做数据校验:

  • @IsString()确保是字符串
  • @IsNotEmpty()不能为空
  • @Length()限制长度范围

更新用户的DTO也类似:

export class UpdateUserDto { @IsString() @Length(2, 50) name?: string; @IsString() @Length(0, 500) desc?: string; }

3.3 实现Service层

用户服务的核心逻辑都在这里:

import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Like } from 'typeorm'; import { User } from './user.entity'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Injectable() export class UserService { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {} async create(createUserDto: CreateUserDto): Promise<User> { const user = this.userRepository.create(createUserDto); return this.userRepository.save(user); } async findAll(query: { keyword?: string; page?: number; pageSize?: number; }): Promise<{ data: User[]; total: number }> { const { keyword = '', page = 1, pageSize = 10 } = query; const [data, total] = await this.userRepository.findAndCount({ where: { name: Like(`%${keyword}%`), }, order: { id: 'DESC' }, skip: (page - 1) * pageSize, take: pageSize, }); return { data, total }; } async findOne(id: number): Promise<User> { return this.userRepository.findOneBy({ id }); } async update(id: number, updateUserDto: UpdateUserDto): Promise<void> { await this.userRepository.update(id, updateUserDto); } async remove(id: number): Promise<void> { await this.userRepository.delete(id); } }

这里有几个关键点:

  1. 使用@InjectRepository注入用户仓库
  2. findAll方法实现了分页和模糊搜索
  3. Like操作符实现模糊查询
  4. findAndCount一次性获取数据和总数,性能更好

3.4 实现Controller层

控制器处理HTTP请求:

import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('users') export class UserController { constructor(private readonly userService: UserService) {} @Post() create(@Body() createUserDto: CreateUserDto) { return this.userService.create(createUserDto); } @Get() findAll( @Query('keyword') keyword?: string, @Query('page') page?: number, @Query('pageSize') pageSize?: number, ) { return this.userService.findAll({ keyword, page, pageSize }); } @Get(':id') findOne(@Param('id') id: string) { return this.userService.findOne(+id); } @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); } }

注意:

  • 路由前缀设为/users
  • @Query获取查询参数
  • +id把字符串ID转为数字

3.5 模块整合

最后把所有这些整合到用户模块:

import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UserService } from './user.service'; import { UserController } from './user.controller'; @Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [UserController], providers: [UserService], }) export class UserModule {}

4. 高级功能实现

4.1 全局异常处理

为了更好的错误处理,我们可以添加全局异常过滤器:

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception.message || 'Internal server error', }); } }

然后在main.ts中使用:

app.useGlobalFilters(new HttpExceptionFilter());

4.2 数据验证管道

NestJS内置了验证管道,我们只需要在main.ts中启用:

app.useGlobalPipes(new ValidationPipe());

这样所有DTO的校验规则都会自动生效。

4.3 接口文档生成

使用Swagger生成API文档:

npm install @nestjs/swagger swagger-ui-express

然后在main.ts中配置:

const config = new DocumentBuilder() .setTitle('CRUD Demo') .setDescription('The CRUD API description') .setVersion('1.0') .addTag('users') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document);

现在访问/api就能看到漂亮的API文档了。

5. 前端对接实战

虽然这不是前端教程,但简单展示下如何用Vue3对接我们的API:

import axios from 'axios'; const api = axios.create({ baseURL: 'http://localhost:3000', }); // 获取用户列表 export const getUsers = (params) => api.get('/users', { params }); // 创建用户 export const createUser = (data) => api.post('/users', data); // 更新用户 export const updateUser = (id, data) => api.patch(`/users/${id}`, data); // 删除用户 export const deleteUser = (id) => api.delete(`/users/${id}`);

前端页面可以这样调用:

const { data, total } = await getUsers({ keyword: searchText.value, page: currentPage.value, pageSize: pageSize.value, });

6. 性能优化与最佳实践

6.1 数据库索引优化

给常用查询字段添加索引:

@Column({ length: 50 }) @Index() name: string;

6.2 缓存常用查询

使用NestJS的缓存模块:

import { CacheModule } from '@nestjs/cache-manager'; @Module({ imports: [CacheModule.register()], }) export class UserModule {}

然后在Service中使用:

@Cacheable({ ttl: 60 }) async findAll() { // 查询逻辑 }

6.3 分页查询优化

对于大数据量分页,推荐使用游标分页:

async findAllAfterId(id: number, limit: number) { return this.userRepository.find({ where: { id: MoreThan(id) }, take: limit, order: { id: 'ASC' }, }); }

6.4 事务处理

多个操作需要原子性时使用事务:

async transfer(fromId: number, toId: number, amount: number) { await this.userRepository.manager.transaction(async (manager) => { const fromUser = await manager.findOneBy(User, { id: fromId }); const toUser = await manager.findOneBy(User, { id: toId }); // 转账逻辑 manager.save(User, [ { ...fromUser, balance: fromUser.balance - amount }, { ...toUser, balance: toUser.balance + amount }, ]); }); }

7. 部署与监控

7.1 生产环境配置

创建ormconfig.prod.js

module.exports = { type: 'mysql', host: process.env.DB_HOST, port: process.env.DB_PORT, username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, entities: ['dist/**/*.entity{.ts,.js}'], synchronize: false, logging: false, extra: { connectionLimit: 10, }, };

7.2 健康检查

添加健康检查端点:

@Get('health') healthCheck() { return { status: 'ok', timestamp: new Date() }; }

7.3 日志记录

使用NestJS的Logger:

private readonly logger = new Logger(UserService.name); async findAll() { this.logger.log('Fetching all users'); // 查询逻辑 }

8. 常见问题解决

8.1 跨域问题

main.ts中启用CORS:

app.enableCors();

8.2 时区问题

确保数据库和服务器时区一致:

@Column({ default: () => 'CURRENT_TIMESTAMP', transformer: { to: (value) => value, from: (value) => moment(value).format('YYYY-MM-DD HH:mm:ss'), }, }) createTime: Date;

8.3 性能监控

使用@nestjs/terminus添加健康检查:

import { TerminusModule } from '@nestjs/terminus'; @Module({ imports: [TerminusModule], }) export class HealthModule {}

9. 项目结构优化

推荐的项目结构:

src/ ├── modules/ │ ├── user/ │ │ ├── dto/ │ │ ├── entities/ │ │ ├── user.controller.ts │ │ ├── user.module.ts │ │ └── user.service.ts ├── common/ │ ├── filters/ │ ├── interceptors/ │ └── pipes/ ├── app.module.ts └── main.ts

10. 测试策略

10.1 单元测试

测试Service方法:

describe('UserService', () => { let service: UserService; let repository: Repository<User>; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UserService, { provide: getRepositoryToken(User), useClass: Repository, }, ], }).compile(); service = module.get<UserService>(UserService); repository = module.get<Repository<User>>(getRepositoryToken(User)); }); it('should create a user', async () => { const userDto = { name: 'Test', desc: 'Test desc' }; const result = await service.create(userDto); expect(result).toBeDefined(); expect(result.name).toEqual(userDto.name); }); });

10.2 E2E测试

测试API端点:

describe('UserController (e2e)', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/users (GET)', () => { return request(app.getHttpServer()) .get('/users') .expect(200) .expect((res) => { expect(res.body.data).toBeInstanceOf(Array); }); }); });

11. 安全加固

11.1 输入消毒

防止XSS攻击:

import { sanitize } from 'class-sanitizer'; @Post() create(@Body() createUserDto: CreateUserDto) { sanitize(createUserDto); // 消毒输入 return this.userService.create(createUserDto); }

11.2 速率限制

防止暴力破解:

import { ThrottlerModule } from '@nestjs/throttler'; @Module({ imports: [ ThrottlerModule.forRoot({ ttl: 60, limit: 10, }), ], }) export class AppModule {}

11.3 敏感数据过滤

使用拦截器过滤密码等敏感字段:

import { map } from 'rxjs/operators'; @Injectable() export class RemoveSensitiveFieldsInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map((data) => { if (Array.isArray(data)) { return data.map((item) => this.removeFields(item)); } return this.removeFields(data); }), ); } private removeFields(data: any) { if (!data || typeof data !== 'object') return data; const { password, ...rest } = data; return rest; } }

12. 扩展功能思路

12.1 文件上传

实现用户头像上传:

@Post('avatar') @UseInterceptors(FileInterceptor('file')) uploadAvatar(@UploadedFile() file: Express.Multer.File) { return this.userService.saveAvatar(file); }

12.2 消息队列

使用Redis处理耗时操作:

import { Queue } from 'bull'; import { InjectQueue } from '@nestjs/bull'; @Injectable() export class UserService { constructor( @InjectQueue('email') private emailQueue: Queue, ) {} async create(userDto: CreateUserDto) { const user = await this.userRepository.create(userDto); await this.emailQueue.add('welcome', { email: user.email }); return this.userRepository.save(user); } }

12.3 微服务拆分

将用户服务拆分为独立微服务:

// main.ts const app = await NestFactory.createMicroservice<MicroserviceOptions>( AppModule, { transport: Transport.TCP, options: { port: 3001 }, }, ); await app.listen();

13. 性能监控与日志

13.1 Prometheus监控

添加性能指标:

import { PrometheusModule } from '@willsoto/nestjs-prometheus'; @Module({ imports: [PrometheusModule.register()], }) export class MetricsModule {}

13.2 ELK日志收集

配置结构化日志:

import { createLogger } from 'winston'; const logger = createLogger({ transports: [new transports.Http({ host: 'logstash', port: 5044 })], }); app.useLogger(logger);

14. 持续集成部署

14.1 Docker化

创建Dockerfile:

FROM node:16-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build CMD ["node", "dist/main"]

14.2 CI/CD配置

GitLab CI示例:

stages: - test - build - deploy test: stage: test script: - npm install - npm run test build: stage: build script: - docker build -t crud-demo . deploy: stage: deploy script: - docker-compose up -d

15. 项目总结与经验分享

在实际开发中,我发现NestJS的模块化设计特别适合团队协作。不同开发者可以并行开发不同模块,通过清晰的接口定义来集成。TypeORM虽然强大,但在复杂查询时还是建议直接使用QueryBuilder,可以获得更好的性能。

分页查询时要注意几个坑:

  1. 不要用skiptake处理大数据量分页,性能很差
  2. 模糊搜索LIKE语句要加索引,否则全表扫描很慢
  3. 事务处理要小心死锁问题

DTO验证在实际项目中帮我们拦截了至少30%的无效请求,大大减轻了服务端压力。建议对所有输入都定义严格的DTO验证规则。

最后,这个项目虽然看起来简单,但涵盖了后端开发的绝大部分核心概念。掌握了这些,你就能应对大多数业务场景的开发需求了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 17:04:22

为hermes agent配置taotoken自定义供应商的完整流程

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 为Hermes Agent配置Taotoken自定义供应商的完整流程 基础教程类&#xff0c;指导需要使用Hermes Agent框架的开发者&#xff0c;如…

作者头像 李华
网站建设 2026/5/15 17:04:05

对比官方价格Taotoken的活动价确实带来了可观节省

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 对比官方价格&#xff0c;Taotoken的活动价确实带来了可观节省 作为一名长期使用多个大模型API进行项目开发的个人开发者&#xff…

作者头像 李华
网站建设 2026/5/15 17:00:06

物联网服务选型指南:从核心模块解析到实战避坑

1. 物联网服务选型&#xff1a;从数据孤岛到智能系统的桥梁在物联网项目里摸爬滚打了十几年&#xff0c;我见过太多项目卡在“服务选型”这个环节。很多工程师朋友&#xff0c;硬件玩得转&#xff0c;代码写得溜&#xff0c;但一到要把设备连上网&#xff0c;让数据跑起来&…

作者头像 李华
网站建设 2026/5/15 16:58:05

明日方舟游戏素材库:创作者的数字宝藏指南

明日方舟游戏素材库&#xff1a;创作者的数字宝藏指南 【免费下载链接】ArknightsGameResource 明日方舟客户端素材 项目地址: https://gitcode.com/gh_mirrors/ar/ArknightsGameResource 你是否曾经为寻找高质量的游戏素材而烦恼&#xff1f;是否希望在创作中融入明日方…

作者头像 李华