Source: awsome – nest

In Getting Started with NestJs (part 1), you’ve learned some important concepts about Nestjs. Now let’s start creating an application based on NestJs.

Nestjs, like Angular, provides CLI tools to help you initialize and develop your application.

$ npm install -g @nestjs/cli
$ nest new my-awesome-app
Copy the code

You get a directory structure like this:

After running NPM start, go to http://localhost:3000/ in your browser to see Hello World! .

The Controller and the Service

In Nestjs, all controllers and services are registered with the corresponding Module, like this:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Copy the code

In the MVC pattern, the controller gets data from the model. Correspondingly, in Nestjs, the controller handles the incoming request, calls the corresponding service to complete the business processing, and returns the response to the client.

You can usually create a controller using a CLI command:

$ nest g co cats
Copy the code

In this case, the CLI automatically generates a controller file and registers the controller with the corresponding module.

Unlike some other Node frameworks, Nestjs routing is not centrally managed, but is scattered across the controller, determined by the (optional) prefix declared in @controller() and any routes specified in the request decorator.

import { Controller, Get } from '@nestjs/common';

import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}@Get(':id')
  findOne(@Param('id') id: string) :string {
    return this.catsService.getCat(); }}Copy the code

The above code, through the Get request to http://localhost:3000/cats/1 will call the findOne method.

If you need to prefix all requests, you can set GlobalPrefix directly in 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();
Copy the code

In Nestjs, the controller acts as the caller to the service, distributing the corresponding request to the corresponding service for processing.

In controller, we notice that we inject a CatsService instance into the constructor to call the methods in the corresponding service. This is how dependency injection is done in Nestjs – constructor injection.

Service can be seen as a layer between the controller and model, in the service call DAO (in Nestjs is various ORM tools or its own DAO layer) to implement database access, processing and integration of data.

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

@Injectable(a)export class CatsService {
  getCat(id: string) :string {
    return `This action returns ${id} cats`; }}Copy the code

The above code defines a service with @Injectable(), so you can inject it into other controllers or services.

Dtos and Pipe

In NestJS, dtos define objects that send data over a network, usually with a class-Validator or a Class-Transformer.

import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  readonly age: number;

  @IsString()
  readonly breed: string;
}
Copy the code
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto } from './dto';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return 'This action adds a new cat'; }}Copy the code

The DTO is defined for the request body, and the parameter type is restricted in the DTO. If the type passed in the body is not the required type, an error will be reported.

The class-Validator in DTO also needs to cooperate with PIPE to complete the verification function:

import {
  PipeTransform,
  ArgumentMetadata,
  BadRequestException,
  Injectable,
} from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToClass } from 'class-transformer'
import * as _ from 'lodash'

@Injectable(a)export class ValidationPipe implements PipeTransform<any> {
  async transform(value, metadata: ArgumentMetadata) {
    const { metatype } = metadata
    if(! metatype || !this.toValidate(metatype)) {
      return value
    }
    const object = plainToClass(metatype, value)
    const errors = await validate(object)
    if (errors.length > 0) {
      const errorMessage = _.values(errors[0].constraints)[0]
      throw new BadRequestException(errorMessage)
    }
    return value
  }

  private toValidate(metatype): boolean {
    const types = [String.Boolean.Number.Array.Object]
    return! types.find(type= > metatype === type)}}Copy the code

The pipe builds the original type based on metadata and object instances, and validates it with validate.

This pipe is usually used as a global pipe:

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  app.setGlobalPrefix('api/v1');
  
  app.useGlobalPipes(new ValidationPipe());
  
  await app.listen(3000);
}
bootstrap();
Copy the code

Assuming we don’t have this pipe, then parameter validation will take place in the controller, breaking the single responsibility principle. Having this layer of PIPE helps us validate parameters, effectively reducing class complexity and improving readability and maintainability.

Interceptor and Exception filters

Writing the code here, we find that it returns a string, which is a little rough, and we need to wrap the correct and wrong responses. Suppose I want to return something like this:

{status: 1, message: 'request succeeded ', data: any} # Request failed {status: 1, message: string,}Copy the code

At this point, you can use the ideas of AOP to do just that. First, we need a global error slice layer to handle all exceptions; Second, if it is a successful request, the return result needs to be wrapped through a slice layer.

