In this article, we'll be looking at the recently added NestJS Mapped Types feature. We'll be showing how Mapped Types can drastically reduce the amount of boilerplate code required to build NestJS applications, speeding up our development and improving overall application maintainability.
In case you're not familiar with NestJS, it is a TypeScript Node.js framework that helps you build enterprise-grade efficient and scalable Node.js applications.
Introduction to mapped types
As you build out features, it's often useful to construct variants on a base entity type. A good example of such a variant is a Data Transfer Object (DTO). A Data Transfer Object is an object that is used to encapsulate data, and send it from one part of your application to another. DTO’s help us define the input and output interfaces of our system.
Let's imagine a real-world example, where we typically need to build both a create and update variations for the same entity type.
The create variant may require all fields, while the update variant may make all fields optional. Not to mention, both these types can also be variants of an entity type (to some extent).
That's a lot of redundant code!
Thus, NestJS now provides several utility functions that perform type transformations to help us avoid doing this, and make life a little bit easier.
Mapped Types are supported by both REST (@nestjs/swagger) and GraphQL (@nestjs/graphql) applications.
Construct partial types
Let's say that we have a User
resource in our application. To allow creating new users, we'll define the CreateUserDto
class (input interface), as follows:
import { ApiProperty } from '@nestjs/swagger';import { IsEmail, MinLength } from 'class-validator';export class CreateUserDto { @IsEmail() @ApiProperty() email: string; @MinLength(10) @ApiProperty() password: string;}
As you can see in the example above, to create a new user we must provide an email
and password
. We've used the @ApiProperty()
decorator to make properties visible to the @nestjs/swagger
(note it's not needed with the CLI plugin enabled) package. Also, we've added some basic validation rules above.
By default, all of these fields are required.
With our first DTO in place, let's define another DTO for the update operation.
The update operation expects the same fields, but with each one optional. To construct such a type, use the PartialType()
function and pass the class reference (CreateUserDto
) as an argument:
export class UpdateUserDto extends PartialType(CreateUserDto) {}
Hint The
PartialType()
function is imported from the@nestjs/swagger
package.
That's it! 🐈
Now all fields are optional. Furthermore, when any value is not present, the validation is skipped for this specific property. You can read more about mapped types for REST APIs here.
Everything we've looked at today is very helpful and relevant for GraphQL applications as well! For instance, let's say we have an input type called CreateUserInput
:
import { InputType, Field } from '@nestjs/graphql';@InputType()class CreateUserInput { @Field() email: string; @Field() password: string;}
Hint Note that using @Field() decorator is not required when the CLI Plugin is enabled.
By default, all of these fields are required. To create a type with the same fields, but with each one optional, use PartialType()
passing the class reference (CreateUserInput
) as an argument:
@InputType()export class UpdateUserInput extends PartialType(CreateUserInput) {}
Hint The
PartialType()
function is imported from the@nestjs/graphql
package.
To learn more about mapped types for GraphQL application, visit this page.
Picking properties from types
What if our system requires that each email must be unique and immutable?
With our new Mapped Types functionality, we have the option to exclude the email
property from the UpdateUserDto
class which will disable users from being able to update already taken emails.
For this, we can use either the OmitType()
or the PickType()
function.
The OmitType()
function creates a new type by picking all properties from an input type and then removing a particular set of keys, while the PickType()
function creates a new type by picking a set of properties from an input type.
Since we only want to exclude a single property, using OmitType()
function is the simpler option here.
export class UpdateUserDto extends PartialType( OmitType(CreateUserDto, ['email'] as const)) {}
Hint The
OmitType()
function is imported from the@nestjs/swagger
package.
Now UpdateUserDto
has every property except email
. All of the other properties are marked as optional (due to the PartialType
function), as we mentioned before.
You can read more about the OmitType()
and PickType()
for REST APIs here.
Likewise, you can use the same construction with GraphQL applications:
@InputType()export class UpdateUserInput extends PartialType( OmitType(CreateUserInput, ['email'] as const)) {}
Hint The
OmitType()
function is imported from the@nestjs/graphql
package.
To learn more about the OmitType()
and PickType()
functions for GraphQL application, visit this page.
But why?
Why do we even need all these utility functions? After all, TypeScript supports mapped types feature already, right?
Built-in types like Partial<T>
, Omit<T, K extends keyof T>
, or Pick<T, K extends keyof T>
are just TypeScript instructions. For example, Partial
will inform the compiler that all properties are optional, but it won't generate any additional metadata (in the transpiled JavaScript files) that we could read at runtime.
Similarly, Pick
will not instruct the compiler to internally define a new, derived class with wiped out decorators (applied to excluded properties) because this would introduce side-effects.
Nest utility functions, on the other hand, will exclude, pick, or apply validation decorators based on the expression you used.
These utility functions we've looked at will remove @nestjs/graphql
or @nestjs/swagger
metadata (if needed) which makes them fully compatible and ready to use in combination with these packages.
How do I get started?
NestJS mapped types feature makes type transformations more convenient. They help to dramatically reduce the boilerplate code by leveraging existing classes to construct type variants.
To start using them now, simply update your package (either @nestjs/graphql
or @nestjs/swagger
depending on the type of your application) to the latest version. 🐱
Support
Nest is an MIT-licensed open source project with its ongoing development made possible thanks to the support by the community, thank you!
<script>We strongly believe that someday we could fully focus on #opensource to make @nodejs world better 🚀🔥 @opencollect
— NestJS (@nestframework) April 25, 2018
- enjoy using @nestframework?
- ask your company to support us:
- https://t.co/taYS49lllr pic.twitter.com/L1O9Vf5uhS
If you want to join them, you can find more here.
Official consulting
Our goal is to help elevate teams — giving them the push they need to truly succeed in today’s ever-changing tech world. Contact us to find out more about expertise consulting, code reviews, architectural support, on-site enterprise workshops, trainings, and private sessions: support@nestjs.com.
Thank you
To all backers, sponsors, contributors, and community, thank you once again! This product is for you. And this is only the beginning of the long 🚀 story.
Become a Backer or Sponsor to Nest by donating to our open collective. ❤
Learn NestJS - Official NestJS Courses 📚
Level-up your NestJS and Node.js ecosystem skills in these incremental workshop-style courses, from the NestJS Creator himself, and help support the NestJS framework! 🐈🚀 The NestJS Fundamentals Course is now LIVE and 25% off for a limited time!
🎉 NEW - NestJS Course Extensions now live!
- NestJS Advanced Concepts Course now LIVE!
- NestJS Authentication / Authorization Course now LIVE!
- NestJS GraphQL Course (code-first & schema-first approaches) are now LIVE!
- NestJS Authentication / Authorization Course now LIVE!