万万万万万万没想到会来到第十一篇,第十一篇写Nest和Nodejs游戏框架

Fastify.js

高效的服务器意味着更低的基础设施成本、更好的负载响应能力和用户满意度。 在不牺牲安全验证和便捷开发的前提下,如何知道服务器正在处理尽可能多的请求,又如何有效地处理服务器资源?

Fastify 是一个 web 开发框架,其设计灵感来自 Hapi 和 Express,致力于以最少的开销和强大的插件结构提供最佳的开发体验。据我们所知,它是这个领域里速度最快的 web 框架之一。

Fastify 已经实现的主要功能及原理:

  • 高性能: 据我们所知,Fastify 是这一领域中最快的 web 框架之一,另外,取决于代码的复杂性,Fastify 最多可以处理每秒 3 万次的请求。
  • 可扩展: Fastify 通过其提供的钩子(hook)、插件和装饰器(decorator)提供完整的可扩展性。
  • 基于 Schema: 即使这不是强制性的,我们仍建议使用 JSON Schema 来做路由(route)验证及输出内容的序列化,Fastify 在内部将 schema 编译为高效的函数并执行。
  • 日志: 日志是非常重要且代价高昂的。我们选择了最好的日志记录程序来尽量消除这一成本,这就是 Pino!
  • 对开发人员友好: 框架的使用很友好,帮助开发人员处理日常工作,并且不牺牲性能和安全性。
  • 支持 TypeScript: 我们努力维护一个 TypeScript 类型声明文件,以便支持不断成长的 TypeScript 社区

安装

npm i fastify --save

创建服务

// ESM
import Fastify from 'fastify'
const fastify = Fastify({
  logger: true
})
// CommonJs
const fastify = require('fastify')({
  logger: true
})

fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
})

const start = async () => {
  try {
    await fastify.listen(3000)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

序列化json

Fastify 对 JSON 提供了优异的支持,极大地优化了解析 JSON body 与序列化 JSON 输出的过程。 在 schema 的选项中设置 response 的值,能够加快 JSON 的序列化 (没错,这很慢!)

const opts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  }
}

fastify.get('/', opts, async (request, reply) => {
  return { hello: 'world' }
})

一旦指明了 schema,序列化的速度就能达到原先的 2-3 倍。这么做同时也保护了潜在的敏感数据不被泄露,因为 Fastify 仅对 schema 里出现的数据进行序列化

content-type

Fastify 原生只支持 'application/json''text/plain' content type。默认的字符集是 utf-8。如果你需要支持其他的 content type,你需要使用 addContentTypeParser API。默认的 JSON 或者纯文本解析器也可以被更改或删除。

注:假如你决定用 Content-Type 自定义 content type,UTF-8 便不再是默认的字符集了。请确保如下包含该字符集:text/html; charset=utf-8

和其他的 API 一样,addContentTypeParser 被封装在定义它的作用域中了。这就意味着如果你定义在了根作用域中,那么就是全局可用,如果你定义在一个插件中,那么它只能在那个作用域和子作用域中可用

日志

日志默认关闭,你可以在创建 Fastify 实例时传入 { logger: true } 或者 { logger: { level: 'info' } } 选项来开启它。要注意的是,日志无法在运行时启用。为此,我们使用了 abstract-logging

Fastify 专注于性能,因此使用了 pino 作为日志工具。默认的日志级别为 'info'

开启日志

const fastify = require('fastify')({
  logger: true
})

fastify.get('/', options, function (request, reply) {
  request.log.info('Some info about the current request')
  reply.send({ hello: 'world' })
})

你还可以提供自定义的日志实例。将实例传入,取代配置选项即可。提供的示例必须实现 Pino 的接口,换句话说,便是拥有下列方法: infoerrordebugfatalwarntracechild

const log = require('pino')({ level: 'info' })
const fastify = require('fastify')({ logger: log })

log.info('does not have request information')

fastify.get('/', function (request, reply) {
  request.log.info('includes request information, but is the same logger instance as `log`')
  reply.send({ hello: 'world' })
})

Pino 支持低开销的日志修订,以隐藏特定内容。 举例来说,出于安全方面的考虑,我们也许想在 HTTP header 的日志中隐藏 Authorization 这一个 header

const fastify = Fastify({
  logger: {
    stream: stream,
    redact: ['req.headers.authorization'],
    level: 'info',
    serializers: {
      req (request) {
        return {
          method: request.method,
          url: request.url,
          headers: request.headers,
          hostname: request.hostname,
          remoteAddress: request.ip,
          remotePort: request.socket.remotePort
        }
      }
    }
  }
})

