Nest.js Quick Start API Project

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

HttpModuleYou 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 loadcan load the config object.

The imported config/configurationfile 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 .envto 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.tsimport 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/apiaccess 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-jsonThe JSON format is the most important, you can use it to import directly in Postman.