9

For you who are using Nestj as a project for the first time (three articles: writing api & uploading pictures)

1. Module

For example, we wrote before user controller (controller) and services (service) they in fact should be regarded as a whole, such as a place to introduce user related operations, direct introduction the User's module can, we now come to re-generate a module try Right.

nest g module modules/users
nest g controller modules/users
nest g service modules/users

image.png

image.png

2. Various request methods

To obtain the parameters of different request methods, you need to use nest provided by 060cd960295f71. For example, get requests such explicit parameter transfers need to be processed @Query

get
import { Controller, Get, Query} from '@nestjs/common';
// ...
@Get()
getList(@Query() query) {
    return query;
}

image.png

post
import { Controller, Post, Body } from '@nestjs/common';
// ...
@Post('create')
create(@Body() body) {
    return body
}

image.png

Line id
import { Controller, Get, Param } from '@nestjs/common';
// ...
@Get(':id')
getUser(@Param('id') id): string {
    return id;
}

image.png

3. Public path

It is necessary to set the prefix of the api path, we set it to /api/v1 , which is /share/src/main.ts .

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api/v1'); // 这里这里
  await app.listen(3000);
}
bootstrap();

image.png

Four. Decorator

In some cases, we may need to preprocess the parameters. For example, the api parameter of the request list must contain page and pageSize and the minimum is 1.

We decorator folder src file plus the paging.decorator.ts file.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const Paging = createParamDecorator(
    (data: string, ctx: ExecutionContext) => {
        const request = ctx.switchToHttp().getRequest();
        const query = request.query;
        
        if (!query.page || query.page < 1) {
            query.page = 1
        }

        if (!query.pageSize || query.pageSize < 1) {
            query.pageSize = 1
        }

        return query
    },
);

We modify the original getList method, replace @Query() query with @Paging() query and view the results;

    @Get()
    getList(@Paging() query) {
        return query;
    }

image.png

5. Pipelines

The pipeline converts the input data into the required data output, verifies the input data, if the verification is successful, continues to pass, and the verification fails, an exception is thrown. The pipeline can handle more extensive things than the decorator, such as the decorator mentioned above. It is for the parameters, while the pipeline is for the entire request.

We use a pipeline to verify page and pageSize. The new pipe folder under paging.pipe.ts . The contents of the file are as follows.

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class PagingPipe implements PipeTransform {
    transform(query: any, metadata: ArgumentMetadata) {
        if (metadata.type === 'query') {
            if (!query.page || query.page < 1) {
                query.page = 1
            }

            if (!query.pageSize || query.pageSize < 1) {
                query.pageSize = 1
            }
        }
        return query;
    }
}

Here you need to use metadata to determine which kind of requester is to do the processing, and his usage is as follows:

import { Controller, Get, Query, UsePipes } from '@nestjs/common';
@Controller('users')
export class UsersController {

    @Get()
    @UsePipes(new PagingPipe())
    getList(@Query() query) {
        return query;
    }
}
  1. Need to rely on UsePipes decorator.
  2. Act on the overall request, not for a certain parameter.
  3. Multiple pipelines can be set, for example, @UsePipes(new PagingPipe(), new PagingPipe2()) from left to right.

Six. Middleware

This is an old friend, I won't introduce it much, just introduce the configuration method, create a middleware folder in the src directory, the following is the global.middleware.ts as follows:

export default function (req, res, next) {
    console.log(`全局函数式: 进入`);
    next();
    console.log(`全局函数式: 退出`);
};
Global use

Use directly in main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import globalMiddleware from './middleware/global.middleware';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api/v1');
  app.use(globalMiddleware)
  await app.listen(3000);
}
bootstrap();
Partial use, only when the route is /users take effect

Create users.middleware.ts file with the following content:

import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class UsersMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    console.log('进入users中间件')
    next();
    console.log('走出users中间件')
  }
}

Make the following changes in the /share/src/app.module.ts

