Many times, we need to transform input payload data before passing it to our core processing logic. NestJS Mapped Types can help us manage the transformation in a declarative manner.

But why do we need the transformation of data?

Simply put, data coming over the wire (path parameters, query parameters etc) is in string format by default. However, we often need to change it to another type before our application logic can use it. Hence, transformation becomes an important aspect of any application.

In this post, we will look at NestJS Mapped Types with examples. This builds up on our knowledge of ValidationPipe. Therefore, I recommend you to check out the post about NestJS ValidationPipe first before going through this.

1 – NestJS Auto Transformation without Mapped Types

Before we explore MappedTypes, it is important to note that NestJS comes with auto transformation feature. This feature uses the ValidationPipe class.

How can we enable this feature?

Well, actually it is quite simple. We can enable it a method-level or globally as well.

Below is how we can do it at method-level.

@Post("/user")
@UsePipes(new ValidationPipe({transform: true}))
create(@Body() createUserModel: CreateUserModel) {
     console.log("New User Created");
}

We can set it up globally by configuring it in the main.ts.

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    transform: true
  }))
  await app.listen(3000);
}
bootstrap();

Notice the transform: true flag. Basically, this flag enables auto transformation across the entire application context.

Auto transformation also performs conversion of primitive types such as integer or boolean. See below example.

@Get("/user/:id")
get(@Param() id: number) {
    console.log(typeof id);
}

In the above example, we specify id as number in the method signature. Therefore, ValidationPipe will automatically try and convert the string identifier to number.

2 – Explicit Transformation

ValidationPipe can implicitly transform data. It works on both query parameters and path parameters. However, this feature requires us to enable auto transformation.

Alternatively, we can also transform data without auto transformation. Basically, we can cast values using ParseIntPipe() or ParseBoolPipe() and so on.

See below example:

@Get("/user/:id")
findOne(
     @Param('id', ParseIntPipe) id: number,
     @Query('active', ParseBoolPipe) active: boolean
){
     console.log(typeof id);
     console.log(typeof active);
}

Here, we transform the id and the active flag to number and boolean respectively. To know more about the pipes we have used, please refer to NestJS Pipes. There we talk about various in-built pipes in great detail.

3 – Transformation using NestJS Mapped Types

Many times we build CRUD applications for entities in our application. As part of that, we have to construct variants on the same base entity.

When implementing input validations, it is often useful to build create and update variants on the same type. For example, creating a new record may need all fields as input. However, update variant may keep all fields as optional. This is where NestJS Mapped Types comes into the picture.

To use mapped types, we first install NestJS Swagger Package. This is because the @nestjs/mapped-types package is also bundled with the @nestjs/swagger package.

npm install --save @nestjs/swagger

3.1 – PartialType

Using PartialType function, we can make all fields of a particular variant as optional. Basically, PartialType returns a type or a class with all properties of input type set to optional. As an example, we have the below model.

export class CreateUserModel {
    email: string;
    password: string;
    address: string;
}

By default, if we use the model for any request handler, all the fields are required. However, we can create a new type where each of these fields is optional by using PartialType as below.

import { CreateUserModel } from "./create-user-model";
import { PartialType } from "@nestjs/mapped-types";

export class UpdateUserModel extends PartialType(CreateUserModel){}

We can simply use the UpdateUserModel in another request handler as below:

@Put("/user")
update(@Body() updateUserModel: UpdateUserModel) {
     console.log("User Updated")
}

3.2 – PickType

The PickType function constructs a new class or type by picking particular set of properties from the input type.

See below example:

import { PickType } from "@nestjs/mapped-types";
import { CreateUserModel } from "./create-user-model";

export class UpdateUserPasswordModel extends PickType(CreateUserModel, ['password'] as const) {}

Basically, here we will have a new type that only contains the password attribute from the CreateUseModel

3.3 – OmitType

As the name suggests the OmitType function constructs a new type or class by picking all properties from an input type and removing certain attributes.

Consider the below example.

import { CreateUserModel } from "./create-user-model";
import { OmitType } from "@nestjs/mapped-types";

export class UpdateUserModel extends OmitType(CreateUserModel, ['password'] as const){}

Basically, here the new model will have all properties from CreateUserModel except password.

3.4 – IntersectionType

The IntersectionType function combines two types into one class or type.

For example, consider a situation where we have the below models.

export class CreateUserModel {
    email: string;
    password: string;
    address: string;
}
export class AdditionalUserModel {
    age: number;
}

Now, we can declare a new type as below.

import { CreateUserModel } from "./create-user-model";
import { IntersectionType } from "@nestjs/mapped-types";
import { AdditionalUserModel } from "./additional-user-model";

export class UpdateUserModel extends IntersectionType(CreateUserModel, AdditionalUserModel){}

Basically, the new type will contain properties of both CreateUserModel and AdditionalUserModel.

3.5 – Composable NestJS Mapped Types

Another useful property of the mapped types is that they can be used to compose more complex combinations.

Consider the below example:

import { CreateUserModel } from "./create-user-model";
import { OmitType, PartialType } from "@nestjs/mapped-types";

export class UpdateUserModel extends PartialType(
    OmitType(CreateUserModel, ['password'] as const),
) {}

Basically, here we have all the properties of the CreateUserModel after omitting password. But by using PartialType, we also make those properties as optional in the new type.

As you can see, there could be many combinations of arranging the models using this composable nature of mapped types.

Conclusion

With this, we have successfully explored the various aspects of NestJS Mapped Types.

We first gave a quick glimpse of the auto transformation feature available with NestJS. Post that, we also saw the various functions that are available to the developers to compose types based on requirement.

If you have any comments or queries about Mapped Types, please write in the comments section below.

Categories: BlogNestJS

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *