TL;DR
Choose class-validator for class- and decorator-centric NestJS DTOs. Choose TypeBox when JSON Schema is the shared contract and you want static types plus documented Compile and Check validation APIs. Choose io-ts when codecs, Either, and fp-ts already shape the application's error-handling model.
The current TypeBox package is typebox 1.3.16. The older @sinclair/typebox package has a separate download and release history, so its activity must not be merged into the current package without labeling both series.
Key takeaways
- NestJS documents
ValidationPipewith class-validator and class-transformer for DTO validation. - TypeBox builds JSON Schema-compatible definitions and can Compile reusable validation logic.
- io-ts models runtime validation as decoding and returns fp-ts results that callers handle explicitly.
- For 2026-08-13 through 2026-08-19, class-validator had 9,483,523 downloads, current typebox had 7,428,135, legacy @sinclair/typebox had 98,277,262, and io-ts had 2,277,453.
- Download totals describe package activity for a dated interval; they do not establish correctness, speed, or architectural fit.
- Validation confirms data shape. It does not replace authorization, output encoding, query parameterization, or business-rule checks.
Comparison matrix
| Decision axis | class-validator | TypeBox | io-ts |
|---|---|---|---|
| Primary model | Decorators on classes and DTOs | JSON Schema type builder | Runtime codecs and decoders |
| Type derivation | TypeScript class is the type | Static type derives from schema | Type derives from codec |
| Typical framework fit | NestJS ValidationPipe | Fastify type providers and schema-driven APIs | fp-ts applications |
| Validation result | ValidationError[] or framework response | Check result plus iterable errors | Either-style decode result |
| Async rules | Supported by validator decorators | Keep external async checks outside the schema validator | Compose async work separately from codec decode |
| Documentation reuse | Requires additional metadata/tooling | JSON Schema can feed API documentation tooling | Usually needs a separate documentation adapter |
| Current package identity | class-validator | typebox 1.3.16 | io-ts |
class-validator: decorators and NestJS DTOs
class-validator attaches rules to class properties. This matches applications where request contracts are already expressed as DTO classes and where NestJS owns transformation and validation at the route seam.
import { IsEmail, IsInt, IsString, Min } from 'class-validator'
export class CreateAlertDto {
@IsString()
packageName!: string
@IsInt()
@Min(1)
threshold!: number
@IsEmail()
email!: string
}
NestJS's documented ValidationPipe can transform inputs, remove properties without decorators with whitelist, or reject them with forbidNonWhitelisted. Those options are policy decisions, not cosmetic settings. A public API should decide whether unknown fields are ignored, stripped, or rejected and apply the same rule across endpoints.
class-validator errors include property and constraint information. The default message password must be longer than or equal to 8 characters may be useful during development, but public APIs should map internal validation details to a stable error contract.
Use class-validator when classes are already part of the domain and framework model. Outside that context, remember that plain JSON is not a class instance until the application transforms it.
TypeBox: one JSON Schema-oriented definition
TypeBox targets teams that want runtime schema data and TypeScript types from the same definition. The current package uses typebox; older examples that import @sinclair/typebox belong to the legacy package line.
import Type from 'typebox'
import Schema from 'typebox/schema'
const Package = Type.Object({
name: Type.String({ minLength: 1 }),
weeklyDownloads: Type.Number({ minimum: 0 }),
})
const PackageValidator = Schema.Compile(Package)
if (!PackageValidator.Check(input)) {
const errors = [...PackageValidator.Errors(input)]
throw new Error('Package payload failed validation')
}
Compile the schema once and reuse the validator. TypeBox documents compilation as a validation capability; throughput still depends on the schema, runtime, surrounding framework, and error handling. Benchmark the actual request path if validation cost matters.
Fastify documents TypeBox type-provider integration for schema inference. Keep the provider scoped correctly, and test request and response schemas through Fastify rather than assuming a standalone TypeBox check reproduces every framework behavior.
TypeBox error details are precise but should be mapped before they reach users. A raw message such as Expected string to match format 'email' describes a schema constraint, not necessarily the product language your API promises.
io-ts: codecs and explicit decode results
io-ts treats validation as decoding untrusted input into a typed value. That aligns with functional codebases where Either and fp-ts composition are already normal.
import * as t from 'io-ts'
import { isLeft } from 'fp-ts/Either'
const Package = t.type({
name: t.string,
weeklyDownloads: t.number,
})
const decoded = Package.decode(input)
if (isLeft(decoded)) {
throw new Error('Package payload failed validation')
}
const value = decoded.right
The explicit success/failure branch makes error ownership clear. It also adds ceremony for teams that do not otherwise use fp-ts. The diagnostic Invalid value undefined supplied to: Package/weeklyDownloads: number is suitable for logs, but an API response should use a stable field/code/message shape.
io-ts is strongest when codecs can participate in an existing functional pipeline. Adopting fp-ts solely for one validation call often gives the team more concepts to maintain than the validator saves.
Package activity without package-identity mistakes
The official npm API values checked on 2026-08-21 cover the same 2026-08-13 through 2026-08-19 interval:
| Package series | Weekly downloads |
|---|---|
| class-validator | 9,483,523 |
| current typebox | 7,428,135 |
| legacy @sinclair/typebox | 98,277,262 |
| io-ts | 2,277,453 |
The two TypeBox rows are intentionally separate. Existing applications may still install the scoped legacy package while new documentation refers to the current package. Combining those rows would hide the package transition and produce a misleading comparison.
Migration notes
class-validator to TypeBox
- Inventory transformation behavior separately from validation behavior.
- Translate DTO constraints into JSON Schema rules.
- Decide how unknown properties are handled.
- Move database lookups and other async business checks outside the synchronous schema validator.
- Add request, response, and OpenAPI snapshot tests before removing DTO decorators.
legacy @sinclair/typebox to current typebox
- Read the current package migration and release documentation.
- Update imports in a small branch rather than changing package identity and schema design together.
- Compile and Check representative schemas.
- Compare emitted schema objects and error paths for existing payloads.
- Keep the old and new package names separate in monitoring until the migration is complete.
io-ts to another schema system
- List every codec that also performs transformation or custom encoding.
- Preserve domain-specific error mapping before replacing
Eitherhandling. - Port recursive, union, and branded types with focused fixtures.
- Verify both accepted and rejected payloads; type-checking alone does not exercise runtime decoding.
Security notes
Validation is an input seam, not an authorization system. Validate at the boundary, then apply tenant, ownership, role, and state-transition rules in the domain layer. Reject or strip unexpected properties deliberately so callers cannot smuggle fields into mass-assignment paths.
Do not return raw stack traces, internal schema paths, or rejected secrets. Cap request size and collection length before running expensive nested validation. If a custom validator fetches https://registry.npmjs.org/${name}, normalize and encode the package name, apply timeouts, and treat the network response as untrusted.
Keep schema compilation outside request handlers. Test pathological nested and union inputs if the endpoint is exposed to anonymous traffic. For all three libraries, update dependencies through reviewed lockfile changes and run application-level tests after validation-package upgrades.
Methodology
The source ledger below records the official materials used for this refresh.
Sources
This refresh used official project and framework documentation plus official npm API and registry responses. Volatile package identity, version, license, and download data were rechecked on 2026-08-21. No synthetic performance benchmark was used.
Primary sources:
Compare package health on PkgPulse. Related guides: OpenAPI libraries for Node.js, lightweight CLI argument parsers, and JavaScript AST parsers.
