Serialization & Runtime
Fast JSON serializers and compile-time NestJS integration.
tsgonest generates fast, schema-aware JSON serializers alongside validators. Serialization is injected at compile time — no runtime interceptors or pipes needed.
How it works
When transforms.serialization is enabled, each companion file includes a serialize* function that converts a typed object to a JSON string using string concatenation with known property names — faster than JSON.stringify for typical API payloads.
The generated serializers know the exact shape of your types at build time, so they skip the runtime overhead of key enumeration, type detection, and circular reference handling.
Performance
Benchmarked on Apple Silicon, Node.js 25:
| Scenario | Speedup vs JSON.stringify |
|---|---|
| Simple DTO (5 fields, clean strings) | ~1.4x faster |
| Complex DTO (nested + arrays) | ~1x (comparable) |
| Validation vs class-validator | 25-84x faster |
Serialization wins on simple, flat objects — the vast majority of API DTOs. For deeply nested objects, V8's native JSON.stringify is highly optimized and performs comparably.
Supported types
| Type | Strategy |
|---|---|
string | Fast-path scan, falls back to JSON.stringify only when special characters are found |
number, bigint, boolean, null | Direct concatenation |
| Objects | Property-by-property concatenation |
| Arrays | Loop-based string accumulation |
| Tuples | Element-by-element serialization |
| Optionals | Conditional inclusion with separator tracking |
| Discriminated unions | O(1) switch on discriminant, per-branch serialization |
| Nested objects | Recursive serialization calls |
| Complex unions | Falls back to JSON.stringify |
Compile-time NestJS integration
tsgonest injects validation and serialization directly into your controller JavaScript at build time. No runtime pipes, interceptors, or configuration needed.
What gets injected
- Validation:
@Body(),@Query(),@Param(), and@Headers()parameters are wrapped withassert*()calls. Individual@Query('name')and@Param('name')scalars get inline coercion (string to number/boolean). - Serialization: Return values are wrapped with serializer calls that use the generated fast serializers.
@Controller('users')
export class UserController {
@Post()
create(@Body() dto: CreateUserDto): UserResponse {
return this.userService.create(dto);
}
@Get()
findAll(): UserResponse[] {
return this.userService.findAll();
}
}import { assertCreateUserDto } from './user.dto.CreateUserDto.tsgonest.js';
import { serializeUserResponse } from './user.dto.UserResponse.tsgonest.js';
class UserController {
create(dto) {
dto = assertCreateUserDto(dto);
return serializeUserResponse(this.userService.create(dto));
}
findAll() {
return this.userService.findAll().map(serializeUserResponse);
}
}Zero runtime setup
Your main.ts stays clean:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();Array responses
Array responses are handled automatically. When the return type is T[], the serializer wraps the return with .map(serialize*) instead of a single call.
@tsgonest/runtime
The @tsgonest/runtime package provides configuration helpers and error types:
npm install tsgonestExports
import {
// Config helper
defineConfig,
TsgonestConfig,
// Errors
TsgonestValidationError,
ValidationErrorDetail,
// FormData (multipart/form-data)
FormDataBody,
FormDataInterceptor,
TSGONEST_FORM_DATA_FACTORY,
// SSE (Server-Sent Events)
EventStream, // @EventStream() method decorator
SseEvent, // SseEvent<E, T> typed event interface (type-only)
SseEvents, // SseEvents<M> discriminated union helper (type-only)
TsgonestSseInterceptor, // Iterator -> Observable bridge (auto-injected)
} from '@tsgonest/runtime';TsgonestValidationError
When validation fails, the injected assert*() call throws a TsgonestValidationError:
import { TsgonestValidationError } from '@tsgonest/runtime';
interface ValidationErrorDetail {
path: string; // e.g., "input.email"
expected: string; // e.g., "string (email)"
received: string; // e.g., "number"
}NestJS catches the thrown error and returns a 400 Bad Request response:
{
"statusCode": 400,
"message": "Validation failed",
"errors": [
{
"property": "input.email",
"constraints": {
"tsgonest": "expected string (email), received string"
}
}
]
}