Nestjs 入門(一)

Nestjs 是 Node 漸進式框架,底層默認使用 express(能夠經過 Adapter 轉換到 fastify),可使用 express 或者 fastify 全部中間件,完美支持 TypeScript。熟悉 Spring 和 Angular 的同窗能夠很快上手 Nestjs,它大量借鑑了 Spring 和 Angular 中的設計思想。前端

在開始寫hello world以前,咱們先來看看 Nestjs 中比較重要的設計思想和概念。git

依賴注入

依賴注入(Dependency Injection,簡稱DI)是面向對象中控制反轉(Inversion of Control,簡稱 IoC)最多見的實現方式,主要用來下降代碼的耦合度。咱們用一個例子來講明什麼是控制反轉。github

假設你要造一輛車,你須要引擎和輪子:typescript

import { Engine } from './engine'
import { Tire } from './tire'

class Car {
  private engine;
  private wheel;
  
  constructor() {
    this.engine = new Engine();
    this.tire = new Tire();
  }
}
複製代碼

這時候 Car 這個類依賴於EngineTire,構造器不只須要把依賴賦值到當前類內部屬性上還須要把依賴實例化。假設,有不少種類的Car都用了Engine,這時候須要把Engine替換爲ElectricEngine,就會陷入牽一髮而動全身的尷尬。數據庫

那麼用 IoC 來改造一下:express

import { Engine } from './engine'
import { Tire } from './tire'

class Container {
  private constructorPool;

  constructor() {
    this.constructorPool = new Map();
  }

  register(name, constructor) {
    this.constructorPool.set(name, constructor);
  }

  get(name) {
    const target = this.constructorPool.get(name);
    return new target();
  }
}

const container = new Container();
container.bind('engine', Engine);
container.bind('tire', Tire);

class Car {
  private engine;
  private tire;
  
  constructor() {
    this.engine = container.get('engine');
    this.tire = container.get('tire');
  }
}
複製代碼

此時,container至關於CarEngineTire之間的中轉站,Car不須要本身去實例化一個Engine或者TireCarEngineTire之間也就沒有了強耦合的關係。編程

從上面例子看出,在使用 IoC 以前,Car須要Engine或者Tire時須要本身主動去建立Engine或者Tire,此時對Engine或者Tire的建立和使用的控制權都在Car手上。json

在使用 IoC 以後,CarEngine或者Tire之間的聯繫就切斷了,當Car須要Engine或者Tire時,IoC Container會主動建立這個對象給Car使用,此時Car獲取Engine或者Tire的行爲由主動獲取變成了被動獲取,控制權就顛倒過來。當Engine或者Tire有任何變更,Car不會受到影響,它們之間就完成了解耦。bootstrap

當咱們須要測試Car時,咱們不須要把Engine或者Tire所有new一遍來構造Car,只須要把 mock 的Engine或者Tire, 注入到 IoC 容器中就行。網絡

IoC 有不少實現,好比 Java 的 Spring ,PHP 的 Laravel ,前端的 Angular2+ 以及 Node 的 Nestjs等。

在 Nestjs 中,經過@Injectable裝飾器向 IoC 容器註冊:

import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}
複製代碼

在構造函數中注入CatsService的實例:

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}
複製代碼

CatsService做爲一個privider,須要在module中註冊,這樣在該module啓動時,會解析module中全部的依賴,當module銷燬時,provider也會一塊兒銷燬。

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class ApplicationModule {}
複製代碼

模塊化

Nestjs 提供了一個模塊化結構,用於將同一領域內的代碼組織成單獨的模塊。模塊化的做用就是能夠清晰地組織你的應用,並使用外部庫擴展應用。

Modulecontrollerservicepipe等打包成內聚的功能塊,每一個模塊聚焦於一個特性區域、業務領域、工做流或通用工具。

在 Nestjs 中經過@Module裝飾器聲明一個模塊,@Module接受一個描述模塊屬性的對象:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { CoreModule } from './core/core.module';

@Module({
  imports: [CoreModule],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}
複製代碼

每一個屬於這個模塊的controllerservice等都須要在這個模塊中註冊,若是須要引入其餘模塊或者第三方模塊,須要將它註冊到imports,經過exports能夠將相應的servicemodule等共享出去。

面向切面編程

面向切面編程(Aspect Oriented Programming,簡稱AOP)主要是針對業務處理過程當中的切面進行提取,在某個步驟和階段進行一些操做,從而達到 DRY(Don't Repeat Yourself) 的目的。AOP 對 OOP 來講,是一種補充,好比能夠在某一切面中對全局的 Log、錯誤進行處理,這種一刀切的方式,也就意味着,AOP 的處理方式相對比較粗粒度。

在 Nestjs 中,AOP 分爲下面幾個部分(按順序排列):

  • Middlewares
  • Guards
  • Interceptors (在流被操縱以前)
  • Pipes
  • Interceptors (在流被操縱以後)
  • Exception filters (若是發現任何異常)

Middlewares

Middleware 和 express 的中間件同樣,你能夠直接使用 express 中的中間件:

import * as helmet from 'helmet'

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule, {
    cors: true,
    logger: false,
  })

  app.use(helmet())

  await app.listen(config.port, config.hostName, () => {
    Logger.log(
      `Flash API server has been started on http://${config.hostName}:${config.port}`,
    )
  })
}
複製代碼

