Nest.js 基础入门

安装

# 如果是 node.js 10 之后的版本,需要加 --ignore-engines 来忽略一些不兼容库的警告
npm i -g @nestjs/cli --ignore-engines
# 或
yarn global add @nestjs/cli --ignore-engines

入门文档

小技巧

异步方法优化

减少不必要的 async/await 包裹。示例代码如下:

function test() {
  const deferred = {
    promise: undefined,
    resolve: undefined
  };
  deferred.promise = new Promise((resolve) => {
    deferred.resolve = resolve;
  });
  setTimeout(() => {
    deferred.resolve('hello world');
  }, 1000);
  return deferred.promise;
}
 
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
 
  // 以下两种写法都能运行,推荐使用第一种
  @Get('/test')
  getTest(): Promise<string> {
    return test();
  }
 
  @Get('/test2')
  async getTest2(): Promise<string> {
    return await test();
  }
}

同理,除了在 Controller 中,在 Model、 Service 等其他地方内层方法均可以进行优化,因为外层调用的时候已经带上了 await。同时,还需要注意各个方法的返回类型,养成良好习惯。

使用 Fastify 框架

该部分没有文档,只有一个示例项目: https://github.com/nestjs/nest/tree/master/sample/10-fastify

yarn remove @nestjs/platform-express
yarn remove @types/express
yarn add @nestjs/platform-fastify

修改 main.ts 文件:

import { NestFactory } from '@nestjs/core';
// 新增引用
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
 
async function bootstrap() {
  // 原有代码:
  // const app = await NestFactory.create(AppModule);
  // 替换代码:
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000);
  console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();

Open-API(Swagger)

目前只有英文文档,没有中文文档。地址:https://docs.nestjs.com/openapi/introduction

配合 Fastify 使用:

yarn add @nestjs/swagger
yarn add fastify-swagger
# [Nest] 7253   - 2020/07/08 下午4:42:59   [PackageLoader] The "fastify-swagger" package is missing. Please, make sure to install this library ($ npm install fastify-swagger) to take advantage of SwaggerModule. +37ms
# 如果出现类似报错,使用2.x版本重试
yarn add fastify-swagger@^2.6.0

示例代码:

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  const options = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .addBearerAuth()
    .build();
  // 生成的 JSON 格式文档,可以导出静态化
  const document = SwaggerModule.createDocument(app, options);
  // 注入, 访问 http://localhost:3000/api 可以访问
  SwaggerModule.setup('api', app, document);
 
  await app.listen(3000);
  console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();

Module 代码可以参考 Express Swagger 的示例项目: https://github.com/nestjs/nest/tree/master/sample/11-swagger

生成 Open-API.json 文件示例代码:

import { resolve } from 'path';
import { writeFileSync } from 'fs';
 
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = (await NestFactory.create) < NestFastifyApplication > (AppModule, new FastifyAdapter());
  const options = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .addBearerAuth()
    .build();
  // 生成的 JSON 格式文档,可以导出静态化
  const document = SwaggerModule.createDocument(app, options);
  writeFileSync(resolve(__dirname, '../api.json'), JSON.stringify(document, null, 2), { encoding: 'utf8' });
}
bootstrap();

生成脚本示例

写一个 Bash 脚本:

#!/usr/bin/env bash
 
# Generate api.json
mv src/main.ts src/main.ts.bak
cp src/doc.ts src/main.ts
nest start
rm -f src/main.ts
mv src/main.ts.bak src/main.ts

对应的 TS 脚本:

import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { writeFileSync } from 'fs-extra';
import { resolve } from 'path';
import { AppModule } from './modules/app.module';
 
async function bootstrap(): Promise<void> {
  const app = await NestFactory.create(AppModule);
 
  const builder = new DocumentBuilder()
    .setTitle('Title')
    .setDescription('Desc')
    .setVersion('1.0.0')
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, builder, {
    deepScanRoutes: true
  });
 
  writeFileSync(resolve(__dirname, '../../api.json'), JSON.stringify(document, null, 2), {
    encoding: 'utf-8'
  });
  process.exit(0);
}
 
// eslint-disable-next-line @typescript-eslint/no-floating-promises
bootstrap();

E2E Testing

依然没有找到文档,参考一个示例的测试源码吧: https://github.com/nestjs/nest/blob/master/integration/hello-world/e2e/fastify-adapter.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { AppModule } from './../src/app.module';
// 新增引用
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { expect } from 'chai';
 
describe('AppController (e2e)', () => {
  let app: NestFastifyApplication;
 
  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule]
    }).compile();
 
    // 修改 app 创建
    app = moduleFixture.createNestApplication < NestFastifyApplication > new FastifyAdapter();
 
    await app.init();
  });
 
  it('/ (GET)', () => {
    // return request(app.getHttpServer())
    //   .get('/')
    //   .expect(200)
    //   .expect('Hello World!');
 
    // 改用 inject 方式,不用 supertest
    return app
      .inject({
        method: 'GET',
        url: '/'
      })
      .then(({ payload }) => expect(payload).to.be.eql('Hello World!'));
  });
});

Logger

npm i --save nestjs-pino
npm i --save-dev pino-pretty

main.ts 入口文件引入:

import { Logger } from 'nestjs-pino';
 
const app = await NestFactory.create(MyModule, { logger: false });
app.useLogger(app.get(Logger));

app.module.ts 文件引入:

import { LoggerModule } from 'nestjs-pino';
 
@Module({
  imports: [LoggerModule.forRoot()],
  controllers: [AppController],
  providers: [MyService]
})
class MyModule {}

Controller 中使用示例:

import { Logger } from 'nestjs-pino';
 
@Controller()
export class AppController {
  constructor(
    private readonly myService: MyService,
    private readonly logger: Logger
  ) {}
 
  @Get()
  getHello(): string {
    // pass message
    this.logger.log('getHello()');
 
    // also we can pass context
    this.logger.log('getHello()', AppController.name);
 
    return `Hello ${this.myService.getWorld()}`;
  }
}

或者使用 PinoLogger (推荐):

// my.service.ts
import { PinoLogger, InjectPinoLogger } from 'nestjs-pino';
 
@Injectable()
export class MyService {
  // regular injecting
  constructor(private readonly logger: PinoLogger) {}
 
  // regular injecting and set context
  constructor(private readonly logger: PinoLogger) {
    logger.setContext(MyService.name);
  }
 
  // inject and set context via `InjectPinoLogger`
  constructor(@InjectPinoLogger(MyService.name) private readonly logger: PinoLogger) {}
 
  getWorld(...params: any[]) {
    this.logger.info('getWorld(%o)', params);
    return 'World!';
  }
}

启动脚本修改:

nest start --watch | pino-pretty

Graphql

依赖于apollo-server

TBD.

The End