Detailed explanation of Nest.js parameter validation and custom return data format

Detailed explanation of Nest.js parameter validation and custom return data format

0x0 Parameter verification

Most of the parameter verification business is implemented using the pipeline method in Nest.js. For details, please refer to the documentation. However, I encountered some problems during the writing process, although the documentation is rather obscure.

Make a query interface, which contains some parameters and makes it into dto structure data:

import { ApiProperty } from '@nestjs/swagger'

export class QueryUserDto {
 @ApiProperty({
 required: false,
 description: 'Page number'
 })
 readonly currentPage: number

 @ApiProperty({
 required: false,
 description: 'Number of items'
 })
 readonly pageSize: number

 @ApiProperty({
 required: false,
 description: 'User account'
 })
 readonly username?: string

 @ApiProperty({
 required: false,
 description: 'User status'
 })
 readonly activeStatus: number

 @ApiProperty({
 required: false,
 description: 'Sort by: ASC, DESC'
 })
 readonly order: 'DESC' | 'ASC'
}
 TYPESCRIPT

When the corresponding parameters are passed in the @Query request, it is found that the data types obtained are all String. Then, after consulting the relevant documents, it is realized that the Type of class-transformer is also needed for conversion:

import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'

export class QueryUserDto {
 @ApiProperty({
 required: false,
 description: 'Page number'
 })
 @Type(() => Number)
 readonly currentPage: number = 1

 @ApiProperty({
 required: false,
 description: 'Number of items'
 })
 @Type(() => Number)
 readonly pageSize: number = 10

 @ApiProperty({
 required: false,
 description: 'User account'
 })
 readonly username?: string

 @ApiProperty({
 required: false,
 description: 'User status'
 })
 @Type(() => Number)
 readonly activeStatus: number = 3

 @ApiProperty({
 required: false,
 description: 'Sort by: ASC, DESC'
 })
 readonly order: 'DESC' | 'ASC' = 'DESC'
}

Then enable the transform option in the ValidationPipe pipeline method:

app.useGlobalPipes(
 new ValidationPipe({
 transform: true
 })
)

Or inject in app.modules.ts:

import { ValidationPipe } from '@nestjs/common'
import { APP_PIPE } from '@nestjs/core'

@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers:
 {
  provide: APP_PIPE,
  useValue: new ValidationPipe({
  transform: true
  })
 }
 ]
})

The difference between the two methods of use is whether the program is a mixed application type.

To save trouble, I write it directly in the global method. The data finally obtained by the service is the data processed by the pipeline business, and there is no need to perform a lot of data type judgment at the service layer.

0x1 Customize the return data format

The data returned by the controller comes from the database table structure:

{
 "id": "d8d5a56c-ee9f-4e41-be48-5414a7a5712c",
 "username": "Akeem.Cremin",
 "password": "$2b$10$kRcsmN6ewFC2GOs0TEg6TuvDbNzf1VGCbQf2fI1UeyPAiZCq9rMKm",
 "email": "[email protected]",
 "nickname": "Wallace Nicolas",
 "role": "user",
 "isActive": true,
 "createdTime": "2021-03-24T15:24:26.806Z",
 "updatedTime": "2021-03-24T15:24:26.806Z"
}

If you need to define the data format of the final return interface, for example:

{
 "statusCode": 200,
 "message": "Get Success",
 "data": {
  "id": "d8d5a56c-ee9f-4e41-be48-5414a7a5712c",
  "username": "Akeem.Cremin",
  "password": "$2b$10$kRcsmN6ewFC2GOs0TEg6TuvDbNzf1VGCbQf2fI1UeyPAiZCq9rMKm",
  "email": "[email protected]",
  "nickname": "Wallace Nicolas",
  "role": "user",
  "isActive": true,
  "createdTime": "2021-03-24T15:24:26.806Z",
  "updatedTime": "2021-03-24T15:24:26.806Z"
 }
}

Here you need to make a custom success request interceptor:

nest g in shared/interceptor/transform
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { Request } from 'express'

interface Response<T> {
 data: T
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
 intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
 const request = context.switchToHttp().getRequest<Request>()
 Logger.log(request.url, 'Normal interface request')

