In the last article, we realized the basic operation of database and joint table query.

This is the final part of the tutorial, and while we’ve implemented the basics of NestJs, a real server needs to do global data checking, sensitive operations dropping, and data conversion.

These capabilities can make the background more rich, this article will mainly use the advanced capabilities of NestJs, to achieve these functions.

Summary of this article:

  • useguardsdecoratorsRealize data verification
  • throughinterceptorsdecoratorsRealize sensitive operation entry
  • The custompipesData conversion

The full example can be found at Github.

The guard Guards

Before the request arrives at the business logicIt will go through guard, so that unified processing can be done before the interface.

For example, check login status, check permissions…

Information that needs to be checked uniformly before business logic can be abstracted as guards.

In real scenarios, most backend administrators use JWT to implement interface authentication. NestJs also provides a corresponding solution.

Due to the long and similar principle, this article uses the verification user field to demonstrate.

The new guard

Create the user.guard.ts file

// src/common/guards/user.guard.ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable(a)export class UserGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    const user = request.body.user;

    if (user) {
        return true;
    }

    throw new UnauthorizedException('need user field'); }}Copy the code

A single interface uses guards

Use @useGuards as a reference for a single interface. Take the defined UserGuard as an input parameter.

Used in student.controller.ts

import {  UseGuards  / * *... * * /} from '@nestjs/common';
import { UserGuard } from '.. /common/guards/user.guard';
// ...

@Controller('students')
export class StudentsController {
    constructor(private readonly studentsService: StudentsService) {}

    @UseGuards(UserGuard)
    @Post('who-are-you')
    whoAreYouPost(@Body() student: StudentDto) {
        return this.studentsService.ImStudent(student.name);
    }
    // ...
}

Copy the code

This works when accessing who-are-you and who-IS-Request

/ / ❌ don't use user curl -x POST http://127.0.0.1:3000/students/who-are-you - H'Content-Type: application/json' -d '{"name": "gdccwxx"}'/ / = > {"statusCode": 401,"message":"need user to distinct"."error":"Unauthorized"} % / / ✅ using user / / curl -x POST http://127.0.0.1:3000/students/who-are-you - H'Content-Type: application/json' -d '{"user": "gdccwxx", "name": "gdccwxx"}'
// => Im student gdccwxx%
Copy the code

The global

Global use is only introduced in app.module.ts providers. So that works globally

import { APP_GUARD } from '@nestjs/core';
import { UserGuard } from './common/guards/user.guard';
// ...

@Module({
  controllers: [AppController],
  providers: [{provide: APP_GUARD,
      useClass: UserGuard,
    },
      AppService
  ],
  // ...
})
export class AppModule {}

Copy the code

Get requests who-are-you and POST requests who-IS-Request

/ / ❌ get - all - who you http://localhost:3000/students/who-are-you? name=gdccwxx // => { // statusCode: 401, // message:"need user field",
// error: "Unauthorized"/ / / /} ✅ post curl -x post http://127.0.0.1:3000/students/who-is-request - H'Content-Type: application/json' -d '{"user": "gdccwxx"}'
// => gdccwxx%
Copy the code

Custom decorator filtering

There are always interfaces where we don’t need the User field, and custom decorators come in.

The basic principle is that MetaData is set up in front of the interface and written to memory when the service is started, so that MetaData labels can be detected when requests come in. If yes, it passes; if no, it is verified.

Also filter out the GET request type.

// common/decorators.ts
export const NoUser = () = > SetMetadata('no-user'.true);
Copy the code

The user. The guard. The ts

// user.guard.ts
import { Reflector } from '@nestjs/core';
// ..

@Injectable(a)export class UserGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    const user = request.body.user;

    if(request.method ! = ='POST') {
        return true;
    }

    const noCheck = this.reflector.get<string[] > ('no-user', context.getHandler());

    if (noCheck) {
        return true;
    }

    if (user) {
        return true;
    }

    throw new UnauthorizedException('need user field'); }}Copy the code

NoUser use

// students.controller.ts
import { User, NoUser } from '.. /common/decorators';
// ..

@Controller('students')
export class StudentsController {
    // ...
    @NoUser(a)@Post('who-are-you')
    whoAreYouPost(@Body() student: StudentDto) {
        return this.studentsService.ImStudent(student.name); }}Copy the code

When you call it again, you don’t check it anymore.

/ / ✅ curl -x POST http://127.0.0.1:3000/students/who-are-you - H'Content-Type: application/json' -d '{"name": "gdccwxx"}'
// => Im student gdccwxx%
Copy the code

This implements global guarding, but some interfaces do not need guarding.

