去年开始就关注nestjs, 最近公司采用typescrtipt, 趁着空闲时间, 学习学习nestjs, 记个笔记, 不定时更新. 但内容非完全原创. 主要参考来源以下:
参考资料: docs.nestjs.cn/6/technique… nestjs.com/ github.com/dzzzzzy/Nes… ....
没有全部列出, 要是跟各位大佬内容冲突了, 见谅哈!
初始化项目
- npm i -g @nestjs/cli
- nest new project_name
controller支持的请求方式
@nest/common组件下支持@Get, @Post, @Delete, @Put四种请求方式.
参数获取
import { Controller, Get, Param, Query, Post, Body } from "@nestjs/common";
@Controller('base')
export class BaseController {
/**
* 获取query string /?page=1&size=20
* @param query
*/
@Get()
getByQuery(@Query() query: any): any {
return query;
}
/**
* 获取url path参数
* @param id
*/
@Get('/:id')
getId(@Param('id') id: number): string {
return `the user id is ${id}`;
}
/**
* json格式 {}
* @param body
*/
@Post()
getByBody(@Body() body: any): any {
return body;
}
}
Providers & Dependency Injection
控制层负责处理request及response, 数据处理一般采用service 依赖注入的方式
-
创建service
import { Injectable } from '@nestjs/common'; @Injectable() export class BaseService { getOne(): any { return '获取到所有的用户'; } }
-
在module中注册
import { Module } from '@nestjs/common'; import { BaseController } from './base.controller'; import { BaseService } from './base.service'; @Module({ controllers: [BaseController], providers: [BaseService],// 注册服务 }) export class BaseModule {}
-
在controller层注入
constructor(private readonly baseService: BaseService) { }
管道-参数校验
管道的作用:
-
数据转换 将输入数据转换为所需的输出
-
数据验证 接收客户端提交的参数,如果通过验证则继续传递,如果验证未通过则提示错误 class-validator, class-transformer组件可以实现用来验证DTO传输的参数校验 一般流程:
-
建立参数DTO, 使用class-validator相关的Decorators
import { IsString, IsInt, IsEmail, IsNotEmpty } from 'class-validator'; import { ApiErrorCode } from 'src/common/enums/api-error-code.enum'; export class BaseDto { @IsNotEmpty({ message: '用户姓名是必不可少的', context: { errorCode: ApiErrorCode.USER_NAME_INVALID } }) name: string; @IsInt({ message: '用户年龄必须是整数', context: { errorCode: ApiErrorCode.USER_ID_INVALID} }) age: number; @IsEmail({}, { message: '必须满足邮箱格式', context: { errorCode: ApiErrorCode.USER_EMAIL_INCALID}}) email: string; }
-
建立对应验证类, 实现PipeTransform的transfrom方法
import { PipeTransform, ArgumentMetadata, Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { plainToClass } from 'class-transformer'; import { validate } from 'class-validator'; import { ApiException } from 'src/common/exception/api.exception'; @Injectable() export class BaseFormDTOValidationPipe implements PipeTransform<any> { async transform(value, { metatype }: ArgumentMetadata) { if (!metatype || !this.toValidate(metatype)) { return value; } const object = plainToClass(metatype, value); const errors = await validate(object); if (errors.length > 0) { // 获取到第一个没有通过验证的错误对象 const error = errors.shift(); const constraints = error.constraints; const contexts = error.contexts; // 将未通过验证的字段的错误信息和状态码,以ApiException的形式抛给我们的全局异常过滤器 for (const key of Object.keys(constraints)) { throw new ApiException(constraints[key], contexts[key].errorCode, HttpStatus.BAD_REQUEST); } } return value; } private toValidate(metatype): boolean { const types = [String, Boolean, Number, Array, Object]; return !types.find((type) => metatype === type); } }
-
建立shared服务模块, 并注册
import { Module } from '@nestjs/common'; import { BaseFormDTOValidationPipe } from './BaseDTOValidationPipe'; @Module({ providers: [ BaseFormDTOValidationPipe, ], }) export class SharedModule {}
-
在app.module.ts模块注册该shared服务模块
@Module({ imports: [BaseModule, SharedModule], controllers: [AppController], providers: [AppService], }) export class AppModule {}
-
使用:
在对应的controllers加上@UsePipes注解, 参数为对应创建的验证类
@Post()
@UsePipes(BaseFormDTOValidationPipe)
getByBody(@Body() baseDto: BaseDto): any {
// tslint:disable-next-line: no-console
console.log(baseDto);
return baseDto;
}
}
中间件middleware
client -> middleware -> routing handler
- 可以执行任何代码
- 对请求和响应进行拦截
- 结束请求-响应周期
- 调用堆栈下一个中间件函数
- 下一个中间件必须调用next()传递控制
-
定义中间件类,实现NestMiddleware的方法
import { NestMiddleware, Injectable } from '@nestjs/common'; import { Request, Response } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: () => void) { // tslint:disable-next-line: no-console console.log(req.url); // tslint:disable-next-line: no-console console.log('记录'); next(); } }
-
依赖注入
注意: 中间件不能在@Module()装饰器列出, 使用模块configure()方法来设置, 包含中间件模块必须实现NestModule接口
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware) // 指定要套用哪个Middleware,可以套用多个middleware,以逗點分隔
.exclude()
.forRoutes(BaseController); // 指定哪些routes要套用,可以传入Controller或是Controller数组
}
}
3. 如果要配置全局中间件 const app = await NestFactory.create(ApplicationModule); app.use(LoggerMiddleware); await app.listen(3000);
统一异常处理横切面过滤器
-
自定义异常类继承ExceptionFilter
import { ExceptionFilter, ArgumentsHost, Catch, HttpException } from '@nestjs/common'; import { ApiException } from './api.exception'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus(); if (exception instanceof ApiException) { response.status(status).json({ errorCode: exception.getErrorCode(), errorMessage: exception.getErrorMessage(), date: new Date().toLocaleDateString(), path: request.url, }); } else { response.status(status).json({ statusCode: status, date: new Date().toLocaleDateString(), path: request.url, error: exception, }); } } }
-
自定义接口异常继承HttpException
import { HttpException, HttpStatus } from '@nestjs/common'; import { ApiErrorCode } from '../enums/api-error-code.enum'; export class ApiException extends HttpException { private errorMessage: string; private errorCode: ApiErrorCode; constructor(errorMessage: string, errorCode: ApiErrorCode, statusCode: HttpStatus) { super(errorMessage, statusCode); this.errorMessage = errorMessage; this.errorCode = errorCode; } getErrorCode(): ApiErrorCode { return this.errorCode; } getErrorMessage(): string { return this.errorMessage; } }
-
注册
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './common/exception/http.exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(3000); } bootstrap();
-
在控制层可以通过throw new ApiException()来抛出异常.
-
自定义错误码
export enum ApiErrorCode { TIMEOUT = -1, // 系统繁忙 SUCCESS = 0, // 成功 USER_ID_INVALID = 10001, // 用户id无效 }
-
最佳方式是每一个控制器的异常通过继承ApiException来抛出异常.
统一的返回格式
-
定义一个返回格式的接口
/** * 请求正确返回的json格式的interface */ export interface ResponseData { code: number; msg: string; data: any; time: string; }
-
定义一个统一返回的类
import { ResponseData } from '../interface/json.data'; export class JsonData { static success(data: any): ResponseData { const result: ResponseData = { code: 10, msg: 'success', data, time: new Date().toLocaleString(), }; return result; } static fail(code: number, msg: string) { const result: ResponseData = { code: 0, msg, data: undefined, time: new Date().toLocaleString(), }; return result; } }
文件上传
-
file.controller.ts
import { Post, Controller, UploadedFile, UseInterceptors, Body } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { UploadFileService } from './file.service'; @Controller('upload') @UseInterceptors(FileInterceptor('file', { limits: { fieldSize: 1024 * 1024, fileSize: 1024 * 1024, }, })) export class FileUploadController { constructor(private readonly uploadFileService: UploadFileService) {} @Post('/file') uploadFile(@UploadedFile() file, @Body() body): any { // tslint:disable-next-line: no-console console.log(file); // tslint:disable-next-line: no-console console.log(body.name); return this.uploadFileService.uploadFile(file); } }
-
file.service.ts
import { Injectable, HttpStatus } from '@nestjs/common'; import { SingleFile } from './file.interface'; import { CryptoUtil } from 'src/common/util/hmac.crytpo'; import { join, extname } from 'path'; import { createWriteStream } from 'fs'; import { ApiException } from 'src/common/exception/api.exception'; import { ApiErrorCode } from 'src/common/enums/api-error-code.enum'; /** * 文件传输controller */ @Injectable() export class UploadFileService { /** * 单个文件传输 * @param file */ public uploadFile(file: SingleFile): string { try { const { originalname, buffer} = file; const extName = extname(originalname); const md5FileName = CryptoUtil.MD5Encrypt(buffer); const fullName = md5FileName + extName; const filePath = join(__dirname, '../', uploadBasePath, fullName); const writeStream = createWriteStream(filePath); writeStream.write(buffer); return uploadBasePath + '/' + fullName; } catch (err) { throw new ApiException('文件上传错误', ApiErrorCode.UPLOAD_FILE_ERROR, HttpStatus.FORBIDDEN); } } } const uploadBasePath: string = 'public/uploads';
整合mysql ----- typeorm
初始化配置
-
$ npm install --save @nestjs/typeorm typeorm mysql
-
项目根目录配置数据库连接信息
{ "type": "mysql", "host": "localhost", "port": 3306, "username": "root", "password": "root", "database": "test", "entities": ["src/**/*.entity{.ts}"], "synchronize": true, "logging": true }
user模块
-
user.module.ts
import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserEntity } from './user.entity'; @Module({ imports: [TypeOrmModule.forFeature([UserEntity])], controllers: [UserController], providers: [UserService], }) export class UserModule {}
-
user.dto.ts
import { IsNotEmpty, MaxLength, IsMobilePhone, IsEmail} from 'class-validator'; import { ApiErrorCode } from 'src/common/enums/api-error-code.enum'; export class UserDto { @IsNotEmpty({message: '用户名不能为空', context: { errorCode: ApiErrorCode.USER_NAME_INVALID}}) @MaxLength(20, {message: '最大长度奴能超过20', context: { errorCode: ApiErrorCode.USER_NAME_INVALID}}) public name: string; @IsMobilePhone('zh-CN', {message: '手机号不合法', context: { errorCode: ApiErrorCode.USER_PHONE_INVALID}}) public phone: string; @IsEmail({}, { message: '邮箱不合法', context: {errorCode: ApiErrorCode.USER_EMAIL_INVALID}}) public email: string; @IsNotEmpty({message: '密码不能为空', context: {errorCode: ApiErrorCode.USER_PASSWORD_INVALID}}) public password: string; }
-
user.entity
import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm'; @Entity('user') export class UserEntity { @PrimaryGeneratedColumn({name: 'id', comment: '主键自增id'}) id: number; @Column({length: 20, name: 'name', comment: '用户名'}) name: string; @Column({name: 'email', length: 32, comment: '邮箱'}) email: string; @Column({name: 'password', length: 64, comment: '密码'}) password: string; }
-
user.service.ts
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { UserEntity } from './user.entity'; import { Repository } from 'typeorm'; import { UserDto } from './use.dto'; @Injectable() export class UserService { constructor( @InjectRepository(UserEntity) private readonly userRepository: Repository<UserEntity>, ) {} async create(userDto: UserDto): Promise<number> { const user = new UserEntity(); user.name = userDto.name; user.password = userDto.password; user.email = userDto.email; const result = await this.userRepository.save(this.userRepository.create(user)); return result.id; } }
-
user.controller.ts
import { Controller, Body, Post, UsePipes } from '@nestjs/common'; import { UserDto } from './use.dto'; import { BaseFormDTOValidationPipe } from 'src/shared/BaseDTOValidationPipe'; import { UserService } from './user.service'; import { UserEntity } from './user.entity'; @Controller('user') export class UserController { constructor(private readonly userService: UserService) {} @Post() @UsePipes(BaseFormDTOValidationPipe) async create(@Body() userDto: UserDto): Promise<number> { // tslint:disable-next-line: no-console return await this.userService.create(userDto); } }
CRUD操作
nestjs 调式
新建 .vscode 文件夹,新增launch.json文件内容如下
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach NestJS WS",
"port": 9229,
"restart": true,
"stopOnEntry": false,
"protocol": "inspector",
"skipFiles": [
"<node_internals>/**/*.js",
"${workspaceRoot}/node_modules/**/*.js",
]
}
]
}
npm run start:debug
启动调试