【NestJS系列】连接数据库及优雅地处理响应

前言

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

我们以mysql为例,自行安装mysql

TypeORM

TypeORM 是一个ORM框架,它可以运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript一起使用。 它的目标是始终支持最新的 JavaScript 特性并提供额外的特性以帮助你开发任何使用数据库的(不管是只有几张表的小型应用还是拥有多数据库的大型企业应用)应用程序。

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

安装依赖

shell 复制代码
npm install --save @nestjs/typeorm typeorm mysql2

新建数据库

sql 复制代码
CREATE DATABASE nanjiu
    DEFAULT CHARACTER SET = 'utf8mb4';

新建一个nanjiu数据库

连接数据库

数据库建好之后,我们就可以使用typeorm来连接数据库并建立映射关系了

js 复制代码
// 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中进行注册

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

定义实体

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

js 复制代码
// user.entry.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity('user')  // 表名
export class User {

    @PrimaryGeneratedColumn() // 自增主键
    id: number;

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

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

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

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

关联实体

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

js 复制代码
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService]
})

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

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

CURD接口

数据库准备准备工作完成后,我们就可以来写接口了

controller控制器中定义接口path

js 复制代码
// 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数据验证器

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

dto一般用来做参数验证

注册全局DTO验证管道

js 复制代码
// main.ts
import { ValidationPipe } from '@nestjs/common';

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

service逻辑处理,入库操作

js 复制代码
// 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内置的异常处理层,它返回的信息格式如下:

js 复制代码
{
  "statusCode": 500,
  "message": "Internal server error"
}

比如我们往user库中插入相同的name,但name设置了唯一性,所以这时会抛出错误,如果我们不处理返回给前端就是上面那种信息,这样前端同学看到就会很蒙,根本不知道为啥报错

所以我们要做的就是将响应格式化处理

在nest中,一般是在service 中处理异常,如果有异常,直接抛出错误,由过滤器 捕获,统一格式返回,如果成功,service把结果返回,controller直接return结果即可,由拦截器捕获,统一格式返回 失败:过滤器统一处理 成功:拦截器统一处理

异常拦截器

为了更加优雅地处理异常,我们可以创建一个异常过滤器,它主要用来捕获作为HttpException类实例的异常。

异常抛出封装:

js 复制代码
// 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)
    }
}

抛出异常:

js 复制代码
// 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);
    }
  }
}

异常拦截器封装:

js 复制代码
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);
    }
  }
  

使用:

js 复制代码
 app.useGlobalFilters(new HttpExceptionFilter()); // 全局异常过滤器

请求:

这样报错信息就能够一目了然,简单实用的话可以直接抛出异常就可以,然后在抛出异常的地方给出详细信息。

全局响应拦截器

那成功的响应应该如何优雅地处理呢?

Interceptor拦截器

这里我们可以使用Interceptor拦截器,给成功响应按固定格式返回

js 复制代码
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
            }
        }))
    }
}

使用:

js 复制代码
 app.useGlobalInterceptors(new HttpStatusSuccess()); // 全局拦截器请求成功

请求:

相关推荐
吴声子夜歌4 小时前
Node.js——操作MySQL数据库
数据库·mysql·node.js
清风细雨_林木木8 小时前
Node.js 和 Python 的关系
node.js
吴声子夜歌8 小时前
Node.js——Express框架
node.js·express
吴声子夜歌10 小时前
Node.js——异常处理
node.js
FreeBuf_12 小时前
谷歌将Axios npm供应链攻击归因于朝鲜APT组织UNC1069
前端·npm·node.js
阿正的梦工坊12 小时前
pnpm和npm前端包管理工具有什么不同?
前端·npm·node.js
叶半欲缺12 小时前
Node.js 安装教程
node.js
吴声子夜歌12 小时前
Node.js——Web模板引擎
前端·node.js
雪碧聊技术12 小时前
linux下载node.js(这里面已经包含了npm)
npm·node.js
摇滚侠1 天前
搭建前端开发环境 安装 nodejs 设置淘宝镜像 最简化最标准版本 不使用 NVM NVM 高版本无法安装低版本 nodejs
java·开发语言·node.js