It is especially suitable for verification of login state. Only login interface does not need login state, and other interfaces need login state or authentication.

The interceptor Interceptors

Interceptors work before and after a request. It works like a decorator, except that it can be done at the global level.

Its application scenarios are also very wide, such as: interface request parameters and request results of data storage, design mode adapter mode…

We use it to achieve sensitive information data preservation.

It works like Guards in that the decorator loads it into memory, knows which interfaces need sensitive action records, and then stores the input parameters and results when the interface is called.

Database operations are involved, so you need to add modules and database connections.

Create a sensitive permission module

Create sensitive permission modules, including Controller, Module, and Service

nest g controller sensitive
nest g module sensitive
nest g service sensitive
Copy the code

Creating an Entity file

A new sensitive. Entity. Ts.

Transformer is used here because mysql does not have the Object type underlying. It needs to be stored in string format via JS and read in object format. So the code doesn’t have to be aware of what type it is.

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
import { SensitiveType } from '.. /constants';

// to write to the database
// from reads from the database
const dataTransform = {
    to: (value: any) = > JSON.stringify(value || {}),
    from: (value: any) = > JSON.parse(value)
}

@Entity(a)export class Sensitive {
  @PrimaryGeneratedColumn(a)id: number;

  @Column({ type: 'enum'.enum: SensitiveType })
  type: string;

  @Column({ type: 'varchar' })
  pathname: string;

  @Column({ type: 'text'.transformer: dataTransform })
  parameters: any;

  @Column({ type: 'text'.transformer: dataTransform })
  results: any;

  @CreateDateColumn(a)createDate: Date;
}
Copy the code

Reference database

As before, we introduced the database in sensitive.module.ts

import { Module } from '@nestjs/common';
import { SensitiveController } from './sensitive.controller';
import { SensitiveService } from './sensitive.service';
import { Sensitive } from './entities/sensitive.entity';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  controllers: [SensitiveController],
  imports: [TypeOrmModule.forFeature([Sensitive])],
  providers: [Sensitive, SensitiveService],
  exports: [SensitiveService],
})
export class SensitiveModule {}
Copy the code

Service core logic

Sensitive operations are relatively simple, and the Service only needs to add and query.

Define sensitive operation types first

// src/sensitive/constants.ts
export enum SensitiveType {
    Modify = 'Modify'.Set = 'Set',
    Create = 'Create',
    Delete = 'Delete',}Copy the code

When modifying service, introduce db operation

// src/sensitive/sensitive.service.ts
import { Injectable } from '@nestjs/common';
import { Sensitive } from './entities/sensitive.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SensitiveType } from './constants';

@Injectable(a)export class SensitiveService {
    constructor(
        @InjectRepository(Sensitive)
        private readonly sensitiveRepository: Repository<Sensitive>,
    ) {}

    async setSensitive(type: SensitiveType, pathname: string, parameters: any, results: any) {
        return await this.sensitiveRepository.save({
            type,
            pathname,
            parameters,
            results,
        }).catch(e= > e);
    }

    async getSensitive(type: SensitiveType) {
        return await this.sensitiveRepository.find({ 
            where: {
                type,}}); }}Copy the code

The controller to modify

Controller is relatively simple and requires only a simple query. Decorator + Interceptor is used to write sensitive information

// src/sensitive/sensitive.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { SensitiveService } from './sensitive.service';
import { SensitiveType } from './constants';

@Controller('sensitive')
export class SensitiveController {
    constructor(private readonly sensitiveService: SensitiveService) {}

    @Get('/get-by-type')
    getSensitive(@Query('type') type: SensitiveType) {
        return this.sensitiveService.getSensitive(type); }}Copy the code

New decorator

This is where decorators come in. You just need to tell an interface that you want sensitive operation records and specify the type.

// src/common/decorators
import { SetMetadata } from '@nestjs/common';
import { SensitiveType } from '.. /sensitive/constants';

export const SensitiveOperation = (type: SensitiveType) = > SetMetadata('sensitive-operation'.type);
// ...
Copy the code

Define the types of sensitive operations by passing parameters. In the database can be classified, through the way of the index to find modified entry and results.

The interceptor

Here we go!!

Similar to guard permission check, use reflector to remove the sensitive-operation type from the memory.

New SRC/common/interceptors/sensitive. The interceptor. Ts

// src/common/interceptors/sensitive.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SensitiveService } from '.. /.. /sensitive/sensitive.service';
import { SensitiveType } from '.. /.. /sensitive/constants';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable(a)export class SensitiveInterceptor implements NestInterceptor {
  constructor(private reflector: Reflector, private sensitiveService: SensitiveService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();

    const type = this.reflector.get<SensitiveType | undefined> ('sensitive-operation', context.getHandler());

    if (!type) {
        return  next.handle();
    }

    return next
      .handle()
      .pipe(
        tap((data) = > this.sensitiveService.setSensitive(type, request.url, request.body, data)), ); }}Copy the code

And introduce globals in app.module.ts.

// app.module.ts
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { SensitiveInterceptor } from './common/interceptors/sensitive.interceptor';
// ...

@Module({
  providers: [{provide: APP_INTERCEPTOR,
      useClass: SensitiveInterceptor,
    },
    // ...].// ...
})
export class AppModule {}Copy the code

Other module references

Introduction of Student module

// src/students/students.controller.ts
import { SensitiveOperation } from '.. /common/decorators';
import { SensitiveType } from '.. /sensitive/constants';
// ...

@Controller('students')
export class StudentsController {
    constructor(private readonly studentsService: StudentsService) {}
  

    @SensitiveOperation(SensitiveType.Set)
    @Post('set-student-name')
    setStudentName(@User() user: string) {
        return this.studentsService.setStudent(user);
    }
    // ...
}

Copy the code

You only need to import @sensitiveOperation (sensitiveType.set) before the interface. Isn’t it beautiful?

Call it again!

/ / ✅ using the command line calls curl -x POST http://127.0.0.1:3000/students/set-student-name - H'Content-Type: application/json' -d '{"user": "gdccwxx1"}'/ / = > {"name":"gdccwxx1"."id": 3."updateDate":"The 2021-09-17 T05:48:41. 685 z"."createDate":"The 2021-09-17 T05:48:41. 685 z"/ /} % ✅ open the browser to http://localhost:3000/sensitive/get-by-type?type=set
// => [{
//  id: 1,
//  type: "Set",
//  pathname: "/students/set-student-name",
//  parameters: { user: "gdccwxx1" },
//  results: { name: "gdccwxx1", id: 3, updateDate: "The 2021-09-17 T05:48:41. 685 z", createDate: "The 2021-09-17 T05:48:41. 685 z" },
//  createDate: "The 2021-09-17 T05:48:41. 719 z"
// }]
Copy the code

Bingo! That’s what we want!

A simple call to identify an interface without affecting the original business logic. Implementation of AOP call way. It is useful to adapt old code and write new business.

Plumbing Pipes

NestJs PipesConcepts and LinuxshellThe concept is very similar in that you do something with the output of the former.

Its application scenarios are also very wide, such as: data conversion, data verification…

Useful for data entry operations. Useful for complex data validation, such as form data.

Instead of complex input, we will use a simple data transformation to implement the prefix 🇨🇳 before the name

The new Pipes

New SRC/common/pipes/name. Pipes. The ts.

// src/common/pipes/name.pipes.ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable(a)export class TransformNamePipe implements PipeTransform {
  transform(name: string, metadata: ArgumentMetadata) {
    return ` 🇨 🇳${name.trim()}`; }}Copy the code

Like other NestJs, you need to override the built-in object. Pipes also requires an overloaded PipeTransform.

Use the pipe

Use Pipes in controller.

import { TransformNamePipe } from '.. /common/pipes/name.pipes';
// ...

@Controller('students')
export class StudentsController {
    constructor(private readonly studentsService: StudentsService) {}
  
    @Get('who-are-you')
    whoAreYou(@Query('name', TransformNamePipe) name: string) {
        return this.studentsService.ImStudent(name);
    }
    // ...
}

Copy the code

The second parameter to Query is Pipes, and data can also be processed continuously using multiple Pipes

Call interface

Rebrowser access

/ / ✅ http://localhost:3000/students/who-are-you? Name = GDCCWXX // => Im student 🇨🇳 GDCCWXXCopy the code

This is the simple version of the data conversion!

conclusion

This concludes the introductory chapter of NestJs.

A quick review of the tutorial:

  • throughnest cliNew project, new module
  • through@Get@PostImplement GET and POST requests
  • throughdtoLimit Limits parameters
  • The customdecoratorRealize parameter acquisition and settingmetadata
  • Call the built-inlogImplementing log normalization
  • usetypeormDatabase connection and basic operation, and joint table operation and query
  • useguardVerify parameters (extensible to login state)
  • useinterceptorImplement sensitive data landing
  • usepipesImplement data formatting

The author is also gradually exploring in NestJs. It not only includes simple data services, but also supports GraphQL, SSE, Microservice, etc. It is a very comprehensive framework.

BTW: This is my favorite Node framework for a long time

The full example can be found at Github.

If you like the article, give it a thumbs up. You can find more exciting content on my personal blog

Thank you for reading ~