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
这个类依赖于Engine
和Tire
,构造器不只须要把依赖赋值到当前类内部属性上还须要把依赖实例化。假设,有不少种类的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
至关于Car
和Engine
、Tire
之间的中转站,Car
不须要本身去实例化一个Engine
或者Tire
,Car
和Engine
、Tire
之间也就没有了强耦合的关系。编程
从上面例子看出,在使用 IoC 以前,Car
须要Engine
或者Tire
时须要本身主动去建立Engine
或者Tire
,此时对Engine
或者Tire
的建立和使用的控制权都在Car
手上。json
在使用 IoC 以后,Car
和Engine
或者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 提供了一个模块化结构,用于将同一领域内的代码组织成单独的模块。模块化的做用就是能够清晰地组织你的应用,并使用外部库扩展应用。
Module
把controller
、service
和pipe
等打包成内聚的功能块,每一个模块聚焦于一个特性区域、业务领域、工做流或通用工具。
在 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 {}
复制代码
每一个属于这个模块的controller
、service
等都须要在这个模块中注册,若是须要引入其余模块或者第三方模块,须要将它注册到imports
,经过exports
能够将相应的service
、module
等共享出去。
面向切面编程(Aspect Oriented Programming,简称AOP)主要是针对业务处理过程当中的切面进行提取,在某个步骤和阶段进行一些操做,从而达到 DRY(Don't Repeat Yourself) 的目的。AOP 对 OOP 来讲,是一种补充,好比能够在某一切面中对全局的 Log、错误进行处理,这种一刀切的方式,也就意味着,AOP 的处理方式相对比较粗粒度。
在 Nestjs 中,AOP 分为下面几个部分(按顺序排列):
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 和前端路由中的路由守卫同样,主要肯定请求是否应该由路由处理程序处理。经过守卫能够知道将要执行的上下文信息,因此和 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 能够给每个须要执行的函数绑定,拦截器将在该函数执行前或者执行后运行。能够转换函数执行后返回的结果,扩展基本函数行为等。
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))
}
}
复制代码
Pipe 是具备 @Injectable()
装饰器的类,并实现了 PipeTransform
接口。一般 pipe 用来将输入数据转换为所需的输出或者处理验证。
下面就是一个ValidationPipe
,配合class-validator
和 class-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 负责处理整个应用程序中的全部抛出的异常,也是 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(Data Transfer Object), 是一组须要跨进程或网络边界传输的聚合数据的简单容器。它不该该包含业务逻辑,并将其行为限制为诸如内部一致性检查和基本验证之类的活动。
在 Nestjs 中,可使用 TypeScript 接口或简单的类来完成。配合 class-validator
和class-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 是"对象-关系映射"(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();
}
}
复制代码