Typed and validated REST API using NestJS

In short, NestJS is a Node.js framework for creating scalable server-side applications. It uses TypeScript, which is compiled to pure JavaScript during the build process. Under the hood, it makes use of Express as a library to handle HTTP calls. The overall architecture of NestJS is heavily inspired by Angular: you’ll find there plenty of decorators, modularity, the usage of services provided by dependency injection mechanism, etc.

If you want to find more about NestJS, I encourage you to read an article written by my colleague, Tomek. Once you do that, we can proceed straight to the code samples!

Built-in features

NestJS is known for providing many built-in solutions, which – with a bit of effort on the developer’s part – can provide us with a number of new functionalities. To be more precise, I have in mind:

  • Middlewares
  • Exception filters
  • Guards
  • Interceptors
  • Or even custom decorators!

But in this case, we will take a look at pipes – which, by the way, are classes with @Injectable() decorator.

Flow in the pipe 🚰

First things first: what are these pipes? To begin with, we need to specify two types of pipes mostly used in NestJS application:

  • transformation pipe which transforms input data to the desired form,
  • validation pipe that checks whether input data is valid, and in case it isn’t, the pipe throws an exception.

In fact, we can call pipes simple middleware-functions that take some input data and return the desired output. Then, the transformed and validated data invokes the route handler in the controller. But how does it relate to a typed and validated REST API? Let me show you a couple of examples.

Let's assume we're creating a backend for a simple blog. We will focus on the POST request on the `/article` endpoint for creating a new article in our database. Our handler in Nest controller will be similar to this code snippet:

@Post()
@UseGuards(JwtAuthenticationGuard)
create(@Body() createArticleDto: CreateArticleDTO, @Req() { user }: RequestWithUser) {
return this.articleSerivce.create(createArticleDto, user);
}

To make sure we’re on the same page, let me go through every line of this snippet:

  • @Post() is a built-in Nest decorator, informing our controller how it should handle incoming POST requests.
  • @UseGuards(JwtAuthenticationGuard) is a guard decorator responsible for checking if a user is authorized to make such a call. If not, the guard will return an HTTP exception, containing a message that the user is unauthorized.
  • @Body() createArticleDto: CreateArticleDTO is a body decorator which provides us with information about request body payload.
  • @Req() { user }: RequestWithUser – if a user is authorized to make a specific request, our implementation of JwtAuthenticationGuard will provide us with information about the user in the upcoming request.
  • Lastly, we return the method from our service responsible for creating a new article in the database.

As our NestJS application uses TypesScript, we can easily declare how our DTO’s (Data Transfer Object) will look like. In this particular example, our body will look like an instance of CreateArticleDTO:

export class CreateArticleDTO {
readonly title: string;
readonly description: string;
readonly content: string;
}

Looking at that, you would think that it’s enough for type-safe and data validation on our server – but that’s not exactly right.

Sending the following POST request

{
"title": "Article",
"description": "NestJS Article",
"content": 123
}

will not cause any exception. What’s more, we provided the wrong data type to create an article – we can expect our server to return an error providing some detailed info, can’t we?

How can we solve this problem? The solution is super simple: we need to use built-in pipes!

📚 Want to learn more things connected with Node.js? Check out our blog and take a look at the article about the differences between REST & GraphQL!

Validate all!

First of all, we need to inform Nest that we would like to use our ValidationPipe globally. To do this, add one line of code in the main.ts file of our project before initating the bootstrapping process:

app.useGlobalPipes(new ValidationPipe());

ValidationPipe is a pipe provided in the @nestjs/common package. It promotes a useful and problem-solving approach to validation schema for all incoming data, where the rules are declared with decorators in class declarations. Adding it globally will ensure all endpoints are protected from receiving incorrect data.

The next step is adding two very helpful NPM packages by running:

npm install class-validator class-transformer

As NestJS ValidationPipe uses these 2 libraries under the hood, there are many ways to customize the behavior of our pipe. I encourage you to take a look at the official documentation to read more about that!

Having these two dependencies installed in our project, we can now head to the CreateArticleDTO class declaration, and we make some minor changes by adding helpful decorators:

import { IsString, Length, IsOptional } from "class-validator";

export class CreateArticleDTO {
@IsString()
readonly title: string;

@IsString()
readonly description: string;

@Length(50)
readonly content: string;
}

As you can quickly notice, we used two decorators from the installed package:

  • @IsString(), which checks if an incoming value is a real string,
  • @Length(50) which checks if an incoming value has a minimum length of 50 characters; moreover, if the value is not a string, it also returns an error.

The above decorators are only a small part of the data validation capabilities. For numbers you can use for example @IsPositive(), @IsNegative(), @Min(), @Max(). It’s also possible to validate whole arrays of data – cool, isn’t it?

Let’s recompile our application and send once again the incorrect HTTP request mentioned above. Here’s our response payload:

{
"statusCode": 400,
"message": [
"content must be longer than or equal to 50 characters"
],
"error": "Bad Request"
}

You can immediately see that our data hasn’t been appropriately processed: we got 400 response codes together with an error message. Now, let’s check if our IsString() pipe works correctly with another request with such body payload:

{
"title": "Article",
"description": 123,
"content": "Very long attractive NestJS article on Merixstudio Blog"
}

And here is the correct behavior of our validation:

{
"statusCode": 400,
"message": [
"description must be a string"
],
"error": "Bad Request"
}

If you think that such error messages are too simple and not very pleasant for the users, you can easily manage their content within decorator:


@Length(50, {
message: 'Content is too short.'
})
readonly content: string;

It’s worth mentioning that we are able to validate every single param in our calls. Suppose we’re dealing with GET endpoint to receive details of a single article:

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.articleSerivce.findOne(id);
}

At first glance, you will see the usage of the next simple NestJS built-in pipe - ParseIntPipe. As you may know, every parameter provided in the request path comes over to the backend as a string by default. But we implicitly declare ID as a number – so don't we have wrong declared types at this moment?

With the use of this pipe implementation, it will try to convert a string to a number automatically – and if that’s not possible, return an exception.

Let’s create a request using Postman for such an endpoint:

/articles/test

Our application expects an ID in the form of a number, not a string, which is why we’re getting an error in the response:

{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}

Taking a couple of simple steps, we can ensure that our data is typed and validated correctly. I hope you liked this article. If you want to know more about NestJS and best practices, I encourage you to visit the framework’s official site.

Keen on honing your skills within an excellent back-end team? Check our job offers!

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .