likes
comments
collection
share

Nest实现CRUD

作者站长头像
站长
· 阅读数 21

Nest 介绍

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用的框架。它使用渐进式 JavaScript,构建并完全支持 TypeScript(但仍然允许开发者使用纯 JavaScript 进行编码)并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数式反应式编程)的元素。 在幕后,Nest 使用强大的 HTTP 服务器框架,如 Express(默认),也可以选择配置为使用 Fastify! Nest 在这些常见的 Node.js 框架(Express/Fastify)之上提供了一个抽象级别,但也直接向开发者公开了它们的 API。这使开发者可以自由使用可用于底层平台的无数第三方模块。

Nest官网:nest.nodejs.cn/

Nest实现CRUD

创建项目

使用Nest CLI

# 全局安装 nest
pnpm i -g @nestjs/cli
# 创建项目
nest new nest-project

项目结构

Nest实现CRUD

简要概述一下 src/下的几个核心文件

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts

app.controller.ts:具有单一路由的基本控制器

app.controller.spec.ts:控制器的单元测试

app.module.ts:应用的根模块

app.service.ts:具有单一方法的基本服务

main.ts:使用核心函数 NestFactory创建应用实例的应用入口文件

启动项目

运行以下命令将会启动应用,HTTP 服务器将会监听 src/main.ts 文件中定义的端口,应用运行后,在浏览器中打开 http://localhost:3000/ 将会看到 Hello World! 信息

pnpm run satrt
# 此命令将监视你的文件,自动重新编译并重新加载服务器
pnpm run start:dev 

上手体验

浏览分析几个核心文件后,尝试着模仿示例代码编写第一个接口 app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

![image.png](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3226c489f648479dae097ad624ab328e~tplv-73owjymdk6-watermark.image?rk3s=f64ab15b&x-expires=1722824056&x-signature=csQ4t0fO%2BzmAr7fJlEao%2BPWFGF4%3D)
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

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

  @Get('nest')
  getMsg(): string {
    return this.appService.getMsg();
  }
}

app.service.ts

import { Injectable } from '@nestjs/common';

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

  getMsg(): string {
    return 'Hello Nest!';
  }
}

在编写上述代码中,涉及到几个核心知识点

模块(Module)

模块是用 @Module() 装饰器注释的类。NestJS 应用程序由多个模块组成,每个模块都是独立的,都有自己的功能范围,可以进行嵌套和引用。每个应用至少有一个模块,即根模块。

Nest实现CRUD @Module() 装饰器修饰的模块,具有以下属性:

  • providers:将由 Nest 注入器实例化并且至少可以在该模块中共享的提供程序
  • controllers:此模块中定义的必须实例化的控制器集
  • imports:导出此模块所需的提供程序的导入模块列表
  • exports:这个模块提供的 providers 的子集应该在导入这个模块的其他模块中可用

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

控制器(Controller)

控制器负责处理传入请求并向客户端返回响应

Nest实现CRUD 其目的是接收应用的特定请求,路由机制控制哪个控制器接收哪些请求。通常,每个控制器都有不止一条路由,不同的路由可以执行不同的操作。 在上述示例中,使用 @Controller() 装饰器来修饰定义控制器,使用 @Get() HTTP 请求方法装饰器告诉 Nest 为 get 请求响应特定的请求处理程序,此外,还可以在 @Controller() 装饰器中使用路径前缀对相关路由进行分组,示例如下: app.controller.ts

import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';

// 设置路径前缀 user
@Controller('user')
export class AppController {
  constructor(private readonly appService: AppService) {}
  
  @Get()
  findOne(): string {
    return '唔西迪西';
  }

  @Post()
  create() {
    return '新增用户';
  }
}

在浏览器中打开 http://localhost:3000/user 即可看到唔西迪西信息,使用 postman 模拟 post 请求也可以看到新增用户的信息。 Nest 为所有标准的 HTTP 方法都提供了相应的装饰器:@Get()@Post()@Put()@Delete()@Patch()@Options()@Head()。此外,@All() 定义了一个端点来处理所有这些。 与此同时,Nest 也提供了访问请求对象相关的装饰器,例如 @Body()@Query() 等等,具体如下:

Nest实现CRUD

提供器(Provider)

提供器是 Nest 中的一个基本概念。许多基本的 Nest 类可以被视为 provider - service、respository、factory、helper等等。他们都可以通过constructor 注入依赖关系,这意味着对象之间可以创建各种关系,并且 "接线" 这些对象的功能很大程度上可以委托给 Nest 运行时系统。

Nest实现CRUD 提供者使用 @Injectable() 装饰器进行修饰,在上述示例中,AppService 类就是一个提供者,在被 @Injectable() 装饰后,AppService 类就可以被 [[Nest IoC]]((https://en.wikipedia.org/wiki/Inversion_of_control)) 容器管理,通过 constructor 注入依赖,Nest 在解析的时候,就会根据其依赖关系自动创建并返回AppService 的实例,而无需我们手动创建。

// 这里通过 constructor 构造函数注入,就不需要使用 new AppService() 去实例化
constructor(private readonly appService: AppService) {}

安装数据库

对 Nest 有了基本认识后,接下来就来开始写 CRUD 了,但是在此之前,需要安装配置数据库。 在这里,我选用的是数据库是 MySQL,毕竟实际项目中大多数选择的是它。我选择安装数据库的方式的 Docker,具体操作方式如下:

下载安装 Docker

Docker官网:www.docker.com/

Nest实现CRUD

拉取 MySQL 镜像

docker pull mysql

根据镜像生成容器

docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql-container mysql

连接MySQL

使用 Navicat 连接管理数据库

Nest实现CRUD

建立数据库

Nest实现CRUD

TypeORM

为了与 SQL 和 NoSQL 数据库集成,Nest 提供了 @nestjs/typeorm 包。TypeORM 是可用于 TypeScript 的最成熟的对象关系映射器 (ORM)。由于它是用 TypeScript 编写的,因此可以很好地与 Nest 框架集成。

ORMObject Relational Mapping,对象关系映射。也就是说把关系型数据库的表映射成面向对象的 class,表的字段映射成对象的属性映射,表与表的关联映射成属性的关联。

TypeORM官网:typeorm.bootcss.com/

Nest实现CRUD

下载安装

pnpm install --save @nestjs/typeorm typeorm mysql2

使用配置 app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql', // 数据库类型
      host: 'localhost', // 指定数据库的主机地址
      port: 3306, // 指定数据库的端口号
      username: 'root', // 指定登录数据库的用户名
      password: '123456', // 指定登录数据库的密码
      database: 'database', // 指定要操作的数据库
      entities: [], // 指定与数据库的表相对应的 Entity 类
      synchronize: true, // 是否同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行
      logging: true, // 是否打印生成的 sql 语句
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

CRUD

前置知识已经了解的差不多了,接下来我们就开始操作数据库,编写 crud代码了哦。首先了,我们得建表,前面提到过,TypeORM 是通过实体(Entity)映射到数据库表,建表的依据就是 Entity。

编写 Entity 建表

使用装饰器定义实体及列 user/entities/user.entity.ts

import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm';
import dayjs from 'dayjs';

const transformer = {
  // 用于在从数据库读取数据时转换数据
  from(value) {
    return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
  },
  // 用于在向数据库写入数据时转换数据
  to(value) {
    // return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
  },
};

@Entity()
export class User {
  // 主键,值自动生成
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    length: 20,
  })
  username: string;

  @Column({
    length: 20,
  })
  nickname: string;

  @Column()
  gender: string;

  @Column()
  age: number;

  @Column({
    type: 'text',
  })
  description: string;

  @CreateDateColumn({
    transformer,
  })
  createTime: Date;

  @UpdateDateColumn({
    transformer,
  })
  updateTime: Date;
}

app.module.ts导入

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { User } from './user/entities/user.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql', // 数据库类型
      host: 'localhost', // 指定数据库的主机地址
      port: 3306, // 指定数据库的端口号
      username: 'root', // 指定登录数据库的用户名
      password: '123456', // 指定登录数据库的密码
      database: 'database', // 指定要操作的数据库
      entities: [User], // 指定与数据库的表相对应的 Entity 类
      synchronize: true, // 是否同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行
      logging: true, // 是否打印生成的 sql 语句
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

保存即可,打开 Navicat ,找到我们建立的数据库,就可以发现建立一张 user

Nest实现CRUD

搭建结构

创建 user 相关的 providercontrollermodule

src
├── user
│   ├── entities
│   │   └── user.entity.ts
│   ├── user.controller.ts
│   ├── user.module.ts
│   └── user.service.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts

UserModule 模块中使用使用 forFeature() 方法来定义在当前作用域内注册了 User 存储库。有了它,我们可以使用 @InjectRepository() 装饰器将 UsersRepository 注入到 UsersService 中。 user.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

user.service.ts

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';

@Injectable()
export class UserService {
  @InjectRepository(User)
  private userRepository: Repository<User>;
}

user.controller.ts

import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}
}

验证数据

在编写接口之前,需要考虑每个接口接受的参数是否符合要求,哪些字段必传,什么类型,这时候就需要用到管道,管道是用 @Injectable() 装饰器注释的类,它具有转型、验证的作用,在由控制器路由处理器处理的 arguments上运行。 user.controller.ts

import { Controller, Post, Get, Param, ParseIntPipe } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}
  
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return id;
  }
}

在 postman 中进行测试

Nest实现CRUD

id 为字符串 '1' 时,不符合要求,管道验证不通过,就会抛出异常

Nest实现CRUD 在对复杂的数据进行验证时,则需要借助 class-validator 这个库,它允许你使用基于装饰器的验证。 user/dto/create-user.dto.ts

import { IsInt, IsString, MaxLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MaxLength(20)
  username: string;

  @IsString()
  @MaxLength(20)
  nickname: string;

  @IsString()
  gender: string;

  @IsInt()
  age: number;

  @IsString()
  description: string;
}

注册全局作用域管道,以便将其应用于整个应用中的每个路由处理程序。 在 app.module.ts 中是使用 APP_PIPE 令牌进行全局注册 app.module.ts

import { Module, ValidationPipe } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { User } from './user/entities/user.entity';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql', // 数据库类型
      host: 'localhost', // 指定数据库的主机地址
      port: 3306, // 指定数据库的端口号
      username: 'root', // 指定登录数据库的用户名
      password: '123456', // 指定登录数据库的密码
      database: 'database', // 指定要操作的数据库
      entities: [User], // 指定与数据库的表相对应的 Entity 类
      synchronize: true, // 是否同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行
      logging: true, // 是否打印生成的 sql 语句
    }),
    UserModule,
  ],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}

统一接口返回格式

统一接口返回格式,增强项目规范,返回的数据应包含:状态码、状态信息、具体数据。格式规范如下:

{
  "code": 200,
  "msg": "OK",
  "data": {
  }
}

封装一个接口格式化返回工具 common/utils/result.ts

import { HttpStatus } from '@nestjs/common';

export class ResultData<T = any> {
  code: number;
  msg: string;
  data: T;
  constructor(code: number, msg?: string, data?: T) {
    this.code = code;
    this.msg = msg || '操作成功!';
    this.data = data || null;
  }

  static OK<T>(msg?, data?: T) {
    return new ResultData(HttpStatus.OK, msg, data);
  }

  static Error<T>(code: number = HttpStatus.BAD_REQUEST, msg: string = '操作失败!', data?: T) {
    return new ResultData(code, msg, data);
  }
}

使用过滤器处理应用中未处理的异常,进行响应格式化 common/filters/http-exception.filter.ts

import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common';
import { Response } from 'express';

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const exceptionResponse: any = exception instanceof HttpException ? exception.getResponse() : 'Internal server error';
    const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
    response.status(status).json({
      code: status,
      msg: exceptionResponse?.message ? exceptionResponse.message : exceptionResponse,
      data: null,
    });
  }
}