In Nestjs, when the request result is returned, the Interceptor will fire before the Exception Filter, so the Exception Filter will be the last chance to catch Exception. We use it as a slice layer for handling global errors.

import {
  Catch,
  ArgumentsHost,
  HttpException,
  ExceptionFilter,
  HttpStatus,
} from '@nestjs/common'

@Catch(a)export class ExceptionsFilter implements ExceptionFilter {
  async catch(exception, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse()
    const request = ctx.getRequest()

    let message = exception.message
    let isDeepestMessage = false
    while(! isDeepestMessage) { isDeepestMessage = ! message.message message = isDeepestMessage ? message : message.message }const errorResponse = {
      message: message || 'Request failed',
      status: 1,}const status = exception instanceof HttpException ? 
          exception.getStatus() :
    			HttpStatus.INTERNAL_SERVER_ERROR
    
    response.status(status)
    response.header('Content-Type'.'application/json; charset=utf-8')
    response.send(errorResponse)
  }
}
Copy the code

The Interceptor wraps the result of a successful request:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'

interface Response<T> {
  data: T
}

@Injectable(a)export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>> {
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(
      map(rawData= > {
          return {
            data: rawData,
            status: 0,
            message: 'Request successful',}}))}}Copy the code

In the same way, the Interceptor and Exception Filter need to define it globally:

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

  app.useGlobalFilters(new ExceptionsFilter());
  app.useGlobalInterceptors(new TransformInterceptor());
  app.useGlobalPipes(new ValidationPipe());

  await app.listen(3000);
}
Copy the code

TypeORM

TypeORM is equivalent to the DAO layer in Nestjs. It supports multiple databases such as PostgreSQL, SQLite and even MongoDB (NoSQL). MySQL > create database; MySQL > create database;

> CREATE DATABASE test
Copy the code

Then install TypeORm:

$ npm install --save @nestjs/typeorm typeorm mysql
Copy the code

Usually, we have multiple environments with different database configurations, so we need to create a config folder to place the different database configurations:

// index.ts
import * as _ from 'lodash'
import { resolve } from 'path'

import productionConfig from './prod.config'

const isProd = process.env.NODE_ENV === 'production'

let config = {
  port: 3000,
  hostName: 'localhost',

  orm: {
    type: 'mysql',
    host: 'localhost',
    port: 3310,
    username: 'root',
    password: '123456',
    database: 'test',
    entities: [resolve(`./**/*.entity.ts`)],
    migrations: ['migration/*.ts'],
    timezone: 'UTC',
    charset: 'utf8mb4',
    multipleStatements: true,
    dropSchema: false,
    synchronize: true,
    logging: true,}}if (isProd) {
  config = _.merge(config, productionConfig)
}

export { config }
export default config
Copy the code
// prod.config.ts
import { resolve } from 'path'

export default {
  port: 3210,

  orm: {
    type: 'mysql',
    host: 'localhost',
    port: 3312,
    username: 'root',
    password: '123456',
    database: 'test',
    entities: [resolve('./**/*.entity.js')],
    migrations: ['migration/*.ts'],
    dropSchema: false,
    synchronize: false,
    logging: false,}}Copy the code

You are advised not to enable orM synchronization in an online environment. If the type of an entity is different from that of the database, orM will drop and then add after you synchronize the database. This will cause data loss in the local test. The local test turns on the synchronize function so that the entity is automatically synchronized to the database after writing it.

Import TypeOrmModule in app.module.ts:

import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { CatsController } from './cats/cats.controller'
import { CatsService } from './cats/cats.service'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
import config from './config'

@Module({
  imports: [
    TypeOrmModule.forRoot(config.orm as TypeOrmModuleOptions),
  ],
  controllers: [AppController, CatsController],
  providers: [AppService, CatsService],
})
export class AppModule {}
Copy the code

We define a table named cat where id is an increment primary key:

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

@Entity('cat')
export class CatEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ length: 50 })
  name: string

  @Column()
  age: number

  @Column({ length: 100, nullable: true })
  breed: string
}
Copy the code

At this point, the Entity is synchronized to the database and the CAT table is visible in the test database.