graphQL

mercurius

https://github.com/mercurius-js/mercurius

thinkjs

feathersjs

创建feathers项目

npm create feathers@pre feathers-chat

启动项目

npm run compile
npm run migrate
npm start

使用

import type { Application, Id, NullableId, Params } from '@feathersjs/feathers'

class MyService {
  async find(params: Params) {}
  async get(id: Id, params: Params) {}
  async create(data: any, params: Params) {}
  async update(id: NullableId, data: any, params: Params) {}
  async patch(id: NullableId, data: any, params: Params) {}
  async remove(id: NullableId, params: Params) {}
  async setup(path: string, app: Application) {}
  async teardown(path: string, app: Application) {}
}

Nestjs

https://segmentfault.com/a/1190000018153359

nest之于javascript就像spring boot之于java,nest可以使用typescrip或者JavaScript开发,默认使用express作为底层服务框架

nest基于typescript编写并且结合了OOP(面向对象编程)、FP(函数式编程)、FRP(函数式响应编程)

熟悉spring或者angular的同学可以快速上手Nestjs,因为它大量借鉴了Spring和Angular中的思想和概念。nest 的核心思想是提供一个层与层之间直接耦合度极小、抽象化较高的架构体系。

安装nest

npm i -g @nestjs/cli

检查安装是否成功

nest -h

创建nest项目

nest  new nest-demo

进入项目,npm run start

//nest常用指令
nest new []//创建项目
nest -h//帮助
nest g co [名称]//创建控制器
nest g s [名称]//创建服务
nest g mi [名称]//创建中间件
nest g pi [名称]//创建管道
nest g mo [名称]//创建模块
nest g gu [名称]//创建守卫

依赖注入

依赖注入是面向对象中控制反转最常见的实现方式,主要降低代码的耦合度,

实例

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');
    }
}

在nestjs中,通过@injectable装饰器向IoC容器注册

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

控制器

控制器负责处理传入的请求和向客户端返回响应。每个控制器有多个路由,每个路由能执行不同的操作

通过命令行创建控制器

$ nest g co cats

实例

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

@Controller('cats')
export class CatsController {
    @Post
    create(): string {
        return 'this is a cat';
    }
    @Get
    findAll(): string {
        return 'this return all cats';
    }
}

Nest还提供其他端点装饰器@Put()、@Delete()、@Patch()、

状态码

自定义响应头

可以使用 @header() 修饰器或类库特有的响应对象,

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}

重定向

可以使用 @Redirect()装饰器或特定于库的响应对象(并直接调用 res.redirect())。

@Redirect() 带有必需的 url参数和可选的 statusCode参数。 如果省略,则 statusCode 默认为 302

@Redirect('https://nestjs.com', 301)

动态路由

当您需要接受动态数据作为请求的一部分时,

模块化

nest把controller、service、pipe等打包成内部的功能块,每个模块聚焦一个特性区域、业务领域、工作流等。

在nest中通过@Module装饰器声明一个模块,每个nest程序至少有一个模块,即根模块,根模块是Nest开始安排应用程序树的地方

@module()装饰器接受哦一个描述模块属性的对象

provider 
controller
imports 
exports

把模块到处就能在其他任意模块中重复使用,模块导出时可以导出他们的内部提供者,也可以再导出自己导入的模块

全局模块

当你在很多地方需要导入同一模块时,可以将模块定义为全局模块。一旦定义,他们到处可用。

@Global装饰器使模块注册为全局模块。全局模块只注册一次,最好在根模块或者核心模块注册

实例

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

@Global()
@Module({
   controllers: [CatsController],
   provider: [CatsService],
   exports: [CatsService],
})
export class CatModule {}

提供者

Providers 是 Nest 的一个基本概念。许多基本的 Nest 类可能被视为 provider - service,repository, factory, helper 等等。

装饰器和注解

@

自定义装饰器

面向切面编程

面向切面编程是针对业务处理过程中的切面进行提取,在某个步骤和阶段进行一些操作。面向切面编程是对面向对象编程的一种补充。

在nest中,面向切面编程主要分为下面几个部分:中间件、守卫、拦截器、管道、过滤器

洋葱模型

中间件

nest的中间件和express的语言,可以直接使用express的中间件

管道

管道有两种类型:

将输入数据转化为所需的数据输出,或者对输入数据进行验证,验证成功则继续传递,否则抛出异常。即转换管道和验证管道。管道也是具有@Injecttable装饰器的类

nest自带5个开箱即用的管道,从@nestjs/common包中导出,ValidationPipe、ParseIntPipe、ParseBoolPipe、ParseArrayPipe、ParseUUIDPipe。

