Validation
Generated runtime validators from TypeScript types — JSDoc tags and branded phantom types.
tsgonest generates companion files (.tsgonest.js / .tsgonest.d.ts) alongside your compiled TypeScript output. Each companion exports runtime validation functions derived entirely from your type definitions — no schemas to write, no decorators to maintain.
Companion files
Running tsgonest build analyzes your TypeScript types and emits one companion file per exported type:
dist/
user.dto.js ← tsgo output
user.dto.CreateUserDto.tsgonest.js ← generated validator + serializer
user.dto.CreateUserDto.tsgonest.d.ts ← generated type declarationsEach companion exports three functions:
| Function | Returns | On failure |
|---|---|---|
validate | { success, data?, errors? } | Returns error details |
assert | Validated T | Throws error |
serialize | JSON-safe object | — |
validate — safe parsing
validate returns a result object and never throws. Use it when you need to handle errors programmatically.
import { validateCreateUserDto } from './user.dto.CreateUserDto.tsgonest.js';
const result = validateCreateUserDto(input);
if (result.success) {
// result.data is typed as CreateUserDto
console.log(result.data.email);
} else {
// result.errors is ValidationErrorDetail[]
for (const err of result.errors) {
console.log(`${err.path}: expected ${err.expected}, received ${err.received}`);
}
}The errors array contains objects with the following shape:
interface ValidationErrorDetail {
path: string; // e.g. "address.zip"
expected: string; // what was expected
received: string; // what was received
}assert — throw on failure
assert returns the validated data directly or throws a TsgonestValidationError. Use it when you want exceptions to propagate.
import { assertCreateUserDto } from './user.dto.CreateUserDto.tsgonest.js';
try {
const user = assertCreateUserDto(input);
// user is typed as CreateUserDto
} catch (err) {
// err is TsgonestValidationError
console.log(err.errors); // ValidationErrorDetail[]
}Two approaches to constraints
tsgonest supports two complementary ways to define validation constraints on your types.
1. JSDoc tags — zero dependencies
Write constraints as JSDoc tags directly in your type comments. No imports, no packages, no runtime footprint.
interface CreateUserDto {
/** @format email */
email: string;
/** @minLength 8 @maxLength 128 */
password: string;
/** @minimum 0 @maximum 150 */
age: number;
}2. Branded phantom types — @tsgonest/types
Install @tsgonest/types and use intersection types for a fully type-safe, IDE-friendly experience. The branded types are phantom — they erase at runtime and add zero cost.
npm install @tsgonest/typesimport { Email, MinLength, MaxLength, Min, Max } from '@tsgonest/types';
interface CreateUserDto {
email: string & Email;
password: string & MinLength<8> & MaxLength<128>;
age: number & Min<0> & Max<150>;
}Both approaches produce identical runtime validators. You can even mix them in the same interface.
NestJS integration
tsgonest automatically injects validation into your controllers at compile time. When you build with tsgonest build, the controller rewriter adds assertTypeName() calls for @Body(), @Query(), @Param(), and @Headers() parameters — no pipes or runtime setup needed.
@Controller('users')
export class UserController {
@Post()
create(@Body() dto: CreateUserDto) {
// dto is automatically validated at compile time via injected assertCreateUserDto()
return this.userService.create(dto);
}
@Get()
findAll(@Query() query: PaginationDto) {
// query is validated and number/boolean fields are auto-coerced from strings
return this.userService.findAll(query);
}
@Get(':id')
findOne(@Param('id') id: number) {
// id is auto-coerced from string to number with NaN checking
return this.userService.findOne(id);
}
}On validation failure, a TsgonestValidationError is thrown with structured error details (400 Bad Request).
Factory decorators are not supported
tsgonest uses static analysis to detect NestJS decorators. If you create factory functions that return core decorators (e.g., a function that wraps @Body(), @Query(), @Get(), @Post(), etc.), tsgonest cannot introspect them and will skip validation/coercion injection for those parameters.
// This works — tsgonest detects @Body() directly
create(@Body() dto: CreateUserDto) { ... }
// This does NOT work — tsgonest cannot see through the factory
const MyBody = () => Body();
create(@MyBody() dto: CreateUserDto) { ... } // no validation injectedImport aliases (import { Body as NestBody }) are supported — tsgonest resolves the original symbol.
Multipart / form-data
For file upload endpoints using multipart/form-data, use @FormDataBody() with FormDataInterceptor from @tsgonest/runtime:
import { Controller, Post, UseInterceptors } from '@nestjs/common';
import { FormDataBody, FormDataInterceptor } from '@tsgonest/runtime';
@Controller('uploads')
export class UploadController {
@Post()
@UseInterceptors(FormDataInterceptor)
upload(@FormDataBody(() => imageMulter()) body: UploadDto) {
// body.image is the parsed file, body.name is a text field
return this.uploadService.process(body);
}
}@FormDataBody() accepts a multer factory function. The FormDataInterceptor runs multer to parse the multipart request and merges files into the request body. This is a drop-in replacement for Nestia's @TypedFormData.Body() — tsgonest migrate converts it automatically.
Next steps
JSDoc Tags
Zero-dependency validation constraints using JSDoc comments.
String Tags
Branded types for string formats, length, and patterns.
Numeric Tags
Branded types for numeric ranges, types, and precision.
Array Tags
Branded types for array length and uniqueness.
Transforms & Coercion
Pre-validation transforms, type coercion, and defaults.
Custom Validators
Custom validation functions, error messages, and complex types.