Nest.js快速啟動API專案

語言: CN / TW / HK

最近上了一個新專案,這個客戶管理一個龐大的任務和團隊叢集,而不同流程所適用的系統也不太一樣,比如salesforce,hubspots之類的。這次的新專案需要在另外兩個平臺之間做一些事情。目前只需要先封裝其中之一的API,因此我們選定使用NodeJS的框架Nest.js來實現這套API。

快速啟動

開啟nestjs專案有3種便捷的方式。

使用nest自帶的命令列工具
npm i -g @nestjs/cli
nest new project-name

即使不使用這種方式,也建議在node全域性安裝命令列工具,這樣可以方便的生成各種nestjs的模組,比如controller和service之類的。

直接使用starter專案

這裡還有一個用於啟動的樣例專案,可以直接使用。

git clone http://github.com/nestjs/typescript-starter.git my-app
cd my-app
npm install
npm run start

而且這個專案還附帶了Typescript。不過要記得把之前的git資訊刪掉。

用npm安裝所需的包
npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata

直接安裝nestjs的core和common就可以,rxjs和reflext-metadata也是必需的。

這種方式比較乾淨,目錄什麼的需要自己建立。不過也可以使用命令列建立controller之類的,目錄會自動建立好。

總的來說,nestjs和其他語言API框架類似,很多東西可以自動生成或者無需編寫,所以約定的習慣非常重要,儘量不要建立一些“獨特”的結構,避免以後踩坑。

建立controller

建立好專案之後,我建立了一個controller,nest.js中controller可以通過修飾器直接提供路由,因此沒有一個route之類的檔案用於配置。

nest g controller projects

nest.js的目錄約定是按業務模組劃分,因為src目錄中會出現一個projects的目錄,該目錄下會生成一個projects.controller,以及附帶的單元測試。

建立service

接著建立service,用於封裝目標任務管理平臺關於Projects的API。

nest g service projects

建立controller和service都會自動加入到module裡,可以在每次生成後用git diff檢視一下生成了哪些程式碼,也好心理有數。

import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
import { Project } from 'src/interfaces/project.interface';
import { ProjectsService } from './projects.service';

@Controller('projects')
export class ProjectsController {
  constructor(private projectsService: ProjectsService) {}

  @Get()
  findAll(@Req() request: Request): Project[] {
    return this.projectsService.findAll();
  }
}
import { Injectable } from '@nestjs/common';
import { Project } from 'src/interfaces/project.interface';

@Injectable()
export class ProjectsService {
  findAll(): Project[] {
    return [];
  }
}

結構和命名

另外我特別想說明一下的是,雖然我為controller和service都使用了相同的名稱,但並不是說他們是一一對應的。很多專案只是為了分層而分,controller和service都是一一對應的,其實並不正確。分層是因為它們擁有不同的意義,只有明確語義,才能在思維的過程中更好的掌握程式碼,也可以更好的複用,層次起到的是一種認知轉換的作用。如果只是把底層的物件毫無變化的映射出來,那這個過程是毫無意義的。

這裡的service在nestjs中其實是provider的一種,而provider的意義則是從各種不同的地方提供資料或其他東西。我使用的ProjectsService意義在於封裝另一個API,所以這個projects來源於目標任務管理平臺的API名稱。而controller的名稱projects指的是我建立的API所要提供的資料是project,只不過它們在這裡確實是同一個東西,所以名稱也一樣。

假設,現在的業務邏輯是需要從目標任務管理平臺獲取projects,之後過濾出兩種不同特性的projects,一種叫task任務,需要分配給人員;另一種叫note記錄,只是標記一下。它們擁有不同的特性。那麼我就會建立2個controller,taskController和noteController,但是它們都呼叫ProjectsService去使用不同的過濾條件獲取資料。

HTTP請求

使用nest.js官方的Http模組HttpModule就可以向其他API發出請求。該模組其實也是封裝的Axios,所以用起來很方便。先安裝相關模組。

npm i --save @nestjs/axios axios

然後在app.module中引入。

import { HttpModule } from '@nestjs/axios';
...

@Module({
  imports: [HttpModule], // 引入Http模組
  controllers: [ProjectsController],
  providers: [ProjectsService],
})
export class AppModule {}
處理Axios物件