Pipe 是具有 @Injectable() 装饰器的类,并实现了 PipeTransform 接口。

实例

验证管道

Nest 与 class-validator 配合得很好。class-validator库允许您使用基于装饰器的验证。装饰器的功能非常强大,尤其是与 Nest 的 Pipe 功能相结合使用时,因为我们可以通过访问 metatype 信息做很多事情,

转换管道

管道一般作为全局pipe使用

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

假设我们没有这层 pipe,那在 controller 中就会进行参数校验,这样就会打破单一职责的原则。有了这一层 pipe 帮助我们校验参数,有效地降低了类的复杂度,提高了可读性和可维护性。

守卫

守卫与前端(vue)中的路由守卫一样,主要确定请求是否由该路由程序处理,通过守卫可以知道上下文的信息,所以与中间件相比,守卫可以确切知道在next之后要执行什么

守卫在中间件之后执行,在拦截器和管道之前

在控制器中绑定守卫

守卫可以是全局范围或者控制范围内的,使用@UserGuards()装饰器设置一个控制范围的守卫,这个装饰器可以传递单个或多个守卫,用逗号隔开

import { UseGuards } from '@nestjs/common'
@Controller('cats')
@UseGuards(RolesGuard)
export  default CatsControllers {}

全局守卫用于整个应用程序, 每个控制器和每个路由处理程序。全局守卫

为了设置一个全局守卫,使用Nest应用程序实例的 useGlobalGuards() 方法:

const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());

拦截器

拦截器可以:

在函数执行之前/之后绑定额外的逻辑

转换从函数返回的结果

转换从函数抛出的异常

扩展基本函数行为

根据所选条件完全重写函数

实例

异常处理/过滤器

内置的 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,
      });
  }
}

而 Interceptor 则负责对成功请求结果进行包装:

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

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(rawData => {
          return {
            data: rawData,
            status: 0,
            message: '请求成功',
          }
        }
      )
    )
  }
}

同样 Interceptor 和 Exception Filter 需要把它定义在全局范围内:

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);
}

微服务

安装包

npm i --save @nestjs/microservices

创建微服务

Redis

GraphQL

在nest中开发GraphQL有两种方式,一种是代码先行,一种是架构先行

安装包

npm i @nestjs/graphql graphql-tools graphql apollo-server-express

@nestjs/graphql是对apollo server的封装

数据量较少时可以将schema和resolver写在一个文件内,数据量较多时最好写在不同的js/ts文件中

定义模型schema

import {Field,Int,ObjectType} from '@nestjs/graphql';
//也可以从其他模型文件中引入schema
import { Post } from './post.graphql'

@ObjectType()
export class Author {
  @Field(type =>Int)
  id: number;

  @Field({ nullable: true})
  firstName?: String;
  
  @Field({ nullable: true})
  lastName?: String;
  
  @Field(type => [Post])
  posts: Post[];
}

//post.graphql.ts
import {Field,Int,ObjectType} from '@nestjs/graphql';

@ObjectType()
export class Post {
    @Field(type => Int)
    id: number;

    @Field()
    title: string;

    @Field(type =>Int,{nullable:true})
    votes?: number;
}

定义resolver

import {
  Resolver,
  Query,
  Parent,
  ResolveField,
  Args,
  Int,
} from '@nestjs/graphql';
import { Author } from './graphql/author.graphql';
import { Post } from './graphql/post.graphql';

@Resolver(() => Author)
export class AuthorsResolver {
  constructor(
  private authorsService: AuthorsService,
  private postsService: PostsService) {}

  // @Query 表示创建Query 操作类型
  // @Args 表示传入的参数
  @Query(() => Author)
  async author(@Args('id', { type: () => Int }) id: number): Promise<any> {
    // 这里注释掉的是启用 `service` 对数据库进行访问
    // return this.authorsService.findOneById(id);
    return {
      id,
      firstName: 'name',
      lastName: 'mase',
    };
  }
  
  // @ResolveField 表示下面装饰的方法与父类型(在当前示例中为Author类型)相关联
  @ResolveField()
  async posts(@Parent() author: Author): Promise<any> {
    const { id } = author;
    return [
      {
        id: 4,
        title: 'hello',
        votes: 2412,
      },
    ];
  }
}

在module文件中引入

import { Module } from '@nestjs/common';
import { AuthorsResolver } from './authors.resolver'

@Module({
  providers: [AuthorsResolver],
})
export class AuthorModule {}

在主文件中引入module

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { AuthorModule } from './author/author.module'
import { join } from 'path';

@Module({
  imports: [
    ...
    GraphQLModule.forRoot({
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'), // 最后生成的`Schema 文件,不可修改`
    }),
    ConfigModule.forRoot({
      load: [configuration],
    }),
    AuthorModule
  ],
})

Websocket

安装包

npm i --save @nestjs/websockets @nestjs/platform-socket.io
npm i --save-dev @types/socket.io

JWT认证

通过用户认证可以判断该访问角色的合法性和权限。通常认证要么基于 Session,要么基于 Token。这里就以基于 Token 的 JWT(JSON Web Token) 方式进行用户认证。

$ npm install --save @nestjs/passport passport @nestjs/jwt passport-jwt

创建jwt.strategy.ts,用来验证 token,当 token 有效时,允许进一步处理请求,否则返回401(Unanthorized)

模版引擎

在 Nestjs 中,可以使用 hbs 作为模板渲染引擎:

$ npm install --save hbs

main.ts中,我们告诉 express,static文件夹用来存储静态文件,views中含了模板文件:

import { NestFactory } from '@nestjs/core'
import { NestExpressApplication } from '@nestjs/platform-express'
import { join } from 'path'

import { AppModule } from './app.module'
import config from './config'
import { Logger } from './shared/utils/logger'

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

  app.setGlobalPrefix('api/v1')

  app.useStaticAssets(join(__dirname, '..', 'static'))
  app.setBaseViewsDir(join(__dirname, '..', 'views'))
  app.setViewEngine('hbs')

  await app.listen(config.port, config.hostName, () => {
    Logger.log(
      `Awesome-nest API server has been started on http://${config.hostName}:${config.port}`,
    )
  })
}

views下新建一个catsPage.hbs的文件,假设,我们需要在里面填充的数据结构是这样:

{
  cats: [
    {
      id: 1,
      name: 'yyy',
      age: 12,
      breed: 'black cats'
    }
  ],
  title: 'Cats List',
}

此时,可以这样写模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
    <style>
        .table .default-td {
            width: 200px;
        }

        .table tbody>tr:nth-child(2n-1) {
            background-color: rgb(219, 212, 212);
        }

        .table tbody>tr:nth-child(2n) {
            background-color: rgb(172, 162, 162);
        }
    </style>
</head>
<body>
<p>{{ title }}</p>
<table class="table">
    <thead>
    <tr>
        <td class="id default-td">id</td>
        <td class="name default-td">name</td>
        <td class="age default-td">age</td>
        <td class="breed default-td">breed</td>
    </tr>
    </thead>
    <tbody>
    {{#each cats}}
        <tr>
            <td>{{id}}</td>
            <td>{{name}}</td>
            <td>{{age}}</td>
            <td>{{breed}}</td>
        </tr>
    {{/each}}
    </tbody>
</table>
</body>
</html>

http请求

Nestjs 中对Axios进行了封装,并把它作为 HttpService 内置到HttpModule中。HttpService返回的类型和 Angular 的 HttpClient Module一样,都是observables,所以可以使用 rxjs 中的操作符处理各种异步操作。

import { Global, HttpModule, Module } from '@nestjs/common'

import { LunarCalendarService } from './services/lunar-calendar/lunar-calendar.service'

@Global()
@Module({
  imports: [HttpModule],
  providers: [LunarCalendarService],
  exports: [HttpModule, LunarCalendarService],
})
export class SharedModule {}

ORM

通过ORM可以使用面向对象编程的方式操作关系型数据库。Java中通常会有DAO(data access object,数据访问对象)层,DAO包含了各种数据库的操作方法,通过DAO对数据进行相关的操作。DAO的主要作用是分离数据层与业务层,避免业务层与数据层耦合。

在nestjs中,使用typeORM作为DAO层,支持MySQL、MariaDB、MongoDB、NoSQL、SQLite、Postgres、CockroachDB、Oracle。

安装库

$ npm install --save @nestjs/typeorm

在typeORM中数据库的表对应的就是一个类,通过一个类创建实体,实体是一个映射到数据库表的类,通过@Entity来标记

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

@Entity()
export class User{
    @PrimaryGeneratedColumn()
    id: number;
    
    @Column()
    firstName: String;

    @Column()
    lastName: String;

    @Column()
    age: number;
}

通过@InjectRepository()修饰器注入对应的Repository,就可以在Repository对象上

进行数据库的操作。

import {Injectable} from '@nestjs/common';
import {InjectRepository } from '@nestjs/typeorm';
import {Rspository } from '@nestjs/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();
    }
}

Mongo

安装包

yarn add @nestjs/typeorm typeorm mongodb

