Comparisons

tsgonest vs Nestia + Typia

Architecture and DX comparison between tsgonest and the Nestia/Typia ecosystem.

Nestia and Typia are TypeScript libraries by Samchon that take a similar "types as source of truth" approach to validation and serialization. This page compares tsgonest with the Nestia + Typia stack.

At a glance

FeatureNestia + Typiatsgonest
LanguageTypeScript (ts-patch compiler plugin)Go (wraps typescript-go)
Compilationtsc with ts-patch transform plugintsgo (~10x faster than tsc)
Code generationTypeScript compiler plugin (ts-patch)Go-native AST analysis
Validationtypia.assert<T>() callsGenerated companion files
Serializationtypia.json.stringify<T>() callsGenerated companion files
OpenAPI@nestia/swagger + runtime analysisStatic analysis at build time
Runtime costTransformed at compile time, runs at runtimeGenerated at build time, loaded on demand
Setupts-patch + tsconfig plugin configSingle CLI binary
NestJS integration@nestia/core decorators@tsgonest/runtime pipe + interceptor
Branded typestypia.tags.Format<"email"> etc.@tsgonest/types Email, Min, Max, etc.
Standard SchemaNot yet supportedBuilt-in Standard Schema v1
Watch modeExternal (nodemon, ts-node-dev)Built-in tsgonest dev

Architecture difference

This is the fundamental distinction between the two approaches.

Typia: compiler plugin

Typia works as a TypeScript compiler plugin via ts-patch. It transforms typia.assert<T>(input) calls into inline validation code during compilation:

// Source code (what you write)
import typia from 'typia';

const user = typia.assert<CreateUserDto>(input);
// Compiled output (what ts-patch generates)
const user = ((input) => {
  if (typeof input !== 'object' || input === null) throw new Error('...');
  if (typeof input.name !== 'string') throw new Error('...');
  if (input.name.length < 1) throw new Error('...');
  // ... inline validation code
  return input;
})(input);

The validation code is inlined at each call site. There is no separate companion file.

tsgonest: Go-native code generation

tsgonest works at a higher level — it analyzes your entire project's types and generates companion files alongside the compiled output:

dist/
  user.dto.js                             # tsgo output
  user.dto.CreateUserDto.tsgonest.js      # validate + assert + serialize + schema
  user.dto.CreateUserDto.tsgonest.d.ts    # type declarations
  __tsgonest_manifest.json                # manifest for runtime discovery
  openapi.json                            # OpenAPI 3.2 document

The companion files are loaded on demand at runtime via the manifest — your application code doesn't need to import them directly.

What this means in practice

AspectTypia (plugin)tsgonest (codegen)
Explicit calls neededYes — typia.assert<T>() at every use siteNo — discovered via manifest
Bundle size impactInline code at every call siteSingle companion per type
NestJS integration@nestia/core decorators replace NestJS onesDrop-in pipe/interceptor, keep standard decorators
Build toolingRequires ts-patch, plugin config in tsconfigSingle tsgonest build command
Build speedtsc speed (with plugin overhead)tsgo speed (~10x faster)
Incremental buildsVia tsc incrementalBuilt-in caching

DX comparison

Defining DTOs

Both approaches let you use plain TypeScript types. The branded type syntax differs slightly:

// Typia
import typia, { tags } from 'typia';

interface CreateUserDto {
  name: string & tags.MinLength<1> & tags.MaxLength<255>;
  email: string & tags.Format<"email">;
  age: number & tags.Minimum<0> & tags.Maximum<150>;
}
// tsgonest
import { Min, Max, Email } from '@tsgonest/types';

interface CreateUserDto {
  name: string & Min<1> & Max<255>;
  email: string & Email;
  age: number & Min<0> & Max<150>;
}

tsgonest offers shorter aliases (Min vs Minimum, Email vs Format<"email">) and compound types like Range, Between, and Length.

tsgonest also recognizes typia's "typia.tag" branded type convention. If you're migrating from typia, your existing branded types will be detected automatically.

Validation

// Typia — explicit calls required
import typia from 'typia';

@Controller('users')
export class UserController {
  @Post()
  create(@Body() input: CreateUserDto) {
    const dto = typia.assert<CreateUserDto>(input); // must call explicitly
    return this.userService.create(dto);
  }
}
// tsgonest — automatic via pipe
import { TsgonestValidationPipe } from '@tsgonest/runtime';

// In main.ts — one line, applies to all routes:
app.useGlobalPipes(new TsgonestValidationPipe({ distDir: 'dist' }));

// Controller — no validation code needed:
@Controller('users')
export class UserController {
  @Post()
  create(@Body() dto: CreateUserDto) {
    // dto is already validated
    return this.userService.create(dto);
  }
}

Nestia vs standard NestJS decorators

Nestia replaces NestJS's standard decorators with its own:

// Nestia — replaces @Body, @Query, @Param etc.
import { TypedBody, TypedRoute } from '@nestia/core';

@Controller('users')
export class UserController {
  @TypedRoute.Post()
  create(@TypedBody() dto: CreateUserDto): UserResponse {
    return this.userService.create(dto);
  }
}
// tsgonest — uses standard NestJS decorators
import { Controller, Post, Body } from '@nestjs/common';

@Controller('users')
export class UserController {
  @Post()
  create(@Body() dto: CreateUserDto): UserResponse {
    return this.userService.create(dto);
  }
}

tsgonest works with standard NestJS decorators — you don't need to learn or migrate to a different API.


OpenAPI generation

Nestia

Nestia generates OpenAPI at runtime using @nestia/swagger:

import { NestiaSwaggerComposer } from '@nestia/sdk';

const document = await NestiaSwaggerComposer.document(app, {
  openapi: '3.1',
  output: 'dist/swagger.json',
});

This requires:

  • Booting the NestJS application
  • Runtime reflection of decorated controllers
  • Nestia-specific decorators (@TypedRoute, @TypedBody, etc.)

tsgonest

tsgonest generates OpenAPI at build time via static analysis:

tsgonest.config.json
{
  "openapi": {
    "output": "dist/openapi.json",
    "title": "My API",
    "version": "1.0.0"
  }
}

No application boot. No runtime cost. Standard NestJS decorators.


Serialization

Typia

import typia from 'typia';

// Explicit call at each use site
const json = typia.json.stringify<UserResponse>(user);

Or with Nestia:

@TypedRoute.Get()
findAll(): UserResponse[] {
  // Nestia handles serialization via @TypedRoute
  return this.userService.findAll();
}

tsgonest

// Automatic via interceptor — one line in main.ts
app.useGlobalInterceptors(new TsgonestFastInterceptor({ distDir: 'dist' }));

// Controller — no serialization code needed
@Get()
findAll(): UserResponse[] {
  return this.userService.findAll();
}

Both approaches generate fast string-concatenation-based serializers. The difference is that tsgonest wires serialization automatically via the manifest route map, while typia/nestia requires explicit calls or Nestia-specific decorators.


Setup and tooling

Typia + Nestia setup

npm install typia @nestia/core @nestia/sdk
npx ts-patch install
tsconfig.json
{
  "compilerOptions": {
    "plugins": [
      { "transform": "typia/lib/transform" },
      { "transform": "@nestia/core/lib/transform" }
    ]
  }
}

Requirements:

  • ts-patch must be installed and patching the TypeScript compiler
  • Plugin entries must be added to tsconfig.json
  • Must use the patched tsc — standard tsc won't work
  • IDE support requires ts-patch's language service plugin
  • Can conflict with other TypeScript tooling (SWC, esbuild)

tsgonest setup

npm install tsgonest
tsgonest.config.json
{
  "controllers": { "include": ["src/**/*.controller.ts"] },
  "transforms": { "validation": true, "serialization": true },
  "openapi": { "output": "dist/openapi.json" }
}

That's it. No patches. No plugins. No tsconfig modifications. tsgonest uses its own Go-native compilation pipeline.


Feature comparison table

FeatureTypiaNestiatsgonest
Validationtypia.assert<T>()Via @TypedBody()Generated companions
Serializationtypia.json.stringify<T>()Via @TypedRouteGenerated companions
OpenAPIN/A@nestia/sdk (runtime)Static analysis (build time)
Standard Schema v1Not yetNot yetBuilt-in
Interfaces supportYesYesYes
Type aliases supportYesYesYes
Custom validatorstypia.customValidatorsVia typiaValidate<typeof fn>
Branded typestypia.tags.*Via typia tags@tsgonest/types
Transforms (trim, etc.)Not built-inNot built-inTrim, ToLowerCase, ToUpperCase
CoercionNot built-inNot built-inCoerce type tag
Default valuesNot built-inNot built-inDefault<V> type tag
Per-constraint errorsNot supportedNot supportedMin<{ value: 0, error: "..." }>
Discriminated union optimizationYesVia typiaYes (O(1) switch)
Watch modeExternal toolingExternal toolingBuilt-in tsgonest dev
Build speedtsc speedtsc speed~10x faster (tsgo)
NestJS decoratorsReplaced by NestiaCustom decoratorsStandard NestJS decorators
Compiler requirementts-patchts-patchNone (standalone binary)

Migration from Nestia + Typia

Step 1: Install tsgonest

npm install tsgonest
npm uninstall @nestia/core @nestia/sdk typia ts-patch

Step 2: Remove ts-patch plugin config

tsconfig.json
  "compilerOptions": {
-   "plugins": [
-     { "transform": "typia/lib/transform" },
-     { "transform": "@nestia/core/lib/transform" }
-   ]
  }

Step 3: Replace Nestia decorators

- import { TypedBody, TypedRoute } from '@nestia/core';
+ import { Controller, Post, Body } from '@nestjs/common';

  @Controller('users')
  export class UserController {
-   @TypedRoute.Post()
-   create(@TypedBody() dto: CreateUserDto): UserResponse {
+   @Post()
+   create(@Body() dto: CreateUserDto): UserResponse {
      return this.userService.create(dto);
    }
  }

Step 4: Update branded types (optional)

Your existing typia branded types are auto-detected. But for better DX, you can migrate:

- import { tags } from 'typia';
+ import { Min, Max, Email } from '@tsgonest/types';

  interface CreateUserDto {
-   name: string & tags.MinLength<1> & tags.MaxLength<255>;
-   email: string & tags.Format<"email">;
-   age: number & tags.Minimum<0> & tags.Maximum<150>;
+   name: string & Min<1> & Max<255>;
+   email: string & Email;
+   age: number & Min<0> & Max<150>;
  }

Step 5: Remove explicit typia calls

- import typia from 'typia';

  @Controller('users')
  export class UserController {
    @Post()
-   create(@Body() input: CreateUserDto) {
-     const dto = typia.assert<CreateUserDto>(input);
+   create(@Body() dto: CreateUserDto) {
+     // Validated automatically by TsgonestValidationPipe
      return this.userService.create(dto);
    }
  }

On this page