Introduction to the

Nestjs framework is based on express framework secondary development of a framework. The framework is based on TS development. Makes extensive use of the new TS feature decorator. To a certain extent, it improves the development efficiency and speed. It also improves code readability. Reduced coupling of repetitive functions

The purpose of this article was to document some of the configurations and pitfalls I developed using the NestJS framework in real projects. It involves database, Redis, permission verification, request interface logging, basic log configuration, interface parameter verification constraints

The API documentation

Warning! : Any module must be imported in the root module, otherwise it will not be valid!!!!

Module registration

app.module.ts

Import modules in the Import array

import { Module }        from "@nestjs/common";
import { DbModule }      from "src/db/db.module";
import { AppController } from "./app.controller";
import { AppService }    from "./app.service";
import { AuthModule }    from ".. /auth/auth.module";
import { LoginModule }   from ".. /login/login.module";
import { AccountModule } from ".. /account/account.module";
import { LogModule }     from ".. /log/log.module";
import { GameModule }    from ".. /game/game.module";
import { BoxModule }  from ".. /box/box.module";

@Module({
          imports    : [
            DbModule, AuthModule, LoginModule, AccountModule, LogModule, GameModule, BoxModule,
          ],
          controllers: [AppController],
          providers  : [AppService],
          exports    : [AppService]
        })
export class AppModule {}

Copy the code

1. Database

The database NestJS officially recommends using typeOrm as a library. The API of the library, which I will not describe, can be found on TypeORM Chinese Website (biunav.com)

Directory structure:

db.module.ts

This file is the module file of db module.

@ Global () : will be registered as a Global module, the module so that the module using the export export provider | service can be used in any module

import {Global, Module} from '@nestjs/common';
import {DbProvider}     from './dbProvider';
import {RedisModule}    from 'nestjs-redis';
import {CacheService}   from './cache.service';

@Global(a)@Module({
          imports  : [
            // This is the Redis connection configuration
            RedisModule.register({
                                   port    : 6379.host    : 'localhost'.password: ' '.db      : 1})].// CacheService encapsulates Redis operations
          providers: [DbProvider, CacheService],
          exports  : [DbProvider, CacheService]
        })
export class DbModule {}

Copy the code

db.provider.ts

This class is the class for db connection instances.

RepoMap is the instance table of the data table

import { ObjectStrKey } from ".. /types";
import { dbConfig }     from ".. /utiles/config";
import { Injectable }   from "@nestjs/common";
import {
  Connection,
  createConnection,
  EntityTarget
}                       from "typeorm";
import Account          from "./entitys/account.entity";
import Box              from "./entitys/box.entity";
import Game             from "./entitys/game.entity";
import GameCategory     from "./entitys/gameCategory.entity";

// Automatically implement Repository type derivation
type ObjectStrKey<T> = {
  [K in keyof T]: Repository<T[K]>;
}

export type tabs = {
  account: Account,
  box: Box,
  game: Game,
  gameCategory: GameCategory
}

@Injectable(a)export class DbProvider {
  protected connect: Connection;
  public RepoMap: ObjectStrKey<tabs> = { account: undefined.box: undefined.game: undefined.gameCategory: undefined };

  constructor() {
    // TypeOrm creates the connection
    createConnection({
                       type                 : "mysql".database             : dbConfig.database,
                       host                 : dbConfig.host,
                       port                 : dbConfig.port,
                       username             : dbConfig.user,
                       password             : dbConfig.password,
        			  // entity indicates the table instance, which is automatically scanned./entity Indicates ts or js at the end of entity in the directory
                       entities             : [`${__dirname}/entitys/*.entity{.ts,.js}`].synchronize          : true.maxQueryExecutionTime: 1000
                     }).then((connect: Connection) = > {
      this.connect = connect;
      // Complete the Repository table
      this.RepoMap = <ObjectStrKey<tabs>>{
        account     : this.getRepository(Account),
        box         : this.getRepository(Box),
        game        : this.getRepository(Game),
        gameCategory: this.getRepository(GameCategory)
      };
    });
  }

  protected getRepository(tabEntity: EntityTarget<unknown>) {
    return this.connect.getRepository(tabEntity); }}Copy the code

