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:
- use
guards
和decorators
Realize data verification - through
interceptors
和decorators
Realize sensitive operation entry - The custom
pipes
Data conversion
The full example can be found at Github.
The guard Guards
在 Before the request arrives at the business logic
It 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 Pipes
Concepts and Linuxshell
The 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:
- through
nest cli
New project, new module - through
@Get
和@Post
Implement GET and POST requests - through
dto
Limit Limits parameters - The custom
decorator
Realize parameter acquisition and settingmetadata
- Call the built-in
log
Implementing log normalization - use
typeorm
Database connection and basic operation, and joint table operation and query - use
guard
Verify parameters (extensible to login state) - use
interceptor
Implement sensitive data landing - use
pipes
Implement 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 ~