import { Module, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GitlabController } from './modules/gitlab/gitlab.controller';
import { GitlabService } from './modules/gitlab/gitlab.service';
import { UsersModule } from './modules/users/users.module';
import { UsersMiddleware } from './middleware/users.middleware';

@Module({
  imports: [UsersModule],
  controllers: [AppController, GitlabController],
  providers: [AppService, GitlabService],
})
export class AppModule {
  // 此处定义中间件
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(UsersMiddleware)
      .forRoutes('/users*');
  }
}
  1. If you main.ts , it can only be middleware in function mode.
  2. consumer can be spliced behind .apply .
  3. If .forRoutes('/users*'); written as .forRoutes('users*'); , an error will be reported.
  4. In forRoutes directly inside configuration forRoutes('*') is a global key intermediate, and without having to middleware function.

Seven. Guard

The guard is very similar to the middle key. It can be executed before the request is processed. The difference between the guard and the middle key is that the middleware calls next but does not know what to execute next, but the guard can know what to execute next, and the guard is generally It is used to verify permissions.

Next, we will use jwd to make a simple and easy to understand verification, so it is easy to understand, so the simplified version of 160cd960296410 on the official website.

Installation dependencies

yarn add @nestjs/jwt

Generate module

nest g module modules/auth
nest g controller modules/auth
nest g service modules/auth

In share/src/modules/auth/auth.controller.ts , configure the interface that generates the token.

import { Controller, Get, Response } from '@nestjs/common';
import { AuthService } from './auth.service'

@Controller('auth')
export class AuthController {
    constructor(
        private readonly authService: AuthService
    ) { }

    @Get()
    getToken(@Response() res) {
        return this.authService.getToken(res);
    }
}

Define jwt in /share/src/modules/auth/auth.module.ts , such as expiration time.

import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';

@Module({
  imports: [
    JwtModule.register({
      secret: 'secretKey',
      signOptions: { expiresIn: `${60 * 60 * 24 * 10}s` },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService],
  exports: [AuthService], // 这里需要注意, 因为后面会全局使用所以要导出一下
})
export class AuthModule { }

Define the method of token generation in /share/src/modules/auth/auth.service.ts

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(
        private readonly jwtService: JwtService
    ) { }
    getToken(res) {
        res.setHeader("token", this.jwtService.sign({
            id: 'dadscxciweneiu122',
            name: "金毛"
        }))
        res.send()
    }
}
  1. The jwtService above and it is better not to return it directly in the parameter, but to put it in the header.
  2. The object in sig is the data to be encrypted. We can put some user id in it.
  3. exports: [AuthService] is very important, because it will be used in the guard.
Guard configuration

/share/src/guard/token.guard.ts

import { CanActivate, ExecutionContext, Injectable, HttpException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { Inject } from '@nestjs/common';

@Injectable()
export class TokenGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    @Inject('AuthService') private readonly authService,
  ) { }
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    try {
      const user = this.authService.testToken(request)
      request.user = user;
      return true
    } catch (error) {
      throw new HttpException({
        status: 401,
        error: '身份验证失败',
      }, 401);
    }
  }
}
  1. The example here is that after we parse the token, we will look up the user information according to the userId in the token, and then assign the user information to the request, so that each operation can use this user information for verification and other operations.
  2. HttpException an error type. If the verification fails, we will report 401 directly.

To use guards globally, /share/src/app.module.ts adds the following changes:

import { APP_GUARD } from '@nestjs/core';
import { TokenGuard } from './guard/token.guard';
// ...
@Module({
  imports: [UsersModule, AuthModule],
  controllers: [AppController, GitlabController],
  providers: [AppService, GitlabService,
    {
      provide: APP_GUARD,
      useClass: TokenGuard,
    }]
})

8. Detect token

Of course, the generated token needs to be verified, which is the implementation of the testToken /share/src/modules/auth/auth.service.ts :

testToken(req) {
     const token = req.headers.token;
     return this.jwtService.verify(token)
     // 后续链接数据库后会查出user信息返回出去
}

Nine. No need to verify token for setting