Guards

Guards 和前端路由中的路由守衛同樣,主要肯定請求是否應該由路由處理程序處理。經過守衛能夠知道將要執行的上下文信息,因此和 middleware 相比,守衛能夠確切知道將要執行什麼。

守衛在每一箇中間件以後執行的,但在攔截器和管道以前。

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

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);			// validateRequest 函數實現 Request 的驗證
  }
}
複製代碼

Interceptors

Interceptors 能夠給每個須要執行的函數綁定,攔截器將在該函數執行前或者執行後運行。能夠轉換函數執行後返回的結果,擴展基本函數行爲等。

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { getFormatResponse } from '../../shared/utils/response'

export interface Response<T> {
  data: T
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(map(getFormatResponse))
  }
}

複製代碼

Pipes

Pipe 是具備 @Injectable() 裝飾器的類,並實現了 PipeTransform 接口。一般 pipe 用來將輸入數據轉換爲所需的輸出或者處理驗證

下面就是一個ValidationPipe,配合class-validatorclass-transformer ,能夠更方便地對參數進行校驗。

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

@Injectable()
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) {
      throw new BadRequestException('Validation failed')
    }
    return value
  }

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

複製代碼

Exception filters

內置的 Exception filters 負責處理整個應用程序中的全部拋出的異常,也是 Nestjs 中在 response 前,最後能捕獲異常的機會。

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

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

    response
      .status(status)
      .json({
        statusCode: exception.getStatus(),
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}

複製代碼

DTO

數據訪問對象簡稱DTO(Data Transfer Object), 是一組須要跨進程或網絡邊界傳輸的聚合數據的簡單容器。它不該該包含業務邏輯,並將其行爲限制爲諸如內部一致性檢查和基本驗證之類的活動。

在 Nestjs 中,可使用 TypeScript 接口或簡單的類來完成。配合 class-validatorclass-transformer 能夠很方便地驗證前端傳過來的參數:

import { IsString, IsInt, MinLength, MaxLength } from "class-validator";
import { ApiModelProperty } from '@nestjs/swagger'

export class CreateCatDto {
  @ApiModelProperty()
  @IsString()
  @MinLength(10, {
    message: "Name is too short"
  })
  @MaxLength(50, {
    message: "Name is too long"
  })
  readonly name: string;
  
  @ApiModelProperty()
  @IsInt()
  readonly age: number;
  
  @ApiModelProperty()
  @IsString()
  readonly breed: string;
}

複製代碼
import { Controller, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto';

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

複製代碼

若是 Body 中的參數不符合要求,會直接報 Validation failed 錯誤。

ORM

ORM 是"對象-關係映射"(Object/Relational Mapping) 的縮寫,經過實例對象的語法,完成關係型數據庫的操做。經過 ORM 就能夠用面向對象編程的方式去操做關係型數據庫。

在 Java 中,一般會有 DAO(Data Access Object, 數據訪問對象)層,DAO 中包含了各類數據庫的操做方法。經過它的方法,對數據庫進行相關的操做。DAO 主要做用是分離業務層與數據層,避免業務層與數據層耦合。

在 Nestjs 中,能夠用 TypeORM 做爲你的 DAO 層,它支持 MySQL / MariaDB / Postgres / CockroachDB / SQLite / Microsoft SQL Server / Oracle / MongoDB / NoSQL。

在 typeORM 中數據庫的表對應的就是一個類,經過定義一個類來建立實體。實體(Entity)是一個映射到數據庫表(或使用 MongoDB 時的集合)的類,經過@Entity()來標記。

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

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    age: number;

}

複製代碼

上面代碼將建立如下數據庫表:

+-------------+--------------+----------------------------+
|                          user                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| firstName   | varchar(255) |                            |
| lastName    | varchar(255) |                            |
| isActive    | boolean      |                            |
+-------------+--------------+----------------------------+
複製代碼

使用 @InjectRepository() 修飾器注入 對應的Repository,就能夠在這個Repository對象上進行數據庫的一些操做。

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async findAll(): Promise<User[]> {
    return await this.userRepository.find();
  }
}
複製代碼

參考

Nestjs

淺析控制反轉

依賴注入和控制反轉的理解

從Express到Nestjs,談談Nestjs的設計思想和使用方法

相關文章
相關標籤/搜索