Today I am excited to announce the official release of NestJS 7. This is a major release spanning the entire platform, including the framework, numerous changes to the @nestjs/graphql
package, CLI improvements, and updated documentation.
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.
Last year was great for our community. In 2019, Nest has been recognised as the fastest-growing Node.js technology in the world. We’ve also made it to the TOP 20 GitHub repositories on the whole globe, and, according to NPM metrics, experienced an almost 400% growth in number of downloads on NPM, and thus becoming one of the most popular Node.js libraries with roughly 1 million of downloads each month! 2019 was amazing and we expect 2020 to be even better for the whole ecosystem!
This release brings lots of great features and long-awaited improvements. There are far too many to list here, but let’s take a high-level look at some of the most exciting ones.
In the version 6 major release of NestJS, we introduced the code first approach as a compatibility layer between the amazing type-graphql
package and the official @nestjs/graphql
module. This allowed us to solve types redundancy problems by automatically generating GraphQL SDL from TypeScript metadata using decorators.
However, despite the numerous new opportunities that this integration has brought, we have faced many challenges that did not seem easily solvable down the road.
The integration required developers to mix decorators coming from two different libraries. If you’ve messed up the imports, all your resolvers would stop injecting dependencies through constructor (all of them would be equal to undefined
). Thus, developers reported many bugs and issues, assuming that the integration doesn’t work at all.
To generate a schema, type-graphql
generates resolve
function for each field. However, Nest has its own DI system that links dependencies and instantiates classes (in this case, providers), which methods are later being bounded to GraphQL fields. Hence, it led to unnecessary memory allocations.
Lastly, the code first approach requires using many additional decorators to define input types, args, etc. Initially, we decided to simply link to the external library documentation. However, the API of decorators which wrapped external decorators was somewhat different (because we must support schema first approach as well). We also didn’t want to duplicate the documentation in the NestJS documentation because maintaining it would become impossible in the long run (adjusting to API changes, etc.).
In order to future-proof the library to meet the needs of the community, we needed to reimplement all the features from scratch due to a lack of predictability and flexibility while dealing with the third party library.
To avoid lots of breaking changes, we did our best to make sure that the public API IS backwards-compatible.
Therefore, with a simple update: (search & replace) all the type-graphql
imports to use @nestjs/graphql
, and that’s it. 🎊
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Post } from './post';
@ObjectType()
export class Author {
@Field(type => Int)
id: number;
@Field({ nullable: true })
firstName?: string;
@Field({ nullable: true })
lastName?: string;
@Field(type => [Post])
posts: Post[];
}
Learn more about the code first approach here.
In addition, switching to our own Typescript & GraphQL implementation, allowed us to create a plugin
To reduce the amount of boilerplate code required, Nest now provides a new GraphQL plugin (for code first applications) that enhances the TypeScript compilation process.
Hint This plugin is also opt-in. If you prefer, you can declare all decorators manually, or only specific decorators where you need them.
The new GraphQL plugin will automatically:
@InputObject
), object type (@ObjectType
) and args (@Args
) classes properties with @Field
decorator unless @HideField
is usednullable
property depending on the question mark (e.g. name?: string
will set nullable: true
)type
property depending on the type (supports arrays as well)Therefore, the example shown above can be rewritten as follows:
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Post } from './post';
@ObjectType()
export class Author {
@Field(type => Int)
id: number;
firstName?: string;
lastName?: string;
posts: Post[];
}
The plugin adds appropriate decorators on the fly based on the Abstract Syntax Tree.
TIP: You no longer have to struggle with
@Field
boilerplate decorators scattered throughout the entire project. They will be added automatically.
Learn how to enable the plugin here.
The custom decorators API has been unified for all types of applications. Now, whether you’re creating a GraphQL application or a REST API, the factory passed into the createParamDecorator()
function will take the ExecutionContext
(read more here) object as a second argument.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
Now, to determine the type of application that our decorator is currently evaluated, use the getType()
method:
const type = host.getType();
if (type === 'http') {
// HTTP application
} else if (type === 'rpc') {
// Microservice
}
To enable the request-response message style, Nest creates two logical channels - one is responsible for transferring the data while the other waits for incoming responses.
For some underlying transports, such as NATS, this dual-channel support is provided out-of-the-box. For others, Nest compensates by manually creating separate channels.
Let’s say that we have a single message handler @MessagePattern('getUsers')
. In the past, Nest built two channels from this pattern: getUsers_ack
(for requests) and getUsers_res
(for responses). With version 7, this naming scheme changes. Now Nest will build getUsers
channel (for requests) and getUsers.reply
(for responses) instead. Also, specifically for the MQTT transport strategy, the response channel would be getUsers/reply
.
This change simplifies the integration with external systems, existing legacy applications, and also, allows avoiding clashes with wildcard subscriptions available in, for example, MQTT strategy.
Furthermore, the overall performance will improve. From now on, Nest will no longer send out redundant information as part of the message packets (in the request-response message style), hence, decreasing their size.
Nest v7 comes with 2 NEW pipes available out-of-the-box, respectively ParseBoolPipe
and ParseArrayPipe
(both exported from the @nestjs/common
package).
While ParseBoolPipe
is just another transformer pipe which along with the existing ParseIntPipe
can implicitly transform & validate values based on the expected type (read more here), ParseArrayPipe
is a solution for sending either unwrapped or serialized arrays through the network.
Basically, TypeScript does not store metadata about generics or interfaces, so when you use them in your DTOs (Data Transfer Objects), ValidationPipe
may not be able to properly validate incoming data. For instance, the code below won’t be correctly validated:
@Post()
createBulk(@Body() createUserDtos: CreateUserDto[]) {
return 'This action adds new users';
}
Previously, to validate the array you had to create a dedicated class which contained a property that wraps the array. Now, you can simply use the ParseArrayPipe
.
@Post()
createBulk(
@Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
In addition, the ParseArrayPipe
may come in handy when parsing query parameters. Let’s define the findByIds()
method that returns users based on identifiers passed as query parameters.
@Get()
findByIds(
@Query('id', new ParseArrayPipe({ items: Number, separator: ',' }))
ids: number[],
) {
return 'This action return users by ids';
}
And test this endpoint using cURL
:
$ # GET /?ids=1,2,3
$ curl http://localhost:3000/?ids=1,2,3
The stringified ids
query parameter (ids=1,2,3
) will be automatically transformed to an array. In addition, each item will be validated and eventually, parsed to a number
.
With the auto-transformation option enabled (transform: true
), the ValidationPipe
will now perform conversion of primitive types. In the following example, the findOne()
method takes one argument which represents an extracted id
path parameter:
@Get(':id')
findOne(@Param('id') id: number) {
console.log(typeof id === 'number'); // true
return 'This action returns a user';
}
By default, every path parameter and query parameter comes over the network as a string
. In the above example, we specified the id
type as a number
(in the method signature). Therefore, the ValidationPipe
will try to automatically convert the string identifier to a number.
Our official documentation has significant improvements to GraphQL section. We now cover most of features / functionalities you may need to get started with either schema first or code first approaches.
We strongly believe that this update will strongly improve the development experience. 🐈
Thank you
#nestjs #nodejs #webdev #javascript