在service中可以這樣處理http請求的返回值。

import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { map, Observable } from 'rxjs';
import { Project } from 'src/interfaces/project.interface';
import { AxiosResponse } from 'axios';

@Injectable()
export class ProjectsService {
  constructor(
    private readonly httpService: HttpService
  ) {}
  findAll(): Observable<Project[]> {
    return this.httpService.get('http://localhost:3000/api-json').pipe(
      map((axiosResponse: AxiosResponse) => {
        return axiosResponse.data;
      })
    );
  }
}

因為Axios會包裹一層,用data作為統一的key,所以需要map出來。這裡的pipe和map方法都是來自rxjs。rxjs的核心就是Observable,使用響應式程式設計的方式,封裝了回撥和非同步方式的程式碼。因此這裡你看不到promise或者await/async之類的關鍵字。

配置

上面的請求,我只是使用了一個本地的json介面用於測試。要真正的呼叫目標平臺的API,還需要配置項,因為包括API token這種重要字串的一些值,需要放在環境變數中,不能直接放在程式碼被git提交。

所以我需要加上config的配置,安裝nest.js的config包,它封裝的其實是dotenv包,經常使用nodejs的話,應該會很熟悉這個包。

npm i --save @nestjs/config

同樣在app.module中引入config模組。

import { ConfigModule } from '@nestjs/config';
import configuration from './config/configuration';
...
  imports: [
    HttpModule,
    ConfigModule.forRoot({
      load: [configuration],
    })
  ],
...

這裡使用forRoot是因為該模組是單例模式的。而傳入的引數load可以把config物件載入。

引入的config/configuration檔案是新建立的配置物件。

export default () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  runn: {
    url: process.env.RUNN_API_URL,
    token: process.env.RUNN_API_TOKEN
  }
});

我配置了埠,以及API的URL和Token。

然後可能需要用到Typescript的介面,可以使用nest生成檔案。

nest g interface runn-config
export interface RunnConfig {
  url: string
  token: string
}

在service中就可以獲取這些配置項。

import { ConfigService } from '@nestjs/config';
import { RunnConfig } from 'src/interfaces/runn-config.interface';

...
constructor(
private readonly httpService: HttpService,
private configService: ConfigService
) {}

...
const config = this.configService.get<RunnConfig>('runn');

不要忘記在根目錄下建立.env檔案填入配置的值。另外按習慣,可以建立.env.sample檔案,只包含key,沒有值,類似模板,被git提交管理。而.env檔案則被gitignore。只在本地保留。在伺服器上需要另外生成一份。

全域性新增headers

URL使用了,但Token需要在headers中新增。在app.module中使用HttpModule的register方法就可以配置其中封裝的Axios。不過由於token來自config,所以需要用比較麻煩的registerAsync,注入config後,在useFactory中使用。

...
  imports: [
    HttpModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + configService.get('runn.token')
        }
      })
    }),
    ...
  ],
...

這樣我就建立了一個基本API框架,並請求了一個簡單的目標任務管理系統的API獲取資料。

API文件

另外,由於客戶需要了解和測試我們的API,所以需要一個postman的api集合。我準備使用Swagger,這是一個API文件的自動生成工具,它會根據框架中定義的API和引數,自動生成一個頁面,包含所有的API和引數說明,以及可以直接請求該API。當然這需要修飾器的輔助。先安裝nestjs的swagger包。

npm i --save @nestjs/swagger

然後在main.ts中引入並配置。


import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // swagger
  const config = new DocumentBuilder()
    .setTitle('My APIs')
    .setDescription('My APIs documents')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);
  // swagger end

  await app.listen(3000);
}
bootstrap();

然後就可以通過http://localhost:3000/api訪問該API文件頁面。不過目前所有API都會出現在default預設標籤下。官方示例中使用的addTag方法雖然可以新增一個標籤,但並不能指定某些API放入該標籤。我需要通過修飾器實現。

在controller的方法上使用@ApiTags('Project')即可,該方法會被放置在Project標籤下。

除了API文件的頁面形式。http://localhost:3000/api-json的JSON格式才是最重要的,可以使用它在Postman中直接匯入。

原部落格連線

本文來源:Nest.js快速啟動API專案