 return next.handle().pipe(
  map(data => {
  return {
   data: data,
   statusCode: 200,
   message: 'Request successful'
  }
  })
 )
 }
}

Then import it in app.module.ts and use it:

import { ValidationPipe } from '@nestjs/common'
import { APP_INTERCEPTOR } from '@nestjs/core'

import { TransformInterceptor } from '@/shared/interceptor/transform.interceptor'

@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers:
 {
  provide: APP_INTERCEPTOR,
  useClass: TransformInterceptor
 }
 ]
})

However, please pay attention to the order of APP_INTERCEPTOR. It is best to put TransformInterceptor first, otherwise it will fail.

Error Filters:

nest gf shared/filters/httpException
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Logger } from '@nestjs/common'
import { Response, Request } from 'express'

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

 Logger.log(`${request.url} - ${message}`, 'Abnormal interface request')

 response.status(status).json({
  statusCode: status,
  message: message,
  path: request.url,
  timestamp: new Date().toISOString()
 })
 }
}

Then import it in app.module.ts and use it:

import { ValidationPipe } from '@nestjs/common'
import { APP_FILTER } from '@nestjs/core'

import { HttpExceptionFilter } from '@/shared/filters/http-exception.filter'

@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers:
 {
  provide: APP_FILTER,
  useClass: HttpExceptionFilter
 }
 ]
})

0x2 Hide a field in the entity class

I originally wanted to use the @Exclude attribute to hide some sensitive fields in the database, but found that it could not meet special needs. If a single instance is returned, hiding can be achieved, but I have a findAll that cannot be implemented. The above is explained in great detail in the Serialization | NestJS - A progressive Node.js framework document, but there is another way here. First, add attributes to the strength-sensitive data fields:

import { BaseEntity, Entity, Column, PrimaryGeneratedColumn } from 'typeorm'

@Entity('user')
export class UserEntity extends BaseEntity {
 @PrimaryGeneratedColumn('uuid', {
  comment: 'User ID'
 })
 id: string

 @Column({
  type: 'varchar',
  length: 50,
  unique: true,
  comment: 'Logged in user'
 })
 username: string

 @Column({
  type: 'varchar',
  length: 200,
  select: false,
  comment: 'Password'
 })
 password: string

select: false can hide this field when returning query results, but all queries involving this field must add this field, such as in my login query in user.service.ts:

const user = await getRepository(UserEntity)
   .createQueryBuilder('user')
   .where('user.username = :username', { username })
   .addSelect('user.password')
   .getOne()

.addSelect('user.password') Adding this attribute query will include the password field, otherwise the normal query method will not include this field.

Summarize

This is the end of this article about Nest.js parameter validation and custom return data format. For more information about Nest.js parameter validation and data format, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

<<:  Sample code for configuring nginx to support https

>>:  A complete guide on how to query and delete duplicate records in MySQL

Recommend

How to convert a column of comma-separated values ​​into columns in MySQL

Preface Sometimes you come across business tables...

How to get the width and height of the image in WeChat applet

origin Recently, I am working on requirement A, i...

MySQL executes commands for external sql script files

Table of contents 1. Create a sql script file con...

Understand CSS3 Grid layout in 10 minutes

Basic Introduction In the previous article, we in...

How to draw the timeline with vue+canvas

This article example shares the specific code of ...

How to use node to implement static file caching

Table of contents cache Cache location classifica...

WeChat applet implements calculator function

WeChat Mini Programs are becoming more and more p...

18 common commands in MySQL command line

In daily website maintenance and management, a lo...

Nginx implements dynamic and static separation example explanation

In order to speed up the parsing of the website, ...

React mouse multi-selection function configuration method

Generally, lists have selection functions, and si...

Detailed explanation of the new features of ES9: Async iteration

Table of contents Asynchronous traversal Asynchro...

How to implement real-time polygon refraction with threejs

Table of contents Preface Step 1: Setup and front...

Example code of Vue3 encapsulated magnifying glass component

Table of contents Component Infrastructure Purpos...

CSS optimization skills self-practice experience

1. Use css sprites. The advantage is that the smal...