I have to say that I am in love with NestJS to build backend applications, services, libraries, etc. using TypeScript. This framework has been the fastest-growing nodejs framework in 2019 due to its awesome features that, besides helps you to build your code, if you are thinking about to build medium or large services and you want it to be maintainable and scalable, also it give you a way to have your project well structured.
Part 1 - Project Creation
API Service
The service that I am going to create is a simple API service that contains 2 endpoints. Each endpoint returns a chart as image in different formats:
/image: Return an image as attachment.
/image-base64: Return an image as base64 string data.
To build the charts, I am going to use node-echarts library to get ECharts images in the backend world. ECharts is an open-sourced JavaScript visualization tool that in my opinion, it has great options to build tons of different chart types.
You can check its examples if you don't believe me 😛
Once all requirements are installed, I am going to use the NestJS CLI to create the project structure:
odin@asgard:~/Blog $ nest new echarts-api-service -p npm
After the execution, I have opened the ~/Blog/echarts-api-service folder in my Visual Studio Code and this is the project structure:
Here, you can see more about the NestJS project structure. I assume that you are familiar with this structure and I continue building the service.
Now, you can run the service using npm start and the service will respond a Hello World! string doing the following request: http://localhost:3000, as you can see in the following image (I'm using REST Client extension for Visual Studio Code).
Before continue, I am going to add a .prettierc file for code formatting using Prettier with the following options:
And also I like to add the hot reload option to check faster my code changes. To do this, and because I am using NestJS CLI, it is only necessary to change the start:dev script inside the package.json as nest start --watch --webpack. In NestJS Hot Reload documentation you can see more options.
Now, I am ready to modify the code to add the above endpoints /image and /image-base64.
'End of Part 1' You can check the project code in the part-1 tag
Part 2 - EchartsService creation
I am going to modify/delete all the not needed files to adapt the project in a better way.
Delete unnecessary files
In my case, It is not needed to use the app.controller.spec.ts. I delete it.
Modifying app.controller.ts file
This file is responsible for handling incoming requests and returning responses to the client and it is the site where I am going to create the endpoints to let NestJS know the code to use when receiving the requests.
The service is responsible for data storage and retrieval, in my case, the service will be the chart image creator.
Because I use ECharts, with node-echarts, I am going to create a new folder called echarts and inside another folder called entities. After this, move the app.service.ts to this new folder echarts and renamed it to echarts.service.ts.
'WARN' If you use Visual Studio Code, you will see that in every change, the editor adapts the code. Take care if not and adapt the code to be compiled.
After the changes, I am going to create the getImage method which has the code to create the ECharts image. But first, I am going to install the necessary npm dependencies to create the method:
npm i --save imagemin imagemin-pngquant imagemin-jpegtran https://github.com/telco2011/node-echarts.git
Once all the changes are done, your code won't compile due to the compiler does not find the Options name. This name will be a new class with two options, one to store the ECharts options to create the chart and other to store some properties to create the image that will contain the ECharts.
To do so, I am going to create the next 3 files:
src/echarts/entities/options.entity.ts
import{EChartOption}from'echarts';import{ImageOptions}from'./image-options.entity';/**
* Class to configure echarts options.
*/exportclassOptions{echartOptions:EChartOption;options?:ImageOptions;}
src/echarts/entities/image-options.entity.ts
/**
* Class to configure image options.
*/exportclassImageOptions{// Image widthwidth?:number;// Image heightheight?:number;// Download file namefilename?:string;}
'INFO' The file name structure is important in NestJS because it will be relevant for Part 4 of this post. It is IMPORTANT that the entity file name follows the next structure: [NAME].entity.ts
Finally the echarts.service.ts code is the following:
import{Injectable}from'@nestjs/common';import*asnode_echartsfrom'node-echarts';import{buffer}from'imagemin';importimageminPngquantfrom'imagemin-pngquant';import*asimageminJpegtranfrom'imagemin-jpegtran';import{DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT}from'./constants';import{Options}from'./entities/options.entity';@Injectable()exportclassEchartsService{/**
* Get the echarts as image.
*
* @param {Options} opt {@link Options}.
*/asyncgetImage(opt:Options):Promise<Buffer>{returnbuffer(node_echarts({option:opt.echartOptions,width:opt.options?.width||DEFAULT_IMAGE_WIDTH,height:opt.options?.height||DEFAULT_IMAGE_HEIGHT,}),{// plugins to compress the image to be sentplugins:[imageminJpegtran(),imageminPngquant({quality:[0.6,0.8],}),],},);}}
After these updates, the code compiles well and you can start the server again npm run start:dev.
'HINT' If you want, you can add/modify/delete all of these files without stopping the service and you will see how the Hot Reload feature works.
Now, I am going to connect the controller to the service to get all the functionality on. To do this, I am going to make some modifications in the app.controller.ts file to call the getImage method inside the service:
The code does not compile because of a dependency is not installed yet. This dependency is @yggdrasilts/volundr, part of our toolset. This library is a set of utilities for TypeScript developments. To compile, you only need to install it using npm i --save @yggdrasilts/volundr.
If you want to know more about it, take a look to its repository @yggdratilsts/volundr
If you see the code, it is very easy to understand it because the NestJS decorators are very explicit.
@post() decorator indicates that both endpoints are listening POST request.
I am using the @Header() decorator to set the response Headers.
And also I am using @Body() decorator to get the request body to be used inside the service.
At this point, if you start the service npm run start:dev you will have available the wanted endpoints:
'End of Part 2' You can check the project code in the part-2 tag
Part 3 - Logger, Validations and Pipes, Handling Errors and Modules
Now, the application is functional but before to finish, I would like to add, and talk, about some other features that NestJS provides to create a service.
Logger
In my opinion, every project should have a good logger implementation because it is the principal way to know what is happening in the flow. In this case, NestJS has a built-in text-based Logger that is enough for our service.
To use this logger, it is only necessary to instantiate the class and start using it. For example:
import{Controller,Get,Logger}from'@nestjs/common';@Controller()exportclassMyController{privatereadonlylogger=newLogger(MyController.name);@Get('log-data')getImage():void{this.logger.debug('Logging data with NestJS it'ssoeasy...');
}
}
The service that I am creating will use this logger for the app.controller.ts, body.validation.pipe.ts and http-exception.filter.ts. I am going to talk about these two last files in the following parts of the post.
If you want to know more about how to use the NestJS logger, you can go to its documentation. Also, in the following posts, I will talk about it.
Validations and Pipes
Like NestJS documentation says in its Validation section, it is best practice to validate the correctness of any data sent into a web application. For this reason, I am going to use the Object Schema Validation and creating a custom Pipe to do so.
First, I am going to adapt the project installing some needed dependencies and creating and modifying some files and folders.
Installing needed dependencies
npm i --save @hapi/joi
npm i --save-dev @types/hapi__joi
@hapi/joi lets you describe your data using a simple, intuitive, and readable language
This is a simple error handling that NestJS provides by default but I like to customize it and show more readable error using NestJS Exceptions Filters.
'End of Part 3.1' You can check the project code in the part-3.1 tag
Handling Errors
First, I am going to create my custom Exception Filter and activated it in the global-scope to manage all endpoint errors.
To do this purpose, I am going to create a new folder and file called src/exceptions/http-exception.filter.ts
import{ExceptionFilter,Catch,ArgumentsHost,HttpException,Logger}from'@nestjs/common';import{Request,Response}from'express';import{HttpHeaders,MimeType}from'@yggdrasilts/volundr';/**
* Filter to catch HttpException manipulating the response to get understandable response.
*/@Catch(HttpException)exportclassHttpExceptionFilterimplementsExceptionFilter{privatereadonlylogger=newLogger(HttpExceptionFilter.name);catch(exception:HttpException,host:ArgumentsHost){constctx=host.switchToHttp();constresponse=ctx.getResponse<Response>();constrequest=ctx.getRequest<Request>();conststatus=exception.getStatus();constmessage=exception.message;consterrorData={timestamp:newDate().toISOString(),message,details:{request:{method:request.method,query:request.query,body:request.body,},path:request.url,},};this.logger.error(`${JSON.stringify(errorData)}`);response.setHeader(HttpHeaders.CONTENT_TYPE,MimeType.APPLICATION.JSON);response.status(status).json(errorData);}}
This filter is similar to the one in NestJS Exception Filter section, but I have made some personal customizations to get more information in the error data.
After the file creation, it is needed to activate this filter in the global-scope. To do so, I am going to modify the main.js file:
'End of Part 3.2' You can check the project code in the part-3.2 tag
Modules
To finalize this part, I am going to use modules to follow the structure that NestJS propose because I think this structure it is very intuitive to follow having different modules with their own functionality.
To do this, I am going to create the src/echarts/echarts.module.ts file:
Finally, I am going to check if the service continues working as before executing npm run start:dev and checking the endpoints with the REST Client VS extension like at the end of Part 2.
'End of Part 3.3' You can check the project code in the part-3.3 tag
Part 4 - Swagger
Providing good documentation for every API is essential if we want to be used by other people. There are lots of alternatives to create this documentation but NestJS has an awesome module that use Swagger for this purpose.
Kamil Mysliwiec, NestJS creator, has written a great article talking about the new NestJS Swagger module features. I recommend you to read it, it is very interesting.
Once installed, we bootstrap the service modifying the main.ts file like the documentation sais:
import{NestFactory}from'@nestjs/core';import{SwaggerModule,DocumentBuilder}from'@nestjs/swagger';import{AppModule}from'./app.module';import{HttpExceptionFilter}from'./exceptions/http-exception.filter';asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.useGlobalFilters(newHttpExceptionFilter());constoptions=newDocumentBuilder().setTitle('Echarts API').setDescription('API to get charts, using echartsjs, as image file.').setExternalDoc('More about echartsjs','https://echarts.apache.org/en/index.html').setVersion('1.0').build();constdocument=SwaggerModule.createDocument(app,options);SwaggerModule.setup('api',app,document);awaitapp.listen(3000);}bootstrap();
Now we can start the service npm run start:dev and automatically we will have the Swagger documentation at http://localhost:3000/api:
Don't you think this is so easy? 😉
If you are read the above Kamil Mysliwiec's post, with the new NestJS Swagger plugin version, the NestJS CLI has the ability to search in all of our code and create the Swagger documentation automatically. To do so, the only thing that we need to modify is the nest-cli.json file adding the following lines:
Once added, we can run the service again npm run start:dev but in this case, we are going to see the following error:
This is because the service is using the EchartOption and the NestJS Swagger plugin is not able to parse. Also easy to solve. I am going to document this particular object by myself adding the following NestJS Swagger decorators to the echartOptions variable inside the src/echarts/entities/options.entity.ts file:
import{ApiProperty}from'@nestjs/swagger';import{EChartOption}from'echarts';import{ImageOptions}from'./image-options.entity';/**
* Class to configure echarts options.
*/exportclassOptions{@ApiProperty({type:'EChartOption',description:'Chart configuration.',externalDocs:{url:'https://echarts.apache.org/en/option.html'},})echartOptions:EChartOption;options?:ImageOptions;}
Now, we can run the service again npm run start:dev and the Swagger documentation will have changed and we will have the new properties:
As I said before, don't you think this is so easy?
End of Part 4. You can check the project code in the part-4 tag
To Sum Up
As I said at the begging of this post, I am in love with NestJS because it is an awesome framework that helps you to create backend services in an efficient, reliable and scalable way, using TypeScript, with which I am in love as well. In this post, it has been shown several parts of NestJS framework and how it helps you to build a backend service easily.
I hope you have enjoyed reading it and learn, improve or discover this great framework. In the following link, I leave you the Github repository where you can find the project code.
This project is the sample used in the Create an API service using nestframework post showing how to create an API service using NestJS framework that use a service to return an ECharts image chart.