📖 Guide Documents⛲ Pure TypeScript

Pure TypeScript

Outline

nestia can use pure TypeScript type.

You know what? NestJS needs triple duplicated DTO schema definitions. The 1st is defining TypeScript type, the 2nd and 3rd are calling decorator functions of class-validator and @nestjs/swagger. It’s not only annoying, but also error-prone. If you take any mistake on the 2nd or 3rd, it can’t be detected by TypeScript compiler. It will be detected only at runtime. Another words, it is not type safe.

Besides, nestia needs only pure TypeScript type. You don’t need to define any extra schema like class-validator or @nestjs/swagger. Just define pure TypeScript type only (especially recommend to use interface type), then nestia will do all the rest.

Demonstration

If you’re confusing how DTO of NestJS and nestia are different, just see example codes below.

At first, look at the first (Triple duplicated NestJS DTO) tab, and find the BbsArticle.files property, enhanced by blue coloured blocks. Looking at the files property, how do you feel? Just defining an array object type, you’ve to call 7 decorator functions. If you take any mistake when using the decorator like omitting isArray property, it would be a critical runtime error.

Besides, nestia needs only one line. Click the second (Pure Nestia DTO) tab, and find the IAttachmentFile.files property. Only one line being used, and IBbsArticle and IAttachment types are not even class, but just interface types. Comparing it to the first tab, how do you feel? Isn’t it more simple and readable?

This is the power of nestia, with pure TypeScript type.

BbsArticle.ts
import { ApiProperty } from "@nestjs/swagger";
import {
  ArrayNotEmpty,
  Format,
  IsArray,
  IsObject,
  IsOptional,
  IsString,
  Match,
  MaxLength,
  Type,
  ValidateNested,
} from "class-validator";
 
export class BbsArticle {
  @ApiProperty({
    format: "uuid",
  })
  @IsString()
  id!: string;
 
  // DUPLICATED SCHEMA DEFINITION
  // - duplicated function call + property type
  // - have to specify `isArray` and `nullable` props by yourself
  @ApiProperty({
    type: () => AttachmentFile,
    nullable: true,
    isArray: true,
    description: "List of attached files.",
  })
  @Type(() => AttachmentFile)
  @IsArray()
  @IsOptional()
  @IsObject({ each: true })
  @ValidateNested({ each: true })
  files!: AttachmentFile[] | null;
 
  @ApiProperty({
    type: "string",
    nullable: true,
    minLength: 5,
    maxLength: 100,
    description: "Title of the article.",
  })
  @IsOptional()
  @IsString()
  title!: string | null;
 
  @ApiProperty({
    description: "Main content body of the article.",
  })
  @IsString()
  body!: string;
 
  @ApiProperty({
    format: "date-time",
    description: "Creation time of article",
  })
  @IsString()
  created_at!: string;
}
 
export class AttachmentFile {
  @ApiProperty({
    type: "string",
    maxLength: 255,
    pattern: "^[a-zA-Z0-9-_]+$",
    description: "File name.",
  })
  @Matches(/^[a-z0-9]+$/)
  @MaxLength(255)
  @IsString()
  name!: string | null;
 
  @ApiProperty({
    type: "string",
    nullable: true,
    maxLength: 255,
    pattern: "^[a-zA-Z0-9-_]+$",
    description: "File extension.",
  })
  @Matches(/^[a-z0-9]+$/)
  @MaxLength(8)
  @IsOptional()
  @IsString()
  extension!: string | null;
 
  @ApiProperty({
    format: "url",
    description: "URL of the file.",
  })
  @Format("uri")
  @IsString()
  url!: string;
}

AOT Compilation

Someone may be suspicious of the phrase “Pure TypeScript Type”.

“As you know, TypeScript types do not have any tangible instance when compiled to JS.

However, with only these fictitious TypeScript types, how can nestia validates types at runtime? How nestia builds swagger documents or SDK library with only these types? Are these things really possible without extra schema definition like class-validator or @nestjs/swagger?”

My answer is: “Yes, it is possible due to nestia analyzes your server code, and performs AOT compilation”.

When compiling, nestia travels your NestJS server codes, and analyzes DTO definitions. And then, nestia writes optimal code to the compiled JavaScript file. In the @TypedBody() case, nestia transforms it to optimal validation code for the IBbsArticle.IStore type. Also, nestia transforms @TypedRoute.Post() function to optimal JSON serialization code for the IBbsArticle type.

Such compile time optimization is called AOT (Ahead of Time) compilation. And this is the secret why nestia can do everything with only pure TypeScript type. Read below example codes, and just look how JavaScript file being compiled. Then you may understand why nestia is much easier, and futhermore much faster.

  • Runtime validator is 20,000x faster than class-validator
  • JSON serialization is 200x faster than class-transformer
BbsArticlesController.ts
import { TypedBody, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
 
import { IBbsArticle } from "./IBbsArticle";
 
@Controller("bbs/articles")
export class BbsArticlesController {
  @TypedRoute.Post() // 200x faster JSON serialization
  public async store(
    // 20,000x faster validation
    @TypedBody() input: IBbsArticle.ICreate,
  ): Promise<IBbsArticle> {
    return {
      ...input,
      id: "2b5e21d8-0e44-4482-bd3e-4540dee7f3d6",
      created_at: "2023-04-23T12:04:54.168Z",
    };
  }
}

Assert Function Benchmark

Measured on AMD R9-7940HS, Rog Flow X13