nestjs 从零开始 一步一步实践

话不多说 从新建文件夹开始 一步一步爬上去 前端部分不展开 默认有一定的前端基础 源码仓库 需要自取

安装NestJS CLI

首先,需要全局安装NestJS CLI(命令行工具),它可以帮助我们快速创建和管理NestJS项目。

bash 复制代码
npm install -g @nestjs/cli

在当前目录创建项目

安装完成后,可以使用NestJS CLI在当前目录创建一个新项目。

sql 复制代码
nest new . --skip-git

这里的参数解释:

  • . 表示在当前目录创建项目,而不是创建新目录
  • --skip-git 表示跳过Git初始化(如果想使用Git,可以去掉这个参数)

选择包管理器 创建过程

执行命令后,系统会提示选择一个包管理器(npm、yarn或pnpm)。可以根据自己的喜好选择。

创建过程中,CLI会自动为完成以下工作:

  1. 生成标准的NestJS项目结构
  2. 创建必要的配置文件
  3. 安装依赖包 完成后,就可以开始探索NestJS项目的结构和基本概念了。

运行NestJS项目

成功创建了NestJS项目并安装了依赖。现在开始先运行项目,确保一切正常工作。

在终端中执行以下命令来启动开发服务器: pnpm run start:dev

这个命令会启动一个开发服务器,并且会在修改代码时自动重新编译和重启服务器,非常适合开发阶段使用。

如果一切正常,应该会在终端中看到类似下面的输出:

ini 复制代码
[Nest] 10312  - 2025/08/28 17:49:32  LOG [NestFactory] Starting Nest application...
[Nest] 10312  - 2025/08/28 17:49:32  LOG [InstanceLoader] UsersModule dependencies initialized +9ms  
[Nest] 10312  - 2025/08/28 17:49:32  LOG [InstanceLoader] AppModule dependencies initialized +0ms    
[Nest] 10312  - 2025/08/28 17:49:32  LOG [RoutesResolver] AppController {/}: +7ms
[Nest] 10312  - 2025/08/28 17:49:32  LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 10312  - 2025/08/28 17:49:32  LOG [RouterExplorer] Mapped {/greet, GET} route +0ms
[Nest] 10312  - 2025/08/28 17:49:32  LOG [NestApplication] Nest application successfully started +2ms

这表示服务器已经成功启动在3000端口。可以打开浏览器,访问 http://localhost:3000 来查看默认的欢迎页面。

项目结构简介

现在来了解一下NestJS项目的基本结构:

  1. src/main.ts - 应用程序的入口文件,负责创建Nest应用实例并启动它
  2. src/app.module.ts - 应用程序的根模块,包含了其他模块的导入和配置
  3. src/app.controller.ts - 应用程序的根控制器,处理HTTP请求
  4. src/app.service.ts - 应用程序的根服务,包含业务逻辑
  5. src/app.controller.spec.ts - 根控制器的测试文件 NestJS使用了模块化的架构,通过控制器(Controllers)处理HTTP请求,服务(Services)包含业务逻辑,模块(Modules)组织代码结构。

NestJS的核心概念

NestJS采用了模块化的架构,主要包含以下核心概念:

  • 模块(Modules) :组织代码结构,将相关的控制器、服务等组合在一起
  • 控制器(Controllers) :处理HTTP请求,定义路由
  • 服务(Services) :包含业务逻辑,被控制器调用
  • 提供者(Providers) :可以被注入到其他组件的依赖项

修改默认控制器进行牛刀小试

xianzai来修改默认的控制器和服务,创建一个简单的API。首先,查看当前的app.service.ts和app.controller.ts文件,然后进行修改。

修改服务 (AppService)

添加一个新的方法来支持个性化问候:

ts 复制代码
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }

  // 添加个性化问候方法
  getGreeting(name: string): string {
    return `Hello, ${name}! Welcome to 
    NestJS.`;
  }
}

修改控制器 (AppController)

接下来,修改 app.controller.ts ,添加一个新的路由来处理个性化问候请求:

ts 复制代码
import { Controller, Get, Query } from 
'@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: 
  AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  // 添加新的GET路由,支持查询参数name
  @Get('greet')
  getGreeting(@Query('name') name: string): 
  string {
    // 如果没有提供name参数,使用默认值'Guest'
    return this.appService.getGreeting
    (name || 'Guest');
  }
}

