I recently worked on a new project. This customer manages a huge cluster of tasks and teams, and the systems applicable to different processes are also different, such as salesforce, hubspots and the like. This new project requires doing something between the other two platforms. Currently, we only need to encapsulate one of the APIs first, so we chose to use the NodeJS framework Nest.js to implement this set of APIs.
Quick Start
There are three convenient ways to start a nestjs project.
Use the command line tool that comes with nest
npm i -g @nestjs/cli nest new project-name
Even if you don’t use this method, it is recommended to install the command line tool globally in node, so that you can easily generate various nestjs modules, such as controller and service.
Use the starter project directly
There is also a sample project for getting started, which you can use out of the box.
git clone https: //gi thub.com /nestjs/ typescript-starter.git my-app cd my-app npm install npm run start
And this project also comes with Typescript. But remember to delete the previous git information.
Install required packages with npm
npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata
Just install core and common of nestjs directly, rxjs and reflext-metadata are also required.
This method is relatively clean, and you need to create directories yourself. However, you can also use the command line to create a controller or the like, and the directory will be created automatically.
In general, nestjs is similar to other language API frameworks. Many things can be automatically generated or do not need to be written, so the convention is very important. Try not to create some “unique” structures to avoid pitfalls in the future.
Create controller
After creating the project, I created a controller. In nest.js, the controller can directly provide routes through decorators, so there is no file such as route for configuration.
nest g controller projects
The directory convention of nest.js is divided by business modules, because there will be a projects directory in the src directory, and a projects.controller and accompanying unit tests will be generated in this directory.
Create service
Then create a service to encapsulate the API of the target task management platform about Projects.
nest g service projects
When you create a controller and service, they will be automatically added to the module. You can use git diff to check what code was generated after each generation, so you can have a good idea.
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 []; } }
Structure and naming
In addition, what I particularly want to explain is that although I use the same name for both controller and service, it does not mean that they correspond one to one. Many projects are divided just for the purpose of layering. Controllers and services have a one-to-one correspondence, which is actually not correct. Layers are because they have different meanings. Only with clear semantics can we better grasp the code in the thinking process and reuse it better. Layers play a role in cognitive transformation. If you just map the underlying objects unchanged, then this process is meaningless.
The service here is actually a type of provider in nestjs, and the meaning of provider is to provide data or other things from various places. The meaning of the ProjectsService I use is to encapsulate another API, so this projects comes from the API name of the target task management platform. The name of the controller, projects, refers to the fact that the data provided by the API I created is project, but they are indeed the same thing here, so the names are also the same.
Assume that the current business logic needs to obtain projects from the target task management platform, and then filter out two projects with different characteristics, one is called task task, which needs to be assigned to personnel; the other is called note record, which is just marked. They have different properties. Then I will create 2 controllers, taskController and noteController, but they both call ProjectsService to obtain data using different filter conditions.
HTTP request
HttpModule
You can make requests to other APIs using the official Http module of nest.js. This module is actually encapsulated Axios, so it is very convenient to use. Install the relevant modules first.
npm i --save @nestjs /axios axios
Then introduce it in app.module.
import { HttpModule } from '@nestjs/axios' ; ... @Module({ imports: [HttpModule], // Introduce Http module controllers: [ProjectsController], providers: [ProjectsService], }) export class AppModule {}
Handle Axios objects
The return value of the http request can be handled in the service like this.
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 ; }) ); } }
Because Axios will wrap a layer and use data as the unified key, it needs to be mapped out. The pipe and map methods here are both from rxjs. The core of rxjs is Observable, which uses reactive programming to encapsulate callback and asynchronous code. So you can’t see keywords like promise or await/async here.
Configuration
For the above request, I just used a local json interface for testing. To actually call the API of the target platform, configuration items are also required, because some values including important strings such as API token need to be placed in environment variables and cannot be directly placed in the code to be submitted by git.
So I need to add the config configuration and install the config package of nest.js. It actually encapsulates the dotenv package. If you often use nodejs, you should be very familiar with this package.
npm i --save @nestjs /config
Also introduce the config module in app.module.
import { ConfigModule } from '@nestjs/config' ; import configuration from './config/configuration' ; ... imports: [ HttpModule, ConfigModule.forRoot({ load: [configuration], }) ], ...
forRoot is used here because the module is in singleton mode. The parameters passed in load
can load the config object.
The imported config/configuration
file is a newly created configuration object.
export default () => ({ port : parseInt(process .env .PORT , 10) || 3000, runn: { url: process .env .RUNN_API_URL , token: process .env .RUNN_API_TOKEN } });
I configured the port, as well as the URL and Token of the API.
Then you may need to use the Typescript interface, and you can use nest to generate files.
nest g interface runn- config
export interface RunnConfig { url: string token: string }
These configuration items can be obtained in the 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' ) ;
Don’t forget to create a file in the root directory .env
to fill in the configured values. In addition, according to custom, you can create a .env.sample file, which only contains key and no value. It is similar to a template and is submitted and managed by git. The .env file is gitignore. Only retained locally. Another copy needs to be generated on the server.
Add headers globally
The URL is used, but the Token needs to be added in headers. Use the register method of HttpModule in app.module to configure the Axios encapsulated in it. However, since the token comes from config, you need to use registerAsync, which is more troublesome. After injecting config, use it in useFactory.
... imports: [ HttpModule .registerAsync({ imports: [ ConfigModule ], inject: [ ConfigService ], useFactory: (configService: ConfigService ) => ({ headers: { 'Content-Type' : 'application/json' , Authorization : 'Bearer ' + configService.get( 'runn.token' ) } }) }), ... ], ...
In this way, I created a basic API framework and requested the API of a simple target task management system to obtain data.
API documentation
In addition, since customers need to understand and test our API, a postman API collection is needed. I plan to use Swagger, which is an automatic generation tool for API documents. It will automatically generate a page based on the API and parameters defined in the framework, including all API and parameter descriptions, and can directly request the API. Of course this requires the help of modifiers. First install the swagger package of nestjs.
npm i --save @nestjs /swagger
Then main.ts
import and configure it in .
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 ();
You can then http://localhost:3000/api
access the API documentation page. However, currently all APIs will appear under the default label. Although the addTag method used in the official example can add a tag, it cannot specify certain APIs to put the tag. I need to achieve this through decorators.
Just use it on the controller method @ApiTags('Project')
, which will be placed under the Project tag.
In addition to the page form of API documentation. http://localhost:3000/api-json
The JSON format is the most important, you can use it to import directly in Postman.