Boost up NestJS server much faster and easier (maximum 20,000x faster + tRPC similar)

Preface

Nestia Logo

As I've promised in the previous Typia Series Articles, I'll introduce you my new library nestia, which can make NestJS much faster and easier.

With my new library nestia, you can boost up your NestJS server performance dramatically, just by writing only one line per each API function. Also, you can build SDK library, so you can help your fellow client developers to be much convenient.

Let's see nestia, and imagine how much your NestJS server would be powerful.

  • Only one line required, with pure TypeScript type
  • Runtime validator is 20,000x faster than class-validator
  • JSON serialization is 200x faster than class-transformer
  • SDK is similar with tRPC, but much advanced

SDK Sample

Left is NestJS server code, and right is client code utilizing SDK.

You can write client code much easily and safely.

Pure TypeScript Type

In NestJS, you have to define DTO by utilizing those libraries:

  • class-validator
  • class-transformer
  • @nestjs/swagger

I hate those libraries, because they force developer to define duplicated types like below. As long as you are using them following formal guide of NestJS, you never can avoid to duplicated type definitions in both TypeScript type and decorators.

Let's see how those libraries make DTO ugly:

export class BbsArticle {
    @IsString()
    @ApiProperty({
        format: "uuid",
    })
    id!: string;

    // DUPLICATED SCHEMA DEFINITION
    // - duplicated function call + property type
    // - have to specify `isArray` and `nullable` props by yourself
    @IsArray()
    @IsObject()
    @ValidateNested()
    @Type(() => AttachmentFile)
    @ApiProperty({
        type: () => AttachmentFile,
        nullable: true,
        isArray: true,
        description: "List of attached files.",
    })
    files!: AttachmentFile[] | null;

    @IsString()
    @IsOptional()
    @ApiProperty({
        type: "string",
        nullable: true,
        minLength: 5,
        maxLength: 100,
        description: "Title of the article.",
    })
    title!: string | null;

    @IsString()
    @ApiProperty({
        description: "Main content body of the article."
    })
    body!: string;

    @IsString()
    @ApiProperty({
        format: "date-time",
        description: "Creation time of article",
    })
    created_at!: string;
}

export class AttachmentFile {
    @IsString()
    @IsOptional()
    @ApiProperty({
        type: "string",
        nullable: true,
        maxLength: 255,
        pattern: "^[a-zA-Z0-9-_]+$",
        description: "File name.",
    })
    name!: string | null;

    @IsString()
    @IsOptional()
    @ApiProperty({
        type: "string",
        nullable: true,
        maxLength: 255,
        pattern: "^[a-zA-Z0-9-_]+$",
        description: "File extension.",
    })
    extension!: string | null;

    @IsString()
    @ApiProperty({
        format: "url",
        description: "URL of the file.",
    })
    url!: string;
}

In contrary, with nestia, you can define DTO with pure TypeScript type only. Thus, you don't need to be suffered from duplicated type definitions. Furthermore, even supports nestia interface typed DTO.

Let's see how nestia changes DTO so much easier and beautiful:

export interface IBbsArticle {
    /**
     * Primary Key.
     * 
     * @format uuid
     */
    id: string;

    /**
     * List of attached files.
     */
    files: IAttachmentFile[] | null;

    /**
     * Title of the article.
     * 
     * @minLength 5
     * @maxLength 100
     */
    title: string | null;

    /**
     * Main content body of the article.
     */
    body: string;

    /**
     * Creation time of article.
     * 
     * @format date-time
     */
    created_at: string;
}

export interface IAttachmentFile {
    /**
     * File name.
     * 
     * @pattern ^[a-z0-9]+$
     * @maxLength 255
     */
    name: string | null;

    /**
     * File extension.
     * 
     * @pattern ^[a-z0-9]+$
     * @maxLength 8
     */
    extension: string | null;

    /**
     * URL of the file.
     * 
     * @format uri
     */
    url: string;
}

Superfast Validation

If you have seen my previous Typia Series Articles, reading above #TypeScript section, you may suspect that nestia is using typia.

Yes, nestia is utilizing typia, and it is the secret why nestia can use pure TypeScript type as DTO. Also, as nestia is using typia, it can provide superfast validation.

Do you remember? Validation speed of typia was maximum 20,000x faster than class-validator what NestJS is using. Such super-fast validation speed also can be applied to your NestJS server, just by using nestia.

Now, change your class-validator based DTO classes to pure TypeScript type, and utilize @TypedBody() function like below. Then, your NestJS server's validation speed (about request body data) would be 20,000x times faster.

It seems very easy and wonderful, isn't it?

import { TypedBody, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";

import { IBbsArticle } from "./IBbsArticle";

@Controller("bbs/articles")
export class BbsArticlesController {
    @TypedRoute.Post()
    public async store(
        // 20,000x faster validation
        @TypedBody() input: IBbsArticle.IStore
    ): Promise<IBbsArticle> {
        return {
            ...input,
            id: "2b5e21d8-0e44-4482-bd3e-4540dee7f3d6",
            created_at: "2023-04-23T12:04:54.168Z",
        }
    }
}

Assert Function Benchmark

Measured on Intel i5-1135g7, Surface Pro 8


Superfast Serialization

nestia is using typia. Therefore, in the same reason with #Validation, JSON serialization speed also can be 200x faster than class-transformer what NestJS is using.

Just use @TypedRoute.${method}() decorator on your API function. Then, your NestJS server's JSON serialization speed (about response body data) would be 200x times faster.

If you're wondering how JSON serialization speed in the server side, look at the server benchmark graph of below. Just by adapting nestia and utilizing its @TypedRoute.${method}() function, your NestJS server can accept maximum 30x more requests.

Most operations in NodeJS server are asynchronously executed in background thread, what are called "event based non-blocking I/O model". However, JSON serialization is a synchronous operation running on the main thread. Therefore, if the JSON serialization speed is slow, it makes the entire server program slow.

It seems very powerful, isn't it?

import { TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";

import { IBbsArticle } from "./IBbsArticle";

@Controller("bbs/articles")
export class BbsArticlesController {
    // 200x faster JSON serialization
    @TypedRoute.Get("random")
    public async random(): Promise<IBbsArticle> {
        return {
            id: "2b5e21d8-0e44-4482-bd3e-4540dee7f3d6",
            title: "Hello nestia users",
            body: "Just use `TypedRoute.Get()` function like this",
            created_at: "2023-04-23T12:04:54.168Z",
            files: [],
        };
    }
}

Stringify Function Benchmark

Measured on Intel i5-1135g7, Surface Pro 8

Server Benchmark

Measured on Intel i5-1135g7, Surface Pro 8


SDK generation

Do you know tRPC? If you develop your NodeJS backend server with it, you don't need to deliver any Swagger Documents to your fellow client developers. You just can send an interaction library for them.

By the way, tRPC is good for building toy projects, but not suitable for developing enterprise level backend server. In such reason, many NestJS lovers had requested tRPC team to support NestJS. But considering principle of tRPC, it never can support NestJS.

Instead, I support SDK generation for NestJS through my new library nestia. Just run npx nestia sdk command, then nestia will analyze your NestJS backend server code, and generates optimal SDK library in the compilation level.

Look at below gif image, and imagine how SDK would be helpful for your fellow client developers. Your fellow client developers will just import the SDK library, and call API functions with auto completion.

If they take any mistake, compiler will detect it, therefore you don't need to be suffered from runtime error by mistakes.

SDK Sample

Left is NestJS server code, and right is client code utilizing SDK.

You can write client code much easily and safely.

No comments:

Post a Comment