Nest实现CRUD
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 CLI
# 全局安装 nest
pnpm i -g @nestjs/cli
# 创建项目
nest new nest-project
项目结构
简要概述一下 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';

@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 应用程序由多个模块组成,每个模块都是独立的,都有自己的功能范围,可以进行嵌套和引用。每个应用至少有一个模块,即根模块。
@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)
控制器负责处理传入请求并向客户端返回响应
其目的是接收应用的特定请求,路由机制控制哪个控制器接收哪些请求。通常,每个控制器都有不止一条路由,不同的路由可以执行不同的操作。
在上述示例中,使用
@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()
等等,具体如下:
提供器(Provider)
提供器是 Nest 中的一个基本概念。许多基本的 Nest 类可以被视为 provider - service、respository、factory、helper等等。他们都可以通过constructor
注入依赖关系,这意味着对象之间可以创建各种关系,并且 "接线" 这些对象的功能很大程度上可以委托给 Nest 运行时系统。
提供者使用
@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/
拉取 MySQL 镜像
docker pull mysql
根据镜像生成容器
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql-container mysql
连接MySQL
使用 Navicat 连接管理数据库
建立数据库
TypeORM
为了与 SQL 和 NoSQL 数据库集成,Nest 提供了
@nestjs/typeorm
包。TypeORM 是可用于 TypeScript 的最成熟的对象关系映射器 (ORM)。由于它是用 TypeScript 编写的,因此可以很好地与 Nest 框架集成。
ORM
是 Object Relational Mapping
,对象关系映射。也就是说把关系型数据库的表映射成面向对象的 class,表的字段映射成对象的属性映射,表与表的关联映射成属性的关联。
TypeORM官网:typeorm.bootcss.com/
下载安装
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
表
搭建结构
创建 user
相关的 provider
、controller
、module
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 中进行测试
id 为字符串 '1' 时,不符合要求,管道验证不通过,就会抛出异常
在对复杂的数据进行验证时,则需要借助
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 中进行测试
删
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 中进行测试
改
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 中进行测试
查
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 中进行测试
完结撒花
至此,一个 CRUD 的 demo 就完成啦🎉🎉🎉
转载自:https://juejin.cn/post/7393533297894817843