app.module.ts中使用 APP_FILTER令牌进行全局注册 app.module.ts

import { Module, ValidationPipe } from '@nestjs/common';
import { APP_PIPE, APP_FILTER } from '@nestjs/core';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { User } from './user/entities/user.entity';
import { UserModule } from './user/user.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql', // 数据库类型
      host: 'localhost', // 指定数据库的主机地址
      port: 3306, // 指定数据库的端口号
      username: 'root', // 指定登录数据库的用户名
      password: '123456', // 指定登录数据库的密码
      database: 'database', // 指定要操作的数据库
      entities: [User], // 指定与数据库的表相对应的 Entity 类
      synchronize: true, // 是否同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行
      logging: true, // 是否打印生成的 sql 语句
    }),
    UserModule,
  ],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

user.service.ts

import { HttpException, Injectable, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UserService {
  @InjectRepository(User)
  private userRepository: Repository<User>;

  async create(createDemoDto: CreateUserDto) {
    const foundUser = await this.userRepository.findOneBy({
      username: createDemoDto.username,
    });
    if (foundUser) {
      throw new HttpException('此用户已存在!', HttpStatus.BAD_REQUEST);
    }
    await this.userRepository.save(createDemoDto);
    return ResultData.OK('新增成功!');
  }
}

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';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  async create(@Body() createUserDto: CreateUserDto) {
    return await this.userService.create(createUserDto);
  }
}

在 postman 中进行测试

Nest实现CRUD

user.service.ts

import { HttpException, Injectable, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { ResultData } from 'src/common/utils/result';

@Injectable()
export class UserService {
  @InjectRepository(User)
  private userRepository: Repository<User>;

  async remove(id: number) {
    const foundUser = await this.userRepository.findOneBy({
      id,
    });
    if (!foundUser) {
      throw new HttpException('此用户不存在!', HttpStatus.BAD_REQUEST);
    }
    await this.userRepository.delete(id);
    return ResultData.OK('删除成功!');
  }
}

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';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Delete(':id')
  async remove(@Param('id') id: number) {
    return await this.userService.remove(id);
  }
}

在 postman 中进行测试

Nest实现CRUD

user/dto/update-user.dto.ts

import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

user.service.ts

import { HttpException, Injectable, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ResultData } from 'src/common/utils/result';

@Injectable()
export class UserService {
  @InjectRepository(User)
  private userRepository: Repository<User>;

  async update(id: number, updateUserDto: UpdateUserDto) {
    const foundUser = await this.userRepository.findOneBy({
      id,
    });
    if (!foundUser) {
      throw new HttpException('此用户不存在!', HttpStatus.BAD_REQUEST);
    }
    await this.userRepository.update(id, updateUserDto);
    return ResultData.OK('更新成功!');
  }
}

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';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Patch(':id')
  async update(@Param('id') id: number, @Body() updateUserDto: UpdateUserDto) {
    return await this.userService.update(id, updateUserDto);
  }
}

在 postman 中进行测试

Nest实现CRUD

user.service.ts

import { HttpException, Injectable, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ResultData } from 'src/common/utils/result';

@Injectable()
export class UserService {
  @InjectRepository(User)
  private userRepository: Repository<User>;

  async findOne(id: number) {
    const foundUser = await this.userRepository.findOneBy({
      id,
    });
    if (!foundUser) {
      throw new HttpException('此用户不存在!', HttpStatus.BAD_REQUEST);
    }
    return ResultData.OK('查询成功!', foundUser);
  }

  async findAll() {
    const list = await this.userRepository.find();
    return ResultData.OK('查询成功!', list);
  }
}

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';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get(':id')
  async findOne(@Param('id') id: number) {
    return await this.userService.findOne(id);
  }

  @Get()
  async findAll() {
    return await this.userService.findAll();
  }
}

在 postman 中进行测试

Nest实现CRUD Nest实现CRUD

完结撒花

至此,一个 CRUD 的 demo 就完成啦🎉🎉🎉

转载自:https://juejin.cn/post/7393533297894817843
评论
请登录