在app.module.ts中配置数据库连接

@Module({
      imports: [
        TypeOrmModule.forRoot({
          type: 'mongodb',
          host: 'localhost',
          port: 27017,
          database: 'typeorm', // 数据库名
          entities: [join(__dirname, '**/entity/*.{ts,js}')], // 需要自动实体映射的文件路径匹配
          useNewUrlParser: true, // 使用新版mongo连接Url解析格式
          synchronize: true, // 自动同步数据库生成entity
        })
      ],

Mysql

安装包

npm install --save typeorm mysql

配置数据库连接

import { createConnection } from 'typeorm';

export const databaseProviders = [
  {
    provide: 'DATABASE_CONNECTION',
    useFactory: async () => await createConnection({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [
          __dirname + '/../**/*.entity{.ts,.js}',
      ],
      synchronize: true,
    }),
  },
];

数据库迁移

在持续交付项目中,项目会不断迭代上线,这时候就会出现数据库改动的问题,对一个投入使用的系统,通常会使用 migration 帮我们同步数据库。TypeORM 也自带了一个 CLI 工具帮助我们进行数据库的同步。

CRUD

对于一般的 CRUD 的操作,在 Nestjs 中可以使用@nestjsx/crud这个库来帮我们减少开发量。

首先安装相关依赖:

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

安全防范

对 JWT 的认证方式,因为没有 cookie,所以也就不存在 CSRF。如果你不是用的 JWT 认证方式,可以使用csurf这个库去解决这个安全问题。

对于 XSS,可以使用helmet去做安全防范。helmet 中有 12 个中间件,它们会设置一些安全相关的 HTTP 头。比如xssFilter就是用来做一些 XSS 相关的保护。

对于单 IP 大量请求的暴力攻击,可以用express-rate-limit来进行限速。

对于常见的跨域问题,Nestjs 提供了两种方式解决,一种通过app.enableCors()的方式启用跨域,另一种像下面一样,在 Nest 选项对象中启用。

最后,所有这些设置都是作为全局的中间件启用,最后main.ts中,和安全相关的设置如下:

import * as helmet from 'helmet'
import * as rateLimit from 'express-rate-limit'

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { cors: true })

  app.use(helmet())
  app.use(
    rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // limit each IP to 100 requests per windowMs
    }),
  )

  await app.listen(config.port, config.hostName, () => {
    Logger.log(
      `Awesome-nest API server has been started on http://${config.hostName}:${config.port}`,
    )
  })
}

Swagger

Nest提供对Swagger的支持,方便追踪和测试api。

安装npm包

$ npm install --save @nestjs/swagger swagger-ui-express

main.ts中构建文档:

const options = new DocumentBuilder()
    .setTitle('Awesome-nest')
    .setDescription('The Awesome-nest API Documents')
    .setBasePath('api/v1')
    .addBearerAuth()
    .setVersion('0.0.1')
    .build()

const document = SwaggerModule.createDocument(app, options)
SwaggerModule.setup('docs', app, document)

访问http://localhost:3300/docs就可以看到 swagger 文档的页面。

对于不同的 API 可以在 controller 中使用@ApiUseTags()进行分类,对于需要认证的 API,可以加上@ApiBearerAuth(),这样在 swagger 中填完 token 后,就可以直接测试 API:

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

  @Get('page')
  @Render('catsPage')
  getCatsPage(): Promise<any> {
    return this.catsService.getCats()
  }
}

热重载

在开发的时候,运行npm run start:dev的时候,是进行全量编译,如果项目比较大,全量编译耗时会比较长,这时候我们可以利用 webpack 来帮我们做增量编译,这样会大大增加开发效率。

首先,安装 webpack 相关依赖:

$ npm i --save-dev webpack webpack-cli webpack-node-externals ts-loader

在根目录下创建一个webpack.config.js

const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  entry: ['webpack/hot/poll?100', './src/main.ts'],
  watch: true,
  target: 'node',
  externals: [
    nodeExternals({
      whitelist: ['webpack/hot/poll?100'],
    }),
  ],
  module: {
    rules: [
      {
        test: /.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  mode: 'development',
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'server.js',
  },
};

在main.ts中启动HMR,

declare const module: any;

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  await app.listen(3000);

  if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }
}
bootstrap();

package.json中增加下面两个命令:

{
  "scripts": {
    "start": "node dist/server",
		"webpack": "webpack --config webpack.config.js"
  }
}

运行npm run webpack之后,webpack 开始监视文件,然后在另一个命令行窗口中运行npm start

Nodejs游戏框架pomelo

http://nextzeus.github.io/pomelo/#

如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github