NestJS build blog system (four) – use interceptor, exception filter to achieve a unified return format

preface

We implemented data persistence in the previous section, and now we have a working CURD module. In a real project, the server will wrap the data in a uniform return format for easy docking and friendly prompts.

Return structure

When you come into contact with a more comfortable interface format at work, here is a recommendation, there are better practices to share.

// Successful return
{
  code: 200.data: {
    / / class for details
    info: { 
      // Return data
    },

    / / list
    list: [].pagination: {
      total: 100.pageSize: 10.pages: 10.page: 1,}},message: "Request successful"
},

// Return on failure
{
  code: 400.message: "Query failed",}Copy the code

Code Using HTTP code can basically meet this requirement. Info is used to host the detail class, with multiple infos using different prefixes, such as userInfo, articleInfo. List is used for lists, and multiple lists refer to INFO. Pagination is used to carry paging information.

implementation

Request processing successful

Based on Nest’s lifecycle and documentation, successful requests can be wrapped in a post-request interceptor.

Creating interceptors

nest g in interceptor/transform
Copy the code

Modify the interceptor code

// src/interception/transform.interception.ts

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

@Injectable(a)export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(
        map(data= > ({ 
          code: 200,
          data,
          message: 'success'}}})))Copy the code

Use a global interceptor in main

// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './filters/http-execption.filter';
import { TransformInterceptor } from './interceptor/transform.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalInterceptors(new TransformInterceptor())

  await app.listen(3000);
}
bootstrap();
Copy the code

Modify the getOne method of atricle.service

// src/modules/article/article.service.ts

  async getOne(
    idDto: IdDTO  
  ) {
    const { id } = idDto
    const articleDetial = await this.articleRepository
      .createQueryBuilder('article')
      .where('article.id = :id', { id })
      .getOne()

    if(! articleDetial) {throw new NotFoundException('Unable to find article')}const result = {
      info: infoarticleDetial,
    }

    return result
  }
Copy the code

Request /atricle/info? id=1

You can see that the returned information matches our expectations

{
  "code": 200."data": {
    "info": {
      "id": 1."createTime": "The 2021-06-29 T02:48:28. 623 z"."updateTime": "The 2021-06-29 T02:48:28. 623 z"."isDelete": false."version": 1."title": "Heading 1"."description": "1"."content": "1" for details}},"message": "success"
}
Copy the code

Failed to process request

We could have just used the basic exception class provided by Nest, but the format wasn’t what we wanted, so we’ll use an exception filter here.

NestJS provides the basic HTTP exception classes

class meaning Status code
BadRequestException The server did not understand the client’s request and did not do any processing 400
UnauthorizedException The user does not provide authentication credentials or is not authenticated 401
NotFoundException The requested resource does not exist or is unavailable 404
ForbiddenException The user is authenticated, but does not have the required permissions to access the resource 403
NotAcceptableException unacceptable 406
RequestTimeoutException The request timeout 408
ConflictException conflict 409
GoneException The requested resource has been moved from this address and is no longer available 410
PayloadTooLargeException The load is too large 413
UnsupportedMediaTypeException The return format required by the client is not supported. For example, the API can only return JSON, but the client requires XML. 415
UnprocessableException The attachment uploaded by the client cannot be processed, causing a request failure 422
InternalServerErrorException Client request valid, server processing an accident 500
NotImplementedException Unrealized. 501
BadGatewayException Bad gateway 502
ServiceUnavailableException The server is unable to process the request, generally used for website maintenance status 503
GatewayTimeoutException Gateway timeout 504

Blogging systems involve relatively little business, so just go with what Nest provides and unify the return format

Create nest F Filters /httpExecption interceptor

Modify the filters/HTTP – execption. Filters. Ts

// src/filters/http-execption.filters.ts

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
import { execPath } from 'process';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();
    const message = exception.message

    response
      .status(status)
      .json({
        code: status, message, }); }}Copy the code

