乐趣区

关于前端:NestJS系列连接数据库及优雅地处理响应

前言

Node 作为一门后端语言,当然也能够连贯数据库,为前端提供 CURD 接口

咱们以 mysql 为例,自行装置mysql

TypeORM

TypeORM 是一个 ORM 框架,它能够运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,能够与 TypeScript 和 JavaScript 一起应用。它的指标是始终反对最新的 JavaScript 个性并提供额定的个性以帮忙你开发任何应用数据库的(不论是只有几张表的小型利用还是领有多数据库的大型企业应用)应用程序。

TypeORM作为 TypeScript 中最成熟的对象关系映射器,能够很好的与 Nest 框架集成应用。

装置依赖

npm install --save @nestjs/typeorm typeorm mysql2

新建数据库

CREATE DATABASE nanjiu
    DEFAULT CHARACTER SET = 'utf8mb4';

新建一个 nanjiu 数据库

连贯数据库

数据库建好之后,咱们就能够应用 typeorm 来连贯数据库并建设映射关系了

// dbConfig.ts
// 数据库配置
export function dbConfig()  {
    return {
        type: 'mysql', // 数据库类型
        host: '127.0.0.1', // 数据库地址
        port: 3306, // 端口
        username: 'root', // 用户名
        password: '123456', // 明码
        database: 'nanjiu', // 数据库名
        entities: [__dirname + '/../**/*.entity{.ts,.js}'], // 实体类
        synchronize: true, // 主动创立表
        autoLoadEntities: true, // 主动加载实体类
    } as DbConfig
}

须要在 app.module.ts 中进行注册

@Module({
  imports: [
    NanjiuModule, UserModule, InfoModule, 
    TypeOrmModule.forRoot(dbConfig() as any)
  ],
  controllers: [AppController],
  providers: [AppService],
})

定义实体

实体是一个映射到数据库表的类,应用 @Entity 装璜器来定义

// user.entry.ts
import {Column, Entity, PrimaryGeneratedColumn} from "typeorm";

@Entity('user')  // 表名
export class User {@PrimaryGeneratedColumn() // 自增主键
    id: number;

    @Column() // 字段
    name: string;
}

根本实体由列和关系组成,每个实体必须有一个主列。

每个实体都必须在连贯配置中注册:

entities: [__dirname + '/../**/*.entity{.ts,.js}'], // 实体类

关联实体

实体定义后须要在 module 中导入并关联

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

当你做完这一步之后你会发现数据库里曾经依据你刚刚定义的实体建好了表

这是因为刚刚数据库配置那里开启了synchronize: true 主动创立表

CURD 接口

数据库筹备筹备工作实现后,咱们就能够来写接口了

controller 控制器中定义接口path

// user.controller.ts
import {CreateUserDto} from './dto/create-user.dto';
export class UserController {
  constructor(private readonly userService: UserService,) {}

  @Post('addUser')
  create(@Body() createUserDto: CreateUserDto) {
    // 增加用户
    return this.userService.add(createUserDto);
  }
}

新建 DTO 数据验证器

import {Injectable} from "@nestjs/common";
import {IsNotEmpty, IsString} from "class-validator"; // 引入验证器
@Injectable() 
export class CreateUserDto {@IsString({ message: '用户名必须是字符串'}) // 验证是否是字符串
    @IsNotEmpty({message: '用户名不能为空'}) // 验证是否为空
    name: string; // 用户名
}

dto个别用来做参数验证

注册全局 DTO 验证管道

// main.ts
import {ValidationPipe} from '@nestjs/common';

app.useGlobalPipes(new ValidationPipe()) // 全局验证管道

service 逻辑解决,入库操作

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

@Injectable()
export class UserService {
  constructor(// 应用 @InjectRepository(User) 注入实数据库实体
    @InjectRepository(User)
    private readonly userRepository: Repository<User>
  ) {}

 async add(createUserDto: CreateUserDto) {
    // 增加用户,更多操作参考 TypeORM 文档
    const res = await this.userRepository.save(createUserDto);
    return res
  }
}

调用接口

查看数据库

调用完接口,此时数据库中会新增一条数据

响应后果解决

从下面的响应后果来看并不标准,只是简略的返回了数据库查问后果,并且当零碎产生异样谬误时,如果咱们没有手动解决异样,所有的异样都会进入到 nest 内置的异样解决层,它返回的信息格式如下:

{
  "statusCode": 500,
  "message": "Internal server error"
}

比方咱们往 user 库中插入雷同的 name,但name 设置了唯一性,所以这时会抛出谬误,如果咱们不解决返回给前端就是下面那种信息,这样前端同学看到就会很蒙,基本不晓得为啥报错

所以咱们要做的就是将响应格式化解决

在 nest 中,个别是在 service 中解决异样,如果有异样,间接抛出谬误,由 过滤器 捕捉,对立格局返回,如果胜利,service 把后果返回,controller 间接 return 后果即可,由 拦截器 捕捉,对立格局返回
失败:过滤器对立解决
胜利:拦截器对立解决

异样拦截器

为了更加优雅地解决异样,咱们能够创立一个异样过滤器,它次要用来捕捉作为 HttpException 类实例的异样。

异样抛出封装:

// httpStatus.service.ts
import {Injectable, HttpException, HttpStatus, NestInterceptor} from '@nestjs/common'

@Injectable()
export class HttpStatusError {static fail(error, status = HttpStatus.BAD_REQUEST) {throw new HttpException({statusCode: status, message: '申请失败', error}, status)
    }
}

抛出异样:

// group.service.ts
// ...
import {HttpStatusError} from '../utils/httpStatus.service'

@Injectable()
export class GroupService {
  constructor(@InjectRepository(Group)
    private groupRepository: Repository<Group>,
    @InjectRepository(Template)
    private templateRepository: Repository<Template>,
  ) {}
  // todo: 增加分组
  async create(createGroupDto: CreateGroupDto) {const data = this.groupRepository.create(createGroupDto);
    const group = await this.groupRepository.findOne({where: { name: createGroupDto.name} });
    if (group) {return HttpStatusError.fail('该分组已存在');
    }
    try {const res = await this.groupRepository.save(data, { reload: true});
      return res;
    } catch (error) {return HttpStatusError.fail(error);
    }
  }
}

异样拦截器封装:

import {
    ArgumentsHost,
    Catch,
    ExceptionFilter,
    HttpException,
  } from '@nestjs/common';
  
  @Catch()
  export class HttpExceptionFilter implements ExceptionFilter {catch(exception: HttpException, host: ArgumentsHost) {const ctx = host.switchToHttp();
      const response = ctx.getResponse();
      const request = ctx.getRequest();
  
      const status = exception.getStatus();
      const exceptionRes: any = exception.getResponse();
      const {error, message} = exceptionRes;
  
      const msgLog = {
        status,
        message,
        error,
        path: request.url,
        timestamp: new Date().toLocaleString(),
      };
  
      response.status(status).json(msgLog);
    }
  }
  

应用:

 app.useGlobalFilters(new HttpExceptionFilter()); // 全局异样过滤器

申请:

这样报错信息就可能高深莫测,简略实用的话能够间接抛出异样就能够,而后在抛出异样的中央给出详细信息。

全局响应拦截器

那胜利的响应应该如何优雅地解决呢?

Interceptor 拦截器

这里咱们能够应用 Interceptor 拦截器,给胜利响应按固定格局返回

import {Injectable, HttpException, HttpStatus, NestInterceptor, ExecutionContext,CallHandler} from '@nestjs/common'
import {Observable} from 'rxjs'
import {map} from 'rxjs/operators'


@Injectable()
export class HttpStatusSuccess implements NestInterceptor{intercept(context: ExecutionContext, next: CallHandler) :Observable<any> {return next.handle().pipe(map(data => {
            return {
                statusCode: HttpStatus.OK,
                message: '申请胜利',
                data
            }
        }))
    }
}

应用:

 app.useGlobalInterceptors(new HttpStatusSuccess()); // 全局拦截器申请胜利

申请:

退出移动版