Many APIs do not need to restrict the user to the login state, so we need to set a decorator that makes the request without verification. For example, the operation of obtaining the token does not need to verify the identity. The usage is as shown in the figure below.

image.png

/share/src/guard/noauth.ts

import { SetMetadata } from '@nestjs/common';

export const NoAuth = () => SetMetadata('no-auth', true);
  1. SetMetadata sets metadata, metadata is the data used to describe the data, which can be understood as explaining what the requested data is.
  2. 'no-auth' is set to true.

/share/src/guard/token.guard.ts the canActivate method which determines the increase

import { CanActivate, ExecutionContext, Injectable, HttpException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { Inject } from '@nestjs/common';

@Injectable()
export class TokenGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    @Inject('AuthService') private readonly authService,
  ) { }
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    // const headers = request.headers;
    const noAuth =
      this.reflector.get<boolean>('no-auth', context.getHandler());
    if (noAuth) {
      return true;
    }
    // 未授权
    try {
      const user = this.authService.testToken(request)
      request.user = user;
      return true
    } catch (error) {
      throw new HttpException({
        status: 401,
        error: '身份验证失败',
      }, 401);
    }
  }
}
  1. Take out no-auth reflection.
  2. context.getHandler() returns [Function: testToken] which is the method in the service we executed.
  3. You can also use context.getClass() return value is [class AuthController] , the controller where our current request is located.
  4. For insurance, it can be written as follows:

     const noAuth =
       this.reflector.get<boolean>('no-auth', context.getClass()) ||
       this.reflector.get<boolean>('no-auth', context.getHandler());

10. Picture upload & display

Browse pictures

Create a folder /share/public and put a picture in it.
Add the following code to the main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express'
import globalMiddleware from './middleware/global.middleware';

async function bootstrap() {
 // const app = await NestFactory.create(AppModule); // 这个改动了
 const app = await NestFactory.create<NestExpressApplication>(AppModule);
 app.setGlobalPrefix('api/v1');
 app.use(globalMiddleware);
 app.useStaticAssets('public', {
   prefix: '/static' // 一定不可以省略 '/'
 });
 await app.listen(3000);
}
bootstrap();

Let's look at the effect:
image.png

image.png

upload image

Get the file directly, and then store it in the form of a stream. I won’t talk about it here. Let’s directly demonstrate the use of a decorator to realize the function of receiving pictures and uploading.

import { Controller, BadRequestException, Post UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { join } from 'path';
import multer = require('multer');

@Controller('user')
export class UserController {
    @Post('img:import')
    @UseInterceptors(
        FileInterceptor('file', {
            storage: multer.diskStorage({
                destination: function (req, file, cb) {
                    // cb(null, join(process.cwd(), 'upload'));
                    cb(null, join(process.cwd(), 'public'));
                },
                filename: function (req, file, cb) {
                    const unique = `${Date.now()}${Math.round(Math.random() * 1e9)}`;
                    const imgPath = `${unique}.${file.mimetype.split('/')[1]}`;
                    cb(null, imgPath);
                },
            }),
            limits: {
                fileSize: 1024 * 1024,
            },
            fileFilter(req, file, cb) {
                if (file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png') {
                    throw new BadRequestException(`只支持jpg, png格式`);
                }
                cb(null, true);
            },
        }),
    )
    async coverImport(@UploadedFile() file) {
        return { url: `/static/${file.filename}` };
    }
}
  1. Set the storage path in destination
  2. filename Here is the name of the file, don't forget to add the suffix.
  3. limits is the size.
  4. fileFilter does some filtering operations and throws errors.
  5. Finally, the relative path is returned to the client.
  6. This way of writing is also not beautiful, so it is not necessary to use this way.

image.png

end.

The next article is typeorm operate the database. I will share many real cases that are not shown on the official website. Some practical problems caused me a lot of trouble at the time. I hope to make progress with you.


lulu_up
5.7k 声望6.9k 粉丝

自信自律, 终身学习, 创业者