typeorm entityThe sample

See the documentation connection above for details

import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity(a)export default class Account {
  @PrimaryGeneratedColumn(a)id: number;
  @Column({ type: "varchar".nullable: true })
  avatar: string;
  @Column({ type: "varchar".nullable: true })
  userName: string;
  @Column({ type: "varchar".nullable: true })
  passWord: string;
  @Column({ type: "text".nullable: true })
  address: string;
  @Column({ type: "varchar".nullable: true.length: 11 })
  phone: string;
  @Column({ type: "int".default: 0 })
  rmbNum: number;
  @Column({ type: "int".default: 0 })
  coinCount: number;
  @Column({ type: "text".nullable: true })
  coupon: string;
  @Column({ type: "text" })
  likeBoxList: string;
  @Column({ type: "boolean".default: false })
  isDel: boolean;
}
Copy the code

redisOperating encapsulation

CacheService.ts

import { Injectable }   from '@nestjs/common';
import { RedisService } from 'nestjs-redis';

@Injectable(a)export class CacheService {
  private client: any;

  constructor(private redisService: RedisService) {
    this.getClient();
  }

  private async getClient() {
    this.client = await this.redisService.getClient();
  }

  / * * *@Description: encapsulates the redis cache setting method *@param Key {String} Key value *@param Value {String} Key value *@param Seconds {Number} Expiration time *@return: Promise<any>
   */
  public async set(key: string.value: any, seconds? :number) :Promise<any> {
    value = JSON.stringify(value);
    if (!this.client) {
      await this.getClient();
    }
    if(! seconds) {await this.client.set(key, value);
    }
    else {
      await this.client.set(key, value, 'EX', seconds); }}/ * * *@Description: sets the value * in the Redis cache@param key {String}
   */
  public async get(key: string) :Promise<any> {
    if (!this.client) {
      await this.getClient();
    }

    let data = await this.client.get(key);

    if (data) {
      return JSON.parse(data);
    }
    else {
      return null; }}/ * * *@DescriptionDelete redis cache data based on key *@param key {String}
   * @return: * /
  public async del(key: string) :Promise<any> {
    if (!this.client) {
      await this.getClient();
    }
    await this.client.del(key);
  }

  / * * *@Description: Clear the redis cache */
  public async flushall(): Promise<any> {
    if (!this.client) {
      await this.getClient();
    }

    await this.client.flushall(); }}Copy the code

indbandRedisAfter the configuration is complete, use the method

*.service.ts

import { Injectable }   from "@nestjs/common";
import { DbProvider }   from "src/db/dbProvider";
import { Repository }   from "typeorm";
import { BaseResponse } from ".. /.. /utiles";

@Injectable(a)export class AppService {
  // Since the DB module is globally registered, only import is required here. This syntax automatically creates this.db as well as declaring db first and assigning it from constructor
  // Cache is the injected Redis operation class
 constructor(protected readonly db: DbProvider, protected readonly cache: CacheService) {}

  getHello(): string {
    return "Hello World!";
  }

  async createGame() {
    // Fetching the RPO will automatically infer the type
    const gameRpo = this.db.RepoMap.game, categoryRpo = this.db.RepoMap.gameCategory;
    const res = this.cache.get(...) ; . Business code}}Copy the code

Log, token verification, and parameter verification

Logging, token verification, and parameter verification use the nestJS framework’s own guards, interceptors, and pipes

“Class-transformer “: “^0.3.2”,

“The class – the validator” : “^ 0.13.1”,

“Log4j “: “^1.0.0”,// log

“Dotenv “: “^8.2.0”,// env

Log recorder

configuration

import { configure, getLogger } from "log4js";
export const configLogger = async () => {
  configure({// Log rules
              appenders : {
                access: {
                  // dateFile Automatically creates logs 0:0:0 every day
                  type    : "dateFile".filename: "./log/access.log".pattern: "yyyy-MM-dd".level: "LOG".category: "normal"."maxLogSize": 2097152
                },
                debug : { type: "stdout"}},categories: {
                // In appenders is the rule to register. The value is key of appenders
                default: { appenders: ["debug"].level: "all" },
                onLine : { appenders: ["access"].level: "all"}}}); };let dotenv = require("dotenv");
