最近上了一个新我的项目,这个客户治理一个宏大的工作和团队集群,而不同流程所实用的零碎也不太一样,比方salesforce,hubspots之类的。这次的新我的项目须要在另外两个平台之间做一些事件。目前只须要先封装其中之一的API,因而咱们选定应用NodeJS的框架Nest.js来实现这套API。

疾速启动

开启nestjs我的项目有3种便捷的形式。

应用nest自带的命令行工具
npm i -g @nestjs/clinest new project-name

即便不应用这种形式,也倡议在node全局装置命令行工具,这样能够不便的生成各种nestjs的模块,比方controller和service之类的。

间接应用starter我的项目

这里还有一个用于启动的样例我的项目,能够间接应用。

git clone https://github.com/nestjs/typescript-starter.git my-appcd my-appnpm installnpm 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中间接导入。

原博客连贯