Use exception filters globally in main.ts

// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './filters/http-execption.filter';
import { TransformInterceptor } from './interceptor/transform.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalInterceptors(new TransformInterceptor())
  app.useGlobalFilters(new HttpExceptionFilter())

  await app.listen(3000);
}
bootstrap();
Copy the code

/article/info? id=10000

{
    "code": 404."message": "Can't find the article"
}
Copy the code

Now that our formatting is basically configured, let’s rewrite the other article methods

// src/modules/article/article.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { ArticleCreateDTO } from './dto/article-create.dto';
import { ArticleEditDTO } from './dto/article-edit.dto';
import { IdDTO } from './dto/id.dto';
import { ListDTO } from './dto/list.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Article } from './entity/article.entity';
import { getPagination } from 'src/utils';

@Injectable(a)export class ArticleService {  
  constructor(
    @InjectRepository(Article)
    private readonly articleRepository: Repository<Article>,
  ) {}

  / * * * *@param listDTO 
   * @returns * /
  async getMore(
    listDTO: ListDTO,
  ) {
		const { page = 1, pageSize = 10 } = listDTO
    const getList = this.articleRepository
      .createQueryBuilder('article')
      .where({ isDelete: false })
      .select([
        'article.id'.'article.title'.'article.description'.'article.createTime'.'article.updateTime',
      ])
      .skip((page - 1) * pageSize)
      .take(pageSize)
      .getManyAndCount()

    const [list, total] = await getList
    const pagination = getPagination(total, pageSize, page)

    return {
      list,
      pagination,
    }
  }

  / * * * *@param idDto 
   * @returns * /
  async getOne(
    idDto: IdDTO  
  ) {
    const { id } = idDto
		const articleDetial = await this.articleRepository
      .createQueryBuilder('article')
      .where('article.id = :id', { id })
      .getOne()

    if(! articleDetial) {throw new NotFoundException('Unable to find article')}return {
      info: articleDetial
    }
  }

  / * * * *@param articleCreateDTO 
   * @returns * /
  async create(
    articleCreateDTO: ArticleCreateDTO
  ){
    const article = new Article();
    article.title = articleCreateDTO.title
    article.description = articleCreateDTO.description
    article.content = articleCreateDTO.content
    const result = await this.articleRepository.save(article);
    
    return {
      info: result
    }
  }

  / * * * *@param articleEditDTO 
   * @returns * /
  async update(
    articleEditDTO: ArticleEditDTO
  ) {
    const { id } = articleEditDTO
    let articleToUpdate = await this.articleRepository.findOne({ id })
    articleToUpdate.title = articleEditDTO.title
    articleToUpdate.description = articleEditDTO.description
    articleToUpdate.content = articleEditDTO.content
    const result = await this.articleRepository.save(articleToUpdate)

    return {
      info: result,
    }
  }
  
  / * * * *@param idDTO 
   * @returns * /
  async delete (
    idDTO: IdDTO,
  ) {
    const { id } = idDTO
    let articleToUpdate = await this.articleRepository.findOne({ id })
    articleToUpdate.isDelete = true
    const result = await this.articleRepository.save(articleToUpdate)
    
    return {
      info: result
    }
  }

}
Copy the code
// src/utils/index.ts

/** * Compute paging *@param total 
 * @param pageSize 
 * @param page 
 * @returns * /
export const getPagination = (
  total: number, 
  pageSize: number, 
  page: number) = > {
  const pages = Math.ceil(total / pageSize)
  return {
    total,
    page,
    pageSize,
    pages,
  }
}

Copy the code

reference

  • NestJS
  • NestJS Chinese website
  • Ruan Yifeng: RESTful API best practices
  • Code for this section

A series of

  • NestJS build blog system (a) – build a framework
  • NestJS build blog system (2) – write static article CURD
  • NestJS build blog system (3) – using TypeORM+Mysql to achieve data persistence