// Parsing env files configures Settings to process.env
dotenv.config(".. /.. /env");
// Toggle loggers based on environment variables
export const logger = getLogger(process.env.isDebug ? "default" : "onLine");
Copy the code

Routing request logging

Create log module

log.interceptor.ts

import {
  BadGatewayException, CallHandler, ExecutionContext, Injectable, NestInterceptor
}                                 from "@nestjs/common";
import { Observable, throwError } from "rxjs";
import { getTime, logger }        from ".. /.. /utiles";
import { catchError, tap }        from "rxjs/operators";
import { IncomingMessage }        from "http";

@Injectable(a)export class LogInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // Switch the context to the HTTP request
    const ctx = context.switchToHttp();
    // Get the request
    const req = ctx.getRequest<IncomingMessage>();
    // Parse the parameters
    const msg = {
      url   : req.url,
      method: req.method,
      body  : req["body"].query : req["query"].params: req["params"]}; logger.mark(`${getTime()}| | request `.JSON.stringify(msg));
    const now = Date.now();
    return next
      .handle()
      .pipe(
        // called after interface processing
        tap(res= > {
          logger.mark(`${getTime()}| | response `, res, ` |The ${Date.now() - now}ms`);
        }),
        // Catch an interface exception
        catchError((err) = > {
          logger.error(`${getTime()}Abnormal | | `, err);
          return throwError(Object.assign(newBadGatewayException(), err)); })); }}Copy the code

Parameter calibration

Nest comes with the ValidationPipe pipe

configuration

main.ts

import { NestFactory }          from "@nestjs/core";
import { AppModule }            from "./domain/app/app.module";
import { ValidationPipe }       from "@nestjs/common";
import { configLogger, logger } from "./utiles";
import { LogInterceptor }       from "./domain/log/log.interceptor";


async function bootstrap() {
  await configLogger();
  // Use the custom logger to log
  const app = await NestFactory.create(AppModule, { logger });
  // Use global pipes. Then all interfaces will be constrained
  app.useGlobalPipes(new ValidationPipe());
  // Use global interceptor to intercept interface requests and responses
  app.useGlobalInterceptors(new LogInterceptor());
  await app.listen(3000);
}

bootstrap();

Copy the code

practice

Vali.ts

import { IsArray, IsNotEmpty, IsNotEmptyObject } from "class-validator";
export class CreateBoxReq {
  @IsNotEmpty({ message: "Name cannot be empty" })
  name: string;
  @IsNotEmpty({ message: "Description cannot be empty." })
  description: string;
  priceType: PriceType;
  @IsArray({ message: "Contains items cannot be empty" })
  includeMaterials: boxItem[];
  cover: string;
  statue: BoxStatue;
  isSpike: boolean;
  hot: number;
}
Copy the code

*.controller.ts

import { Body, Controller, Get, Post, UseGuards } from "@nestjs/common";
import { Auth }                                   from ".. /auth/auth.service";
import { CreateBoxReq, UpdateReq }                from "./dio";
import { BoxService }                             from "./box.service";
import { AppService }                             from ".. /app/app.service";
import { BoxStatue }                              from ".. /.. /utiles/constant";

@Controller("box")
export class BoxController {
  // Automatically inject service
  constructor(protected readonly service: BoxService, protected readonly commService: AppService) {}

  @Post("createBox")
  // Automatic token verification is implemented below
  @UseGuards(Auth)
  // info automatically verifies the parameters when requested, and returns an error with a message if they fail
  createBox(@Body() info: CreateBoxReq) {
    return this.service.create(info); }}Copy the code

Interface Token Authentication

Module directory

auth.service.ts

import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Observable }                                from "rxjs";
import { CacheService }                              from ".. /.. /db/cache.service";

@Injectable(a)export class Auth implements CanActivate {
  // Inject the Redis dependency
  constructor(protected readonly cache: CacheService) {}

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    // Get the token from the request header
    const { token } = context.switchToHttp().getRequest().headers;
    if(! token || token ==="") {
      return false;
    }
    / / determine
    return this.cache.get(token); }}Copy the code