When an entity is used by a module, it needs to be registered in the corresponding module. Use the forFeature() method to define which repositories should be registered in the current scope:

import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { CatsController } from './cats/cats.controller'
import { CatsService } from './cats/cats.service'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
import config from './config'
import { CatEntity } from './cats/cat.entity'

const ENTITIES = [
  CatEntity,
]

@Module({
  imports: [
    TypeOrmModule.forRoot(config.orm as TypeOrmModuleOptions),
    TypeOrmModule.forFeature([...ENTITIES]),
  ],
  controllers: [AppController, CatsController],
  providers: [AppService, CatsService],
})
export class AppModule {}
Copy the code

We can inject a CatRepository into the CatService using the @injectrepository () decorator:

import { Injectable } from '@nestjs/common' import { InjectRepository } from '@nestjs/typeorm' import { CatEntity } from  './cat.entity' import { Repository } from 'typeorm' @Injectable() export class CatsService { constructor( @InjectRepository(CatEntity) private readonly catRepository: Repository<CatEntity>, ) { } async getCat(id: number): Promise<CatEntity[]> { return await this.catRepository.find({ id }) } }Copy the code

At that time to request http://localhost:3000/api/v1/cats/1 this API, it will return the following results:

{
    "data": []."status": 0."message": "Request successful"
}
Copy the code

If you need to use more complex SQL statements in TypeORm, you can use createQueryBuilder to help you build:

this.catRepository
  .createQueryBuilder('cat')
  .Where('name ! = "" ')
  .andWhere('age > 2')
	.getMany()
Copy the code

If createQueryBuilder does not satisfy your requirements, you can write SQL statements directly using query:

this.catRepository.query(
  'select * from cat where name ! =? and age > ? ',
  [age],
)
Copy the code

Migration

In continuous delivery projects, where the project is iteratively coming online, database changes occur. For a working system, migration is often used to synchronize the database. TypeORM also comes with a CLI tool to help with database synchronization.

First create an ormconfig.json file locally:

{
  "type": "mysql"."host": "localhost"."port": 3310."username": "root"."password": "123456"."database": "test"."entities": ["./**/*.entity.ts"]."migrations": ["migrations/*.ts"]."cli": {
    "migrationsDir": "migrations"
  },
  "timezone": "UTC"."charset": "utf8mb4"."multipleStatements": true."dropSchema": false."synchronize": false."logging": true
}
Copy the code

The JSON file specifies the matching rules for the Entity and migration files, and the location of the migration file is configured in the CLI.

Run the following command to automatically generate the file 1563725408398-update-cat.ts under the migrations folder

$ ts-node node_modules/.bin/typeorm migration:create -n update-cat
Copy the code

1563725408398 in the file name is the timestamp of the generated file. This file will have up and down methods:

import {MigrationInterface, QueryRunner} from "typeorm";

export class updateCat1563725408398 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<any> {}public async down(queryRunner: QueryRunner): Promise<any> {}}Copy the code

Up must contain the code needed to perform migration. Down must restore any up changes. There is a QueryRunner object in up and down. Use this object to perform all database operations. For example, we write a false data in the cat table:

import {MigrationInterface, QueryRunner} from "typeorm";

export class updateCat1563725408398 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.query(`insert into cat (id, name, age, breed) values (2, 'test', 3, 'cat') `)}public async down(queryRunner: QueryRunner): Promise<any> {}}Copy the code

Json and run NPM run migration:run. The cat table will have a false data with id 2.

{
  "scripts": {
    "migration:run": "ts-node node_modules/.bin/typeorm migration:run",}}Copy the code

Note that the ormconfig.json file configuration is the local environment configuration, if you need to use the generated environment, you can write a new ormconfig-prod.json, Then run the migration name with –config ormconfig-prod.json.

One disadvantage of migration generated with TypeORm is that the SQL and the code are coupled together, and it is best if the SQL is a separate file and the migration script is a file, which makes it easier to run the SQL files directly in MySQL in special cases. Migrate: You can use db-migrate to manage migration scripts instead of TypeORm. Db-migrate will generate a JS script and two SQL files under the migration directory. One is down SQL.

For existing projects, if it is difficult to create entities from scratch based on the database, you can use the Typeorm-Model-Generator to automatically generate these entities. For example, run this command:

$typeorm - model - the generator - h 127.0.0.1-d arya -p 3310 -u root -x 123456 -e mysql -d test -o 'src/entities/' --noConfig true --cf param --ce pascal
Copy the code

The entity file cat.ts is generated under SRC /entities/.

import{BaseEntity,Column,Entity,Index,JoinColumn,JoinTable,ManyToMany,ManyToOne,OneToMany,OneToOne,PrimaryColumn,PrimaryGenera tedColumn,RelationId}from "typeorm";


@Entity("cat",{schema:"test", database:"test"})export class Cat {

    @PrimaryGeneratedColumn({
        type:"int", 
        name:"id"
        })
    id:number;
        

    @Column("varchar",{ 
        nullable:false,
        length:50,
        name:"name"
        })
    name:string;
        

    @Column("int",{ 
        nullable:false,
        name:"age"
        })
    age:number;
        

    @Column("varchar",{ 
        nullable:true,
        length:100,
        name:"breed"
        })
    breed:string | null;
        
}
Copy the code

The log

The official log solution is given, but here we refer to Nestify and use Log4JS for log processing. The main reason is that Log4JS classifies, divides, and dumps logs to facilitate log management.

There are nine levels of logs in log4JS:

export enum LoggerLevel {
  ALL = 'ALL',
  MARK = 'MARK',
  TRACE = 'TRACE',
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
  FATAL = 'FATAL',
  OFF = 'OFF',}Copy the code

The ALL and OFF levels are generally not used directly in business code. The remaining seven methods correspond to each Logger instance, that is, when these methods are called, the logs are rated.

For different log levels, the log is printed in log4JS in different colors, with the log output time and the corresponding module name:

Log4js.addLayout('Awesome-nest'.(logConfig: any) = > {
  return (logEvent: Log4js.LoggingEvent): string= > {
    let moduleName: string = ' '
    let position: string = ' '

    const messageList: string[] = []
    logEvent.data.forEach((value: any) = > {
      if (value instanceof ContextTrace) {
        moduleName = value.context
        if (value.lineNumber && value.columnNumber) {
          position = `${value.lineNumber}.${value.columnNumber}`
        }
        return
      }

      if (typeofvalue ! = ='string') {
        value = Util.inspect(value, false.3.true)
      }

      messageList.push(value)
    })

    const messageOutput: string = messageList.join(' ')
    const positionOutput: string = position ? ` [${position}] ` : ' '
    const typeOutput: string = ` [${ logConfig.type }] ${logEvent.pid.toString()}   - `
    const dateOutput: string = `${Moment(logEvent.startTime).format( 'YYYY-MM-DD HH:mm:ss', )}`
    const moduleOutput: string = moduleName
      ? ` [${moduleName}] `
      : '[LoggerService] '
    let levelOutput: string = ` [${logEvent.level}] ${messageOutput}`

    switch (logEvent.level.toString()) {
      case LoggerLevel.DEBUG:
        levelOutput = Chalk.green(levelOutput)
        break
      case LoggerLevel.INFO:
        levelOutput = Chalk.cyan(levelOutput)
        break
      case LoggerLevel.WARN:
        levelOutput = Chalk.yellow(levelOutput)
        break
      case LoggerLevel.ERROR:
        levelOutput = Chalk.red(levelOutput)
        break
      case LoggerLevel.FATAL:
        levelOutput = Chalk.hex('#DD4C35')(levelOutput)
        break
      default:
        levelOutput = Chalk.grey(levelOutput)
        break
    }

    return `${Chalk.green(typeOutput)}${dateOutput}    ${Chalk.yellow( moduleOutput, )}${levelOutput}${positionOutput}`}})Copy the code

In Log4JS, the issue of the log exit (i.e. where the log output goes) is resolved by the Appender:

Log4js.configure({
  appenders: {
    console: {
      type: 'stdout',
      layout: { type: 'Awesome-nest' },
    },
  },
  categories: {
    default: {
      appenders: ['console'],
      level: 'debug',}}})Copy the code

In config, logs of higher than debug level are output through the console.

Export a log class that exposes different levels of log methods in log4JS. The complete code is as follows:

import * as _ from 'lodash'
import * as Path from 'path'
import * as Log4js from 'log4js'
import * as Util from 'util'
import * as Moment from 'moment'
import * as StackTrace from 'stacktrace-js'
import Chalk from 'chalk'

export enum LoggerLevel {
  ALL = 'ALL',
  MARK = 'MARK',
  TRACE = 'TRACE',
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
  FATAL = 'FATAL',
  OFF = 'OFF',}export class ContextTrace {
  constructor(
    public readonly context: string.publicreadonly path? :string.publicreadonly lineNumber? :number.publicreadonly columnNumber? :number.) {}
}

Log4js.addLayout('Awesome-nest'.(logConfig: any) = > {
  return (logEvent: Log4js.LoggingEvent): string= > {
    let moduleName: string = ' '
    let position: string = ' '

    const messageList: string[] = []
    logEvent.data.forEach((value: any) = > {
      if (value instanceof ContextTrace) {
        moduleName = value.context
        if (value.lineNumber && value.columnNumber) {
          position = `${value.lineNumber}.${value.columnNumber}`
        }
        return
      }

      if (typeofvalue ! = ='string') {
        value = Util.inspect(value, false.3.true)
      }

      messageList.push(value)
    })

    const messageOutput: string = messageList.join(' ')
    const positionOutput: string = position ? ` [${position}] ` : ' '
    const typeOutput: string = ` [${ logConfig.type }] ${logEvent.pid.toString()}   - `
    const dateOutput: string = `${Moment(logEvent.startTime).format( 'YYYY-MM-DD HH:mm:ss', )}`
    const moduleOutput: string = moduleName
      ? ` [${moduleName}] `
      : '[LoggerService] '
    let levelOutput: string = ` [${logEvent.level}] ${messageOutput}`

    switch (logEvent.level.toString()) {
      case LoggerLevel.DEBUG:
        levelOutput = Chalk.green(levelOutput)
        break
      case LoggerLevel.INFO:
        levelOutput = Chalk.cyan(levelOutput)
        break
      case LoggerLevel.WARN:
        levelOutput = Chalk.yellow(levelOutput)
        break
      case LoggerLevel.ERROR:
        levelOutput = Chalk.red(levelOutput)
        break
      case LoggerLevel.FATAL:
        levelOutput = Chalk.hex('#DD4C35')(levelOutput)
        break
      default:
        levelOutput = Chalk.grey(levelOutput)
        break
    }

    return `${Chalk.green(typeOutput)}${dateOutput}    ${Chalk.yellow( moduleOutput, )}${levelOutput}${positionOutput}`
  }
})

Log4js.configure({
  appenders: {
    console: {
      type: 'stdout',
      layout: { type: 'Awesome-nest' },
    },
  },
  categories: {
    default: {
      appenders: ['console'],
      level: 'debug',}}})const logger = Log4js.getLogger()
logger.level = LoggerLevel.TRACE

export class Logger {
  statictrace(... args) { logger.trace(Logger.getStackTrace(), ... args) }staticdebug(... args) { logger.debug(Logger.getStackTrace(), ... args) }staticlog(... args) { logger.info(Logger.getStackTrace(), ... args) }staticinfo(... args) { logger.info(Logger.getStackTrace(), ... args) }staticwarn(... args) { logger.warn(Logger.getStackTrace(), ... args) }staticwarning(... args) { logger.warn(Logger.getStackTrace(), ... args) }staticerror(... args) { logger.error(Logger.getStackTrace(), ... args) }staticfatal(... args) { logger.fatal(Logger.getStackTrace(), ... args) }static getStackTrace(deep: number = 2): ContextTrace {
    const stackList: StackTrace.StackFrame[] = StackTrace.getSync()
    const stackInfo: StackTrace.StackFrame = stackList[deep]

    const lineNumber: number = stackInfo.lineNumber
    const columnNumber: number = stackInfo.columnNumber
    const fileName: string = stackInfo.fileName

    const extnameLength: number = Path.extname(fileName).length
    let basename: string = Path.basename(fileName)
    basename = basename.substr(0, basename.length - extnameLength)
    const context: string = _.upperFirst(_.camelCase(basename))

    return new ContextTrace(context, fileName, lineNumber, columnNumber)
  }
}
Copy the code

So where you need to output the log, just call it like this:

Logger.info(id)
Copy the code

We don’t want each request to have its own log, so we can use this log as middleware:

import { Logger } from '.. /.. /shared/utils/logger'

export function logger(req, res, next) {
  const statusCode = res.statusCode
  const logFormat = `${req.method} ${req.originalUrl} ip: ${req.ip} statusCode: ${statusCode}`

  next()

  if (statusCode >= 500) {
    Logger.error(logFormat)
  } else if (statusCode >= 400) {
    Logger.warn(logFormat)
  } else {
    Logger.log(logFormat)
  }
}
Copy the code

Register in main.ts:

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.setGlobalPrefix('api/v1')

  app.use(logger)
  app.useGlobalFilters(new ExceptionsFilter())
  app.useGlobalInterceptors(new TransformInterceptor())
  app.useGlobalPipes(new ValidationPipe())

  await app.listen(config.port, config.hostName)
}
Copy the code

ExceptionsFilter also logs the Exception caught in ExceptionsFilter:

export class ExceptionsFilter implements ExceptionFilter {
  async catch(exception, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse()
    const request = ctx.getRequest()

    Logger.error('exception'.JSON.stringify(exception))

    let message = exception.message
    let isDeepestMessage = false
    while(! isDeepestMessage) { isDeepestMessage = ! message.message message = isDeepestMessage ? message : message.message }const errorResponse = {
      message: message || 'Request failed',
      status: 1,}const status = exception instanceof HttpException ?
      exception.getStatus() :
      HttpStatus.INTERNAL_SERVER_ERROR

    Logger.error(
      `Catch http exception at ${request.method} ${request.url} ${status}`,
    )

    response.status(status)
    response.header('Content-Type'.'application/json; charset=utf-8')
    response.send(errorResponse)
  }
}
Copy the code

Such a basic logging system is almost complete. Of course, log4JS appenders also support the following:

  • DateFile: Logs are output to a file. The log file can be scrolled in a specific date mode. For example, the log file is output to default-2016-08-21.log today and default-2016-08-22.log tomorrow.

  • SMTP: Outputs logs to mails.

  • Mailgun: Outputs logs to the Mailgun through the Mailgun API.

  • LevelFilter You can pass the level filter.

  • You can see the full list here.

For example, the following configuration will output logs to a date-suffix file for 60 days:

Log4js.configure({
    appenders: {
      fileAppender: {
        type: 'DateFile',
        filename: './logs/prod.log',
        pattern: '-yyyy-MM-dd.log',
        alwaysIncludePattern: true,
        layout: { type: 'Flash' },
        daysToKeep: 60
      }
    },
    categories: {
      default: {
        appenders: ['fileAppender'],
        level: 'info'}}})Copy the code

CRUD

For general CRUD operations, the @nestJsx/CRUD library can be used in Nestjs to help reduce the amount of development.

First install the dependencies:

npm i @nestjsx/crud @nestjsx/crud-typeorm class-transformer class-validator --save
Copy the code

Then create a new dog.entity.ts:

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

@Entity('dog')
export class DogEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ length: 50 })
  name: string

  @Column()
  age: number

  @Column({ length: 100, nullable: true })
  breed: string
}
Copy the code

In dog.service.ts, just write the following lines:

import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm'

import { DogEntity } from './dog.entity'

@Injectable()
export class DogsService extends TypeOrmCrudService<DogEntity> {
  constructor(@InjectRepository(DogEntity) repo) {
    super(repo)
  }
}
Copy the code

In dog.controller.ts, use the @crud help to automatically generate the API:

import { Controller } from '@nestjs/common'
import { Crud, CrudController } from '@nestjsx/crud'

import { DogEntity } from './dog.entity'
import { DogsService } from './dogs.service'

@Crud({
  model: {
    type: DogEntity,
  },
})
@Controller('dogs')
export class DogsController implements CrudController<DogEntity> {
  constructor(public service: DogsService) {}}Copy the code

At this point, you can follow the API rules in the @NestJsx/CRUD documentation to request the corresponding CRUD action. For example, asking the GET API /v1/dogs will return an array of all dogs; Ask GET API /v1/dogs/1 to return dog with ID 1.

reference

Using the CLI

The migration

Log4js for Node.js

nestify