代码解释

  1. 服务修改 :添加了 getGreeting 方法,它接受一个name参数并返回个性化问候语。

  2. 控制器修改 :

    • 导入了 Query 装饰器,用于获取URL查询参数
    • 添加了 getGreeting 方法,并使用 @Get('greet') 装饰器将其映射到 /greet 路由
    • 使用 @Query('name') 获取URL中的name参数
    • 实现了参数默认值逻辑,如果没有提供name参数,默认使用'Guest'

测试新API

保存修改后,NestJS开发服务器会自动重启。现在您可以测试两个API端点:

  1. 原有的根路由: http://localhost:3000 ,仍然显示"Hello World!"
  2. 新的个性化问候路由: http://localhost:3000/greet?name=YourName ,将显示"Hello, YourName! Welcome to NestJS." 尝试修改URL中的name参数,看看返回结果如何变化。这个简单的例子展示了NestJS中控制器和服务的基本工作方式,以及如何处理请求参数。

NestJS模块化

接下来学习NestJS的模块化特性,这是NestJS架构的核心概念之一。

随着应用程序的增长,将所有代码放在一个文件中会变得难以维护。NestJS通过模块化来解决这个问题,每个模块可以包含自己的控制器、服务和其他组件。

创建一个用户模块来管理用户相关的功能:

步骤1:使用Nest CLI创建用户模块

在终端中执行以下命令: nest generate module users

这个命令会创建一个新的用户模块,包括:

  • users.module.ts 文件
  • users 目录
  • 自动将新模块导入到根模块 app.module.ts

步骤2:创建用户控制器

接着,在用户模块中创建一个控制器: nest generate controller users

步骤3:创建用户服务

最后,创建一个用户服务: nest generate service users

生成的文件结构

执行完这些命令后,您的项目结构将变为:

arduino 复制代码
src/
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
└── users/
    ├── users.controller.spec.ts
    ├── users.controller.ts
    ├── users.module.ts
    ├── users.service.spec.ts
    └── users.service.ts

理解模块导入

打开 app.module.ts ,会看到Nest CLI已经自动将 UsersModule 添加到了导入列表中:

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.
controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.
module'; // 自动添加的导入

@Module({
  imports: [UsersModule], // 自动添加到导入数组
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

下一步 完善users模块

创建完用户模块后,我们将在新模块中实现用户相关的API端点。这将帮助我们理解NestJS是如何通过模块来组织代码,以及不同模块之间如何交互的。

定义用户数据结构

在用户模块中,需要定义用户的数据结构。先创建一个用户接口: 在users.service.ts

ts 复制代码
import { Injectable } from '@nestjs/common';

// 定义用户接口
export interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable()
export class UsersService {
  // 模拟数据库存储
  private users: User[] = [
    { id: 1, name: '张三', email: 'zhangsan@example.com' },
    { id: 2, name: '李四', email: 'lisi@example.com' },
    { id: 3, name: '王五', email: 'wangwu@example.com' },
  ];

  // 获取所有用户
  findAll(): User[] {
    return this.users;
  }

  // 根据ID获取单个用户
  findOne(id: number): User | undefined {
    return this.users.find(user => user.id === id);
  }

  // 创建新用户
  create(user: Omit<User, 'id'>): User {
    const newUser = {
      id: this.users.length + 1,
      ...user
    };
    this.users.push(newUser);
    return newUser;
  }

  // 更新用户信息
  update(id: number, updatedUser: Partial<User>): User | undefined {
    const index = this.users.findIndex(user => user.id === id);
    if (index !== -1) {
      this.users[index] = {
        ...this.users[index],
        ...updatedUser
      };
      return this.users[index];
    }
    return undefined;
  }

  // 删除用户
  remove(id: number): boolean {
    const index = this.users.findIndex(user => user.id === id);
    if (index !== -1) {
      this.users.splice(index, 1);
      return true;
    }
    return false;
  }
}

接下来,实现用户控制器

现在在控制器中添加API端点来处理HTTP请求: users.controller.ts

ts 复制代码
import { Controller, Get, Post, Put, Delete, Param, Body, NotFoundException } from '@nestjs/common';
import { UsersService, User } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  // GET /users - 获取所有用户
  @Get()
  findAll(): User[] {
    return this.usersService.findAll();
  }

  // GET /users/:id - 获取单个用户
  @Get(':id')
  findOne(@Param('id') id: string): User {
    const user = this.usersService.findOne(Number(id));
    if (!user) {
      throw new NotFoundException(`用户 ID ${id} 不存在`);
    }
    return user;
  }

  // POST /users - 创建新用户
  @Post()
  create(@Body() user: Omit<User, 'id'>): User {
    return this.usersService.create(user);
  }

  // PUT /users/:id - 更新用户信息
  @Put(':id')
  update(@Param('id') id: string, @Body() updatedUser: Partial<User>): User {
    const user = this.usersService.update(Number(id), updatedUser);
    if (!user) {
      throw new NotFoundException(`用户 ID ${id} 不存在`);
    }
    return user;
  }

  // DELETE /users/:id - 删除用户
  @Delete(':id')
  remove(@Param('id') id: string): { message: string } {
    const result = this.usersService.remove(Number(id));
    if (!result) {
      throw new NotFoundException(`用户 ID ${id} 不存在`);
    }
    return { message: '用户已成功删除' };
  }
}

更新用户模块

现在我们需要更新用户模块,将控制器和服务注册到模块中:users.module.ts

ts 复制代码
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService]
})
export class UsersModule {}

理解代码结构

我们刚刚实现了一个简单的用户管理API,包含以下关键点:

  1. 模块 (Module) - users.module.ts 用于组织和注册相关组件

  2. 控制器 (Controller) - users.controller.ts 处理HTTP请求并返回响应

    • 使用装饰器定义路由(@Get, @Post, @Put, @Delete)
    • 使用@Param获取URL参数
    • 使用@Body获取请求体数据
  3. 服务 (Service) - users.service.ts 包含业务逻辑

    • 使用@Injectable装饰器标记为可注入的服务
    • 实现了CRUD操作(创建、读取、更新、删除)
    • 使用数组模拟数据库存储

测试API和开启cors

现在可以使用API测试工具(如Postman、Insomnia或curl)测试这些端点:

  1. 获取所有用户: GET http://localhost:3000/users
  2. 获取单个用户: GET http://localhost:3000/users/1
  3. 创建新用户: POST http://localhost:3000/users (需要JSON请求体)
  4. 更新用户: PUT http://localhost:3000/users/1 (需要JSON请求体)
  5. 删除用户: DELETE http://localhost:3000/users/1

或者在前端项目中开启代理访问 也是能成功获取到接口的 (前端部分暂略)

后端开启代理则在main.ts中

ts 复制代码
  // // // 启用CORS - 这是最基本的配置
  // app.enableCors();

  // 高级CORS配置
  app.enableCors({
    // 允许的源 - 可以是字符串、字符串数组或函数
    origin: [
      'http://localhost:8080', // 前端开发服务器地址
      'http://127.0.0.1:8080',
    ],
    // 允许的HTTP方法
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    // 允许携带凭证(如cookies)
    credentials: true,
    // 允许的请求头
    allowedHeaders: 'Content-Type, Accept, Authorization',
    // 暴露的响应头
    exposedHeaders: 'X-Custom-Header',
    //预检请求的有效期(秒)
    maxAge: 86400,
  });

不过可以看到 目前还没对前端传入数据进行校验 所以肯定是不行的

现在来为用户模块添加数据验证功能,哲理将使用NestJS推荐的 class-validator 库来实现输入数据验证。

第一步:安装必要依赖

首先需要安装两个关键依赖包: pnpm install --save class-validator class-transformer

第二步:创建数据传输对象(DTO)

DTO用于规范API接口的输入输出数据格式,我们创建用户相关的DTO:

create-user.dto.ts

ts 复制代码
import { IsString, IsEmail, MinLength } 
from 'class-validator';
// 创建用户DTO - 用于规范创建用户时的请求数据格式
export class CreateUserDto {
  // @IsString() 验证该字段必须是字符串类型
  @IsString()
  // @MinLength(2) 验证字符串长度至少为2
  @MinLength(2, { message: '用户名长度不能少于
  2个字符' })
  name: string;
  // @IsEmail() 验证该字段必须是合法的邮箱格式
  @IsEmail({}, { message: '请输入有效的邮箱地
  址' })
  email: string;
}

update-user.dto.ts

ts 复制代码
import { IsString, IsEmail, MinLength, 
IsOptional } from 'class-validator';
// 更新用户DTO - 用于规范更新用户时的请求数据格式
export class UpdateUserDto {
  // @IsOptional() 表示该字段是可选的
  @IsOptional()
  @IsString()
  @MinLength(2, { message: '用户名长度不能少于
  2个字符' })
  name?: string;
  @IsOptional()
  @IsEmail({}, { message: '请输入有效的邮箱地
  址' })
  email?: string;
}

第三步:修改用户控制器使用DTO

users.controller.ts

ts 复制代码
import { Controller, Get, Post, Body, 
Patch, Param, Delete, Query } from '@nestjs/
common';
import { UsersService } from './users.
service';
// 导入刚才创建的DTO
import { CreateUserDto } from './dto/
create-user.dto';
import { UpdateUserDto } from './dto/
update-user.dto';
@Controller('users')
export class UsersController {
  constructor(private readonly 
  usersService: UsersService) {}
  // 创建用户 - 使用CreateUserDto验证请求体
  @Post()
  // @Body() 装饰器用于获取请求体数据,并使用
  CreateUserDto进行验证
  create(@Body() createUserDto: 
  CreateUserDto) {
    return this.usersService.create
    (createUserDto);
  }
  // ... 现有代码 ...
  // 更新用户 - 使用UpdateUserDto验证请求体
  @Patch(':id')
  update(
    @Param('id') id: string,
    // 使用UpdateUserDto验证更新数据
    @Body() updateUserDto: UpdateUserDto
  ) {
    return this.usersService.update(+id, 
    updateUserDto);
  }
  // ... 现有代码 ...
}

第四步:更新用户服务

users.service.ts

ts 复制代码
import { Injectable } from '@nestjs/common';
// 导入DTO类型
import { CreateUserDto } from './dto/
create-user.dto';
import { UpdateUserDto } from './dto/
update-user.dto';
// 定义User接口
export interface User {
  id: number;
  name: string;
  email: string;
}
@Injectable()
export class UsersService {
  // 模拟数据库 - 存储用户数据的数组
  private users: User[] = [
    { id: 1, name: '张三', email: 
    'zhangsan@example.com' },
    { id: 2, name: '李四', email: 
    'lisi@example.com' },
    { id: 3, name: '王五', email: 
    'wangwu@example.com' },
  ];
  // 创建用户
  create(createUserDto: CreateUserDto): 
  User {
    // 生成新用户ID (实际项目中通常由数据库自动生
    成)
    const newUser: User = {
      id: this.users.length + 1,
      // 直接使用DTO中的数据,因为已经通过验证
      ...createUserDto
    };
    this.users.push(newUser);
    return newUser;
  }
  // ... 现有代码 ...
  // 更新用户
  update(id: number, updateUserDto: 
  UpdateUserDto): User {
    const index = this.users.findIndex(user 
    => user.id === id);
    if (index === -1) {
      // 在实际项目中,这里应该抛出
      NotFoundException
      throw new Error(`User with ID ${id} 
      not found`);
    }
    // 合并更新数据 - 只更新提供的字段
    this.users[index] = {
      ...this.users[index],
      ...updateUserDto
    };
    return this.users[index];
  }
  // ... 现有代码 ...
}

第五步:启用全局验证管道

main.ts

ts 复制代码
import { NestFactory } from '@nestjs/core';
// 导入ValidationPipe
import { ValidationPipe } from '@nestjs/
common';
import { AppModule } from './app.module';
async function bootstrap() {
  const app = await NestFactory.create
  (AppModule);
  // 启用全局验证管道
  app.useGlobalPipes(
    new ValidationPipe({
      // 启用白名单模式 - 自动移除DTO中未定义的
      属性
      whitelist: true,
      // 禁止非白名单属性 - 如果有未定义的属性,
      将抛出错误
      forbidNonWhitelisted: true,
      // 自动转换输入数据类型
      transform: true,
    })
  );
  await app.listen(3000);
}
bootstrap();

代码解释

  1. DTO (数据传输对象) :

    • 用于验证和规范API输入数据
    • 使用 class-validator 装饰器定义验证规则
    • CreateUserDto 用于创建用户,所有字段都是必填的
    • UpdateUserDto 用于更新用户,所有字段都是可选的
  2. ValidationPipe (验证管道) :

    • whitelist: true :自动过滤掉DTO中未定义的属性
    • forbidNonWhitelisted: true :如果请求中包含未定义的属性,将返回400错误
    • transform: true :自动将请求数据转换为DTO类的实例
  3. 验证装饰器 :

    • @IsString() :验证字段必须是字符串
    • @IsEmail() :验证字段必须是合法邮箱格式
    • @MinLength(n) :验证字符串长度至少为n
    • @IsOptional() :标记字段为可选

测试验证功能

现在可以测试这些验证规则是否生效:

  1. 尝试创建用户时不提供name字段或提供无效邮箱
  2. 尝试更新用户时提供DTO中未定义的字段
  3. 提供符合验证规则的数据,查看是否能成功创建/更新

事实证明现在接口传参验证已经生效

集成数据库 (TypeORM + SQLite)

接下来,我们可以将内存存储替换为实际的数据库。使用TypeORM和SQLite(轻量级,适合学习):

安装必要依赖

bash 复制代码
npm install @nestjs/typeorm typeorm sqlite3

修改app.module.ts以集成数据库:

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.
controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.
module';
// 导入TypeOrmModule,用于配置数据库连接
import { TypeOrmModule } from '@nestjs/
typeorm';

@Module({
  imports: [
    // 配置TypeORM连接
    TypeOrmModule.forRoot({
      // type: 'sqlite' - 指定使用SQLite数据
      库
      type: 'sqlite',
      // database: 'db.sqlite' - 指定数据库文
      件的名称和位置
      database: 'db.sqlite',
      // entities: [] - 指定要映射到数据库表的
      实体类
      // __dirname + '/**/*.entity{.ts,.js}
      ' 表示自动扫描所有以.entity.ts或.entity.
      js结尾的文件
      entities: [__dirname + '/**/*.entity{.
      ts,.js}'],
      // synchronize: true - 自动创建数据库表
      (仅开发环境使用)
      // 注意:在生产环境中应设置为false,避免意
      外删除数据
      synchronize: true,
      // logging: true - 记录SQL查询日志,有助
      于调试
      logging: true,
    }),
    UsersModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

步骤1: 创建用户实体 (Entity)

现在我们需要创建一个用户实体来映射数据库表。实体是一个类,它定义了数据库表的结构: user.entity.ts

ts 复制代码
// 导入TypeORM的装饰器
import { Entity, PrimaryGeneratedColumn, 
Column } from 'typeorm';

// @Entity() 装饰器标记这个类为一个实体,
TypeORM会为它创建对应的数据库表
@Entity()
export class User {
  // @PrimaryGeneratedColumn() 标记这个字段为
  主键,并且值会自动生成(自增)
  @PrimaryGeneratedColumn()
  id: number;

  // @Column() 标记这个字段为表中的一列
  @Column()
  name: string;

  // @Column({ unique: true }) 表示这个字段的
  值在表中必须是唯一的
  // 这对于邮箱字段很重要,可以防止多个用户使用相
  同的邮箱
  @Column({ unique: true })
  email: string;
}

步骤2: 修改UsersModule以使用TypeORM

接下来,我们需要修改UsersModule以导入和使用TypeORM: users.module.ts

ts 复制代码
import { Module } from '@nestjs/common';
import { UsersService } from './users.
service';
import { UsersController } from './users.
controller';
// 导入TypeOrmModule和我们刚刚创建的User实体
import { TypeOrmModule } from '@nestjs/
typeorm';
import { User } from './entities/user.
entity';

@Module({
  // TypeOrmModule.forFeature([User]) 导入
  User实体,使其在当前模块中可用
  // 这会自动创建一个UserRepository,我们可以在
  服务中注入它
  imports: [TypeOrmModule.forFeature
  ([User])],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

步骤3: 更新UsersService以使用TypeORM Repository

现在,让我们更新UsersService以使用TypeORM的Repository来操作数据库,而不是使用内存数组: users.service.ts

ts 复制代码
import { Injectable, NotFoundException } 
from '@nestjs/common';
import { CreateUserDto } from './dto/
create-user.dto';
import { UpdateUserDto } from './dto/
update-user.dto';
// 导入User实体和TypeORM相关的类
import { User } from './entities/user.
entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/
typeorm';

// @Injectable() 装饰器标记这个类为一个可以被注
入的服务
@Injectable()
export class UsersService {
  // 构造函数中注入UserRepository
  // @InjectRepository(User) 装饰器告诉NestJS
  我们需要User实体的Repository
  constructor(
    @InjectRepository(User) 
    private usersRepository: 
    Repository<User>,
  ) {}

  // 获取所有用户
  // Promise<User[]> 表示这个方法返回一个
  Promise,最终解析为User对象的数组
  async findAll(): Promise<User[]> {
    // find() 方法从数据库中检索所有用户记录
    return this.usersRepository.find();
  }

  // 根据ID获取单个用户
  async findOne(id: number): Promise<User> {
    // findOneBy({ id }) 方法根据ID查找单个用
    户
    const user = await this.usersRepository.
    findOneBy({ id });
    
    // 如果找不到用户,抛出NotFoundException异
    常
    if (!user) {
      throw new NotFoundException(`用户 ID $
      {id} 不存在`);
    }
    
    return user;
  }

  // 创建用户
  async create(createUserDto: CreateUserDto)
  : Promise<User> {
    // create() 方法创建一个新的User实体实例,
    但不会保存到数据库
    const newUser = this.usersRepository.
    create(createUserDto);
    
    // save() 方法将新用户保存到数据库并返回保存
    后的用户对象(包含自动生成的ID)
    return this.usersRepository.save
    (newUser);
  }

  // 更新用户信息
  async update(id: number, updateUserDto: 
  UpdateUserDto): Promise<User> {
    // 先调用findOne()检查用户是否存在,如果不存
    在会自动抛出异常
    const user = await this.findOne(id);
    
    // merge() 方法将updateUserDto中的属性合并
    到user对象中
    this.usersRepository.merge(user, 
    updateUserDto);
    
    // save() 方法将更新后的用户保存到数据库
    return this.usersRepository.save(user);
  }

  // 删除用户
  async remove(id: number): Promise<void> {
    // delete() 方法从数据库中删除指定ID的用户
    const result = await this.
    usersRepository.delete(id);
    
    // 如果没有行被影响(即用户不存在),抛出
    NotFoundException异常
    if (result.affected === 0) {
      throw new NotFoundException(`用户 ID $
      {id} 不存在`);
    }
  }
}

步骤4: 更新UsersController以支持异步操作

最后,让我们更新UsersController以支持异步操作(使用async/await): users.controller.ts

ts 复制代码
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Body,
  NotFoundException,
} from '@nestjs/common';
import { UsersService } from './users.
service';
import { CreateUserDto } from './dto/
create-user.dto';
import { UpdateUserDto } from './dto/
update-user.dto';
// 导入User实体
import { User } from './entities/user.
entity';
// @Controller('users') 装饰器标记这个类为控制
器,处理以/users开头的路由
@Controller('users')
export class UsersController {
  // 构造函数中注入UsersService
  constructor(private readonly 
  usersService: UsersService) {}
  // GET /users - 获取所有用户
  @Get()
  // async 关键字表示这个方法是异步的
  async findAll(): Promise<User[]> {
    // await 关键字等待usersService.findAll()
    方法完成
    return await this.usersService.findAll
    ();
  }
  // GET /users/:id - 获取单个用户
  @Get(':id')
  async findOne(@Param('id') id: string): 
  Promise<User> {
    try {
      // 将字符串类型的id转换为数字并调用服务的
      findOne方法
      return await this.usersService.findOne
      (Number(id));
    } catch (error) {
      // 捕获可能的异常并返回友好的错误信息
      throw new NotFoundException(`用户 ID $
      {id} 不存在`);
    }
  }
  // POST /users - 创建用户
  @Post()
  async create(@Body() createUserDto: 
  CreateUserDto): Promise<User> {
    // @Body() 装饰器从请求体中提取数据并自动转
    换为CreateUserDto类型
    return await this.usersService.create
    (createUserDto);
  }
  // PUT /users/:id - 更新用户信息
  @Put(':id')
  async update(
    @Param('id') id: string,
    @Body() updateUserDto: UpdateUserDto,
  ): Promise<User> {
    try {
      // 调用服务的update方法更新用户信息
      return await this.usersService.update
      (+id, updateUserDto);
    } catch (error) {
      throw new NotFoundException(`用户 ID $
      {id} 不存在`);
    }
  }
  // DELETE /users/:id - 删除用户
  @Delete(':id')
  async remove(@Param('id') id: string): 
  Promise<{ message: string }> {
    try {
      await this.usersService.remove(Number
      (id));
      // 返回成功消息
      return { message: '用户已成功删除' };
    } catch (error) {
      throw new NotFoundException(`用户 ID $
      {id} 不存在`);
    }
  }
}

关键概念解释

  1. 异步操作 (async/await) :

    • 异步操作允许程序在等待长时间运行的任务(如数据库查询)完成时继续执行其他代码
    • async 关键字标记一个函数为异步函数
    • await 关键字等待一个Promise完成并返回其结果
  2. TypeORM :

    • TypeORM是一个ORM(对象关系映射)框架,它允许你使用JavaScript/TypeScript对象来操作数据库,而不是直接编写SQL
    • Repository模式是TypeORM的核心概念,每个实体都有一个对应的Repository,用于执行数据库操作
  3. 实体 (Entity) :

    • 实体是一个类,它映射到数据库中的一个表
    • 实体的每个属性映射到表中的一个列
  4. DTO (数据传输对象) :

    • DTO用于定义在应用程序不同层之间传输的数据结构
    • 它们通常用于验证输入数据并限制传输的数据字段
  5. 依赖注入 :

    • 依赖注入是NestJS的核心概念,它允许你将服务和其他组件注入到控制器和其他服务中
    • 这使得代码更加模块化、可测试和可维护

运行与测试

现在,已经将内存存储替换为实际的数据库,并更新了代码以支持异步操作。 完成以上修改后,你可以运行项目并测试新功能:

OK 现在已经成gong建立数据库实现数据持久化了

接下来我们将实现两个重要功能:统一API响应格式和请求参数处理。 管道和过滤器和拦截器的目录

统一API响应格式

我们将创建一个 拦截器(Interceptor) 来统一所有API的响应格式。拦截器可以在请求处理完成后对响应进行转换。

步骤1: 创建响应格式拦截器

ts 复制代码
import { Injectable, NestInterceptor, 
ExecutionContext, CallHandler, HttpStatus } 
from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

// 定义统一响应格式的接口
interface ApiResponse<T> {
  success: boolean;
  data: T;
  message: string;
  statusCode: number;
}

// @Injectable() 标记这是一个可注入的服务
@Injectable()
export class TransformInterceptor<T> 
implements NestInterceptor<T, 
ApiResponse<T>> {
  // intercept方法是拦截器的核心方法
  // context: 包含请求的上下文信息
  // next: 用于将请求传递给下一个拦截器或最终的处
  理程序
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<ApiResponse<T>> {
    // 通过管道操作符处理响应
    return next.handle().pipe(
      map(data => {
        // 获取当前请求的响应对象
        const response = context.
        switchToHttp().getResponse();
        
        // 返回统一格式的响应
        return {
          success: true,
          // 实际响应数据
          data: data || null,
          // 成功消息,默认为空字符串
          message: '',
          // HTTP状态码
          statusCode: response.statusCode
        };
      }),
    );
  }
}

步骤2: 创建异常响应格式过滤器

我们还需要统一异常响应的格式,创建一个异常过滤器:

ts 复制代码
import { ExceptionFilter, Catch, 
ArgumentsHost, HttpException } from 
'@nestjs/common';
import { Response } from 'express';

// @Catch(HttpException) 表示这个过滤器捕获所有
HttpException及其子类
@Catch(HttpException)
export class HttpExceptionFilter implements 
ExceptionFilter {
  catch(exception: HttpException, host: 
  ArgumentsHost) {
    // 获取请求上下文
    const ctx = host.switchToHttp();
    const response = ctx.
    getResponse<Response>();
    
    // 获取异常状态码和响应体
    const status = exception.getStatus();
    const exceptionResponse = exception.
    getResponse();
    
    // 统一异常响应格式
    response.status(status).json({
      success: false,
      data: null,
      // 异常消息
      message: typeof exceptionResponse === 
      'object' 
        ? (exceptionResponse as any).
        message || exception.message
        : exceptionResponse || exception.
        message,
      statusCode: status
    });
  }
}

步骤3: 在main.ts中全局注册拦截器和过滤器

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/
common';
// 导入我们创建的拦截器和过滤器
import { TransformInterceptor } from './
common/interceptors/transform.interceptor';
import { HttpExceptionFilter } from './
common/filters/http-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create
  (AppModule);
  
  // 全局注册验证管道
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }));
  
  // 全局注册响应格式拦截器
  app.useGlobalInterceptors(new 
  TransformInterceptor());
  
  // 全局注册异常过滤器
  app.useGlobalFilters(new 
  HttpExceptionFilter());
  
  await app.listen(3000);
}
bootstrap();

请求参数处理

除了已有的DTO验证外,我们可以添加参数过滤和转换功能。这里我们创建一个自定义管道来处理请求参数。

步骤1: 创建参数处理管道

ts 复制代码
import { PipeTransform, Injectable, 
ArgumentMetadata } from '@nestjs/common';

// 自定义管道实现参数转换和过滤
@Injectable()
export class ParamTransformPipe implements 
PipeTransform {
  // value: 当前处理的参数值
  // metadata: 参数元数据,包含参数类型和数据
  transform(value: any, metadata: 
  ArgumentMetadata) {
    // 根据参数类型进行不同处理
    switch (metadata.type) {
      case 'body':
        return this.transformBody(value);
      case 'query':
        return this.transformQuery(value);
      case 'param':
        return this.transformParam(value);
      default:
        return value;
    }
  }

  // 处理请求体参数
  private transformBody(body: any): any {
    if (typeof body !== 'object' || body 
    === null) {
      return body;
    }

    // 示例:移除所有空字符串属性
    const result = { ...body };
    for (const key in result) {
      if (result[key] === '') {
        delete result[key];
      }
    }
    return result;
  }

  // 处理查询参数
  private transformQuery(query: any): any {
    if (typeof query !== 'object' || query 
    === null) {
      return query;
    }

    // 示例:将查询参数中的字符串'true'/'false'
    转换为布尔值
    const result = { ...query };
    for (const key in result) {
      if (result[key] === 'true') {
        result[key] = true;
      } else if (result[key] === 'false') {
        result[key] = false;
      }
    }
    return result;
  }

  // 处理路径参数
  private transformParam(param: any): any {
    // 示例:移除路径参数中的空格
    if (typeof param === 'string') {
      return param.trim();
    }
    return param;
  }
}

步骤2: 在main.ts中注册参数处理管道

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/
common';
import { TransformInterceptor } from './
common/interceptors/transform.interceptor';
import { HttpExceptionFilter } from './
common/filters/http-exception.filter';
// 导入参数处理管道
import { ParamTransformPipe } from './
common/pipes/param-transform.pipe';

async function bootstrap() {
  const app = await NestFactory.create
  (AppModule);
  
  // 全局注册参数处理管道
  app.useGlobalPipes(
    new ParamTransformPipe(),
    // 已有的验证管道
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    })
  );
  
  app.useGlobalInterceptors(new 
  TransformInterceptor());
  app.useGlobalFilters(new 
  HttpExceptionFilter());
  
  await app.listen(3000);
}
bootstrap();

功能测试与验证

接口数据格式和异常处理都处理完后 然后在前端这边再进行调整下(前端部分略)

调整完成后的效果如下 这样子就可以了

后续有空继续完善。。。

相关推荐
卓码软件测评32 分钟前
【第三方网站运行环境测试:服务器配置(如Nginx/Apache)的WEB安全测试重点】
运维·服务器·前端·网络协议·nginx·web安全·apache
龙在天34 分钟前
前端不求人系列 之 一条命令自动部署项目
前端
开开心心就好34 分钟前
PDF转长图工具,一键多页转图片
java·服务器·前端·数据库·人工智能·pdf·推荐算法
国家不保护废物41 分钟前
10万条数据插入页面:从性能优化到虚拟列表的终极方案
前端·面试·性能优化
文心快码BaiduComate1 小时前
七夕,画个动态星空送给Ta
前端·后端·程序员
web前端1231 小时前
# 多行文本溢出实现方法
前端·javascript
文心快码BaiduComate1 小时前
早期人类奴役AI实录:用Comate Zulu 10min做一款Chrome插件
前端·后端·程序员
人间观察员1 小时前
如何在 Vue 项目的 template 中使用 JSX
前端·javascript·vue.js
布列瑟农的星空1 小时前
大话设计模式——多应用实例下的IOC隔离
前端·后端·架构
EndingCoder1 小时前
安装与环境搭建:准备你的 Electron 开发环境
前端·javascript·electron·前端框架