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

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

时间:2023-08-29 18:57:28浏览次数:49  
标签:const 数据库 ts 优雅 return NestJS import class

前言

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()); // 全局拦截器请求成功

请求:

标签:const,数据库,ts,优雅,return,NestJS,import,class
From: https://www.cnblogs.com/songyao666/p/17665629.html

相关文章

  • 数据库备份和Shell基础测试及AWK(运维)
    第一题:使用MySQL命令进行备份和恢复的步骤如下:备份test库:使用mysqldump命令备份test库,并将备份写入一个.sql文件中。命令示例:mysqldump-u用户名-p密码test>backup.sql恢复备份:使用mysql命令将备份文件中的数据恢复到test库中。命令示例:mysql-u用户名-p密码test<backu......
  • MySQL数据库:第十四章:(DML)Data Manipulation Language数据操纵语言
    回退至Mysql数据库理论与实战#DML语句★DataManipulationLanguage数据操纵语言关键字:insert 、update、deleteUSEstu0906;CREATETABLEstuinfo(idINT,stunameVARCHAR(20)NOTNULL,genderCHAR,borndate TIMESTAMP,seatINT);#一、插入语法:插入单行:insertinto表......
  • MySQL数据库:第十三章:常见约束
    回退至Mysql数据库理论与实战#常见约束理解:约束是用于限定表的字段的,为了保证数据表的完整性常见约束:★(notnull)NOTNULL非空:用于限定某字段为必填项,比如姓名、id等(default)DEFAULT默认:用于限定某字段如果没有显式的插入值,默认存储的选项,比如性别、成绩等(primarykey)PRIM......
  • mybatis时间字段存入mysql数据库时间差一秒的问题
    环境:springbootmybatisplusentryimportjava.util.Date;/***促销开始时间*/ @JsonFormat( pattern="yyyy-MM-ddHH:mm:ss" )@ApiModelProperty(value="促销开始时间")privateDatestartTime;/***促销结束时间......
  • MySQL数据库索引
    为什么使用索引?在无索引的情况下,MySQL会扫描整张表来查找符合sql条件的记录,其时间开销与表中数据量呈正相关。对关系型数据表中的某些字段建索引可以极大提高查询速度(当然,不同字段是否selective会导致这些字段建立的索引对查询速度的提升幅度不同,而且索引也并非越多越好,因为写入或......
  • MySQL数据库:第十六章:sql高级函数
    我最常用的一个函数是FIND_IN_SET逗号分隔的list列表SELECTID,FID,APP_CODE,PARAM_VALUE,PARAM_TEXT,PARAM_SCHEAME,SHOWORDERFROMG_APP_DATA_CONSUME_PARAMWHEREFIND_IN_SET(FID,‘1,2,3,’)ORDERBYSHOWORDERDESC一、数学函数ABS(x)返回x的绝对值BIN(x)返回x的二......
  • MySQL数据库:第十五章:MySQL安装到最后一步未响应MySQL Server Instance Configuration
    MySQL安装到最后一步未响应第一个方法:打开C盘,并且显示隐藏文件,然后在C盘下就能找到一个文件夹叫“ProgamData”,打开它,删除里面的“mysql”文件夹,然后再重新安装mysql就可以了第二个方法:1.强退那个坑死人的未响应打√界面,也就是任务管理器强退,这个略,2.然后在本地硬盘找......
  • 数据库的创建与删除
    1.数据库字段属性(重点)Unsingned:无符号的整数不能声明为负数zerofill0填充的不足的位数,使用0来填充 自增:通常理解为自增,自动在上一条记录的基础上+1(默认)。通常用来设计唯一的主键-index,必须是整数类型。可以自定义设计主键自增的起始值和步长。......
  • SqlServer中查询数据库所有表及其数据总条数和占用空间
    1、查询某数据库中的所有数据表SELECTname数据表FROMsysobjectsWHERExtype='u'ORDERBYname2、查询某数据库中的所有数据表及其数据总条数SELECTa.name数据表,b.rows数据总条数FROMsysobjectsASaINNERJOINsysindexesASbONa.id=......
  • 【随手记】远程连接orcale数据库(PLSQL、Navicat)
    如果不是为了图方便,最好在本地安装数据库不过安装Orcale确实有点麻烦,而且数据库是共同使用的,远程连接弄好了可以省去很多时间。具体操作这里不说了,网上都有,直接搜索PLSQL或者Navicat远程连接数据库就行。踩坑这里说一下我遇到的问题吧,我先用navicat远程连接的,下载好客户......