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
- SDK (Software Development Kit)
-
nestia
: just CLI (command line interface) tool
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.
- I made 1,000x faster TypeScript validator library
- I made 10x faster JSON.stringify() functions, even type safe
- [Typia] 15,000x faster validator and its histories
@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
typia
is maximum 15,000x times faster thanclass-validator
Measured on Intel i5-1135g7, Surface Pro 8
TRPC in NestJS
# BASIC COMMAND
npx nestia <sdk|swagger> <source_directories_or_patterns> \
--exclude <exclude_directory_or_pattern> \
--out <output_directory_or_file>
# EXAMPLES
npx nestia sdk "src/**/*.controller.ts" --out "src/api"
npx nestia swagger "src/controllers" --out "dist/swagger.json"
# ONLY WHEN "nestia.config.ts" FILE EXISTS
npx nestia sdk
npx 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.
Project | Swagger Documents |
---|---|
npx nestia start |
Swagger Editor |
fake-iamport-server | Swagger Editor |
fake-toss-payments-server | Swagger Editor |
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)}`;
}
}
Project | Controller | SDK Library |
---|---|---|
npx nestia start |
BbsArticlesController |
Functions |
fake-iamport-server | FakeIamportCertificationsController |
Functions |
fake-toss-payments-server | FakeTossPaymentsController |
Functions |
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:
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.
Top comments (1)
Great job!