Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
January 28, 2023 06:12 am GMT

[Nestia] 15,000x faster validators and tRPC (SDK) for NestJS

Summary

https://github.com/samchon/nestia

Nestia is a set of helper libraries for NestJS, supporting below features:

  • @nestia/core: 15,000x times faster validation decorators
  • @nestia/sdk: evolved SDK and Swagger generators
    • SDK (Software Development Kit)
      • interaction library for client developers
      • almost same with tRPC
  • nestia: just CLI (command line interface) tool

nestia-sdk-demo

Superfast validator

Do you remember? I'd posted in here dev.to that I've developed a TypeScript library typia, which has superfast validators and type safe JSON stringify functions by utilizing AOT (Ahead of Time) compliation skill.

@nestia/core utilizes such typia to enhance request body validation for NestJS. As basic request body decorator utilizes class-validator library for data validation, @nestia/core is maximum 15,000x times faster.

Also, it does not require extra dedication like defining schema (ajv, io-ts, zod, ...) or DTO class declaration with decorator function calls (class-validator). Just fine with pure TypeScript type like interface.

Just by using @TypedBody() decorator, validation speed would be dramatically increased. Also, it is possible to making JSON stringify function faster and type safe by using @TypedRoute decorator. When you compile, nestia and typia will analyze your NestJS backend server code and generate optimzed code like below.

import { Controller } from "@nestjs/common";import { TypedBody, TypedRoute } from "@nestia/core";import type { IBbsArticle } from "@bbs-api/structures/IBbsArticle";@Controller("bbs/articles/:section")export class BbsArticlesController {    /**     * Update article.     *     * When updating, this BBS system does not overwrite the content, but accumulate it.     * Therefore, whenever an article being updated, length of {@link IBbsArticle.contents}     * would be increased and accumulated.     *     * @param section Target section     * @param id Target articles id     * @param input Content to update     * @returns Newly created content info     */    @TypedRoute.Post() // 10x faster and safer JSON.stringify()    public async store(        @core.TypedParam("section", "string") section: string,        @core.TypedParam("id", "uuid") id: string,        @TypedBody() input: IBbsArticle.IUpdate, // super-fast validator    ): Promise<IBbsArticle.IContent>;         // do not need DTO class definition,         // just fine with interface}
"use strict";var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;    return c > 3 && r && Object.defineProperty(target, key, r), r;};var __metadata = (this && this.__metadata) || function (k, v) {    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);};var __param = (this && this.__param) || function (paramIndex, decorator) {    return function (target, key) { decorator(target, key, paramIndex); }};var __importDefault = (this && this.__importDefault) || function (mod) {    return (mod && mod.__esModule) ? mod : { "default": mod };};Object.defineProperty(exports, "__esModule", { value: true });exports.BbsArticlesController = void 0;const core_1 = __importDefault(require("@nestia/core"));const common_1 = require("@nestjs/common");const BbsArticleProvider_1 = require("../providers/bbs/BbsArticleProvider");/** * This is a fake controller. * * Remove it or make it to be real one. */let BbsArticlesController = class BbsArticlesController {    /**     * Update article.     *     * When updating, this BBS system does not overwrite the content, but accumulate it.     * Therefore, whenever an article being updated, length of {@link IBbsArticle.contents}     * would be increased and accumulated.     *     * @param section Target section     * @param id Target articles id     * @param input Content to update     * @returns Newly created content info     */    update(section, id, input) {        return BbsArticleProvider_1.BbsArticleProvider.update(section, id, input);    }};__decorate([    core_1.default.TypedRoute.Put(":id", { type: "is", is: input => { const is = input => {            const $is_uuid = core_1.default.TypedRoute.Put.is_uuid;            const $io0 = input => "string" === typeof input.id && true === $is_uuid(input.id) && "string" === typeof input.created_at && "string" === typeof input.title && "string" === typeof input.body && ("md" === input.format || "html" === input.format || "txt" === input.format) && (Array.isArray(input.files) && input.files.every(elem => "object" === typeof elem && null !== elem && $io1(elem)));            const $io1 = input => "string" === typeof input.name && (null === input.extension || "string" === typeof input.extension) && "string" === typeof input.url;            return "object" === typeof input && null !== input && $io0(input);        }; const stringify = input => {            const $string = core_1.default.TypedRoute.Put.string;            const $throws = core_1.default.TypedRoute.Put.throws;            const $is_uuid = core_1.default.TypedRoute.Put.is_uuid;            const $io0 = input => "string" === typeof input.id && true === $is_uuid(input.id) && "string" === typeof input.created_at && "string" === typeof input.title && "string" === typeof input.body && ("md" === input.format || "html" === input.format || "txt" === input.format) && (Array.isArray(input.files) && input.files.every(elem => "object" === typeof elem && null !== elem && $io1(elem)));            const $io1 = input => "string" === typeof input.name && (null === input.extension || "string" === typeof input.extension) && "string" === typeof input.url;            const $so0 = input => `{"id":${"\"" + input.id + "\""},"created_at":${$string(input.created_at)},"title":${$string(input.title)},"body":${$string(input.body)},"format":${(() => {                if ("string" === typeof input.format)                    return $string(input.format);                if ("string" === typeof input.format)                    return "\"" + input.format + "\"";                $throws({                    expected: "(\"html\" | \"md\" | \"txt\")",                    value: input.format                });            })()},"files":${`[${input.files.map(elem => $so1(elem)).join(",")}]`}}`;            const $so1 = input => `{"name":${$string(input.name)},"extension":${null !== input.extension ? $string(input.extension) : "null"},"url":${$string(input.url)}}`;            return $so0(input);        }; return is(input) ? stringify(input) : null; } }),    __param(0, core_1.default.TypedParam("section", "string")),    __param(1, core_1.default.TypedParam("id", "uuid")),    __param(2, core_1.default.TypedBody({ type: "assert", assert: input => {            const $guard = core_1.default.TypedBody.guard;            const $join = core_1.default.TypedBody.join;            ((input, path, exceptionable) => {                const $ao0 = (input, path, exceptionable) => ("string" === typeof input.title || $guard(exceptionable, {                    path: path + ".title",                    expected: "string",                    value: input.title                })) && ("string" === typeof input.body || $guard(exceptionable, {                    path: path + ".body",                    expected: "string",                    value: input.body                })) && ("md" === input.format || "html" === input.format || "txt" === input.format || $guard(exceptionable, {                    path: path + ".format",                    expected: "(\"html\" | \"md\" | \"txt\")",                    value: input.format                })) && ((Array.isArray(input.files) || $guard(exceptionable, {                    path: path + ".files",                    expected: "Array<Resolve<IAttachmentFile>>",                    value: input.files                })) && input.files.every((elem, index1) => ("object" === typeof elem && null !== elem || $guard(exceptionable, {                    path: path + ".files[" + index1 + "]",                    expected: "Resolve<IAttachmentFile>",                    value: elem                })) && $ao1(elem, path + ".files[" + index1 + "]", true && exceptionable))) && ("string" === typeof input.password || $guard(exceptionable, {                    path: path + ".password",                    expected: "string",                    value: input.password                }));                const $ao1 = (input, path, exceptionable) => ("string" === typeof input.name || $guard(exceptionable, {                    path: path + ".name",                    expected: "string",                    value: input.name                })) && (null === input.extension || "string" === typeof input.extension || $guard(exceptionable, {                    path: path + ".extension",                    expected: "(null | string)",                    value: input.extension                })) && ("string" === typeof input.url || $guard(exceptionable, {                    path: path + ".url",                    expected: "string",                    value: input.url                }));                return ("object" === typeof input && null !== input || $guard(true, {                    path: path + "",                    expected: "Resolve<IBbsArticle.IUpdate>",                    value: input                })) && $ao0(input, path + "", true);            })(input, "$input", true);            return input;        } })),    __metadata("design:type", Function),    __metadata("design:paramtypes", [String, String, Object]),    __metadata("design:returntype", Promise)], BbsArticlesController.prototype, "update", null);BbsArticlesController = __decorate([    (0, common_1.Controller)("bbs/articles/:section")], BbsArticlesController);exports.BbsArticlesController = BbsArticlesController;//# sourceMappingURL=BbsArticlesController.js.map

Is Function Benchmark

typia is maximum 15,000x times faster than class-validator

Measured on Intel i5-1135g7, Surface Pro 8

TRPC in NestJS

# BASIC COMMANDnpx nestia <sdk|swagger> <source_directories_or_patterns> \    --exclude <exclude_directory_or_pattern> \    --out <output_directory_or_file># EXAMPLESnpx nestia sdk "src/**/*.controller.ts" --out "src/api"npx nestia swagger "src/controllers" --out "dist/swagger.json"# ONLY WHEN "nestia.config.ts" FILE EXISTSnpx nestia sdknpx nestia swagger

With @nestia/sdk, you can build both swagger and SDK library.

At first, I think everyone knows what swagger is. Therefore, I will skip the swagger documents generation just by showing a demonstration table.

The other one SDK (Software Development Kit) means an interaction library with NestJS backend server for client developers written in TypeScript. It is similar with tRPC in NestJS side.

When you run npx nestia sdk command, @nestia/sdk will analyze your NestJS backend server code and generates fetch functions for each API controller methods. Below is an example code generated by @nestia/sdk about above BbsArticlesController.update() method:

/** * @packageDocumentation * @module api.functional.bbs.articles * @nestia Generated by Nestia - https://github.com/samchon/nestia  *///================================================================import { Fetcher } from "@nestia/fetcher";import type { IConnection } from "@nestia/fetcher";import type { IBbsArticle } from "./../../../structures/bbs/IBbsArticle";/** * Update article. *  * When updating, this BBS system does not overwrite the content, but accumulate it. * Therefore, whenever an article being updated, length of {@link IBbsArticle.contents} * would be increased and accumulated. *  * @param connection connection Information of the remote HTTP(s) server with headers (+encryption password) * @param section Target section * @param id Target articles id * @param input Content to update * @returns Newly created content info *  * @controller BbsArticlesController.update() * @path PUT /bbs/articles/:section/:id * @nestia Generated by Nestia - https://github.com/samchon/nestia */export function update    (        connection: IConnection,        section: string,        id: string,        input: IBbsArticle.IUpdate    ): Promise<update.Output>{    return Fetcher.fetch    (        connection,        update.ENCRYPTED,        update.METHOD,        update.path(section, id),        input    );}export namespace update{    export type Input = IBbsArticle.IUpdate;    export type Output = IBbsArticle.IContent;    export const METHOD = "PUT" as const;    export const PATH: string = "/bbs/articles/:section/:id";    export const ENCRYPTED: Fetcher.IEncrypted = {        request: false,        response: false,    };    export function path(section: string, id: string): string    {        return `/bbs/articles/${encodeURIComponent(section)}/${encodeURIComponent(id)}`;    }}

When client developer (maybe TypeScript frontend developer) utilizes the SDK library, it would be looked like below gif image, what you'd seen at first:

nestia-sdk-demo

Start Nestia

npx nestia start <directory>

Now, let's start nestia project. Run above command, then a boilerplate project would be constructed and you can start the NestJS development with superfast validators and SDK generators, directly.

Image description


Original Link: https://dev.to/samchon/nestia-15000x-faster-validators-and-trpc-sdk-for-nestjs-248k

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To