【NestJS系列】連接數據庫及優雅地處理響應
前言
Nest作為一個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()); // 全局攔截器請求成功
「請求:」