io-ts: Runtime Type System for TypeScript

2.2.22 · active · verified Sun Apr 19

io-ts is a TypeScript library providing a robust runtime type system, designed for decoding and encoding data at the boundaries of your application. It allows developers to define types once using its codec combinators, which then serve for both static type checking during development and dynamic validation of external data at runtime. This approach significantly reduces common errors when integrating with uncertain data sources like API responses, user input, or configuration files. The current stable version is 2.2.22. The library maintains an active release cadence with frequent patch updates, often introducing and refining "experimental" features in dedicated modules while ensuring a stable core. A key differentiator is its deep integration with `fp-ts`, leveraging functional programming paradigms for highly composable type definitions and comprehensive error handling, promoting a type-safe and resilient approach to data processing.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to define a runtime type for a `User` object, decode both valid and invalid data against that type, and leverage `fp-ts` and `PathReporter` for robust error handling and reporting. It also shows how to compose types with optional fields.

import * as t from 'io-ts';
import { pipe } from 'fp-ts/function';
import * as E from 'fp-ts/Either';
import { PathReporter } from 'io-ts/PathReporter';

// 1. Define a runtime type using io-ts codecs
const User = t.type({
  id: t.number,
  name: t.string,
  email: t.string,
  isAdmin: t.boolean,
  tags: t.array(t.string),
});

// Infer the TypeScript type from the runtime type
type User = t.TypeOf<typeof User>;

// 2. Data to decode
const validUserData = {
  id: 123,
  name: 'Alice',
  email: 'alice@example.com',
  isAdmin: false,
  tags: ['developer', 'typescript'],
};

const invalidUserData = {
  id: 'abc', // Invalid type
  name: 'Bob',
  email: 'bob@example.com',
  isAdmin: 'no', // Invalid type
  tags: [123, 'tester'], // Mixed types
};

// 3. Decode valid data
pipe(
  User.decode(validUserData),
  E.fold(
    (errors) => console.error('Failed to decode valid data:', PathReporter.report(E.left(errors))),
    (user) => {
      console.log('Successfully decoded valid user:', user);
      const _user: User = user; // Type-check confirms 'user' is of type User
      console.log('Is Alice an admin?', _user.isAdmin);
    }
  )
);

// 4. Decode invalid data and report errors
pipe(
  User.decode(invalidUserData),
  E.fold(
    (errors) => console.error('Failed to decode invalid data:', PathReporter.report(E.left(errors))),
    (user) => console.log('Unexpectedly decoded invalid user:', user)
  )
);

// Example with a nullable/optional field
const OptionalUser = t.partial({
  phone: t.string,
});

const UserWithOptionalPhone = t.intersection([User, OptionalUser]);

const userWithPhone = {
    id: 456,
    name: 'Charlie',
    email: 'charlie@example.com',
    isAdmin: true,
    tags: ['lead'],
    phone: '555-1234'
};

pipe(
    UserWithOptionalPhone.decode(userWithPhone),
    E.fold(
        (errors) => console.log('Failed to decode user with phone:', PathReporter.report(E.left(errors))),
        (user) => console.log('Decoded user with phone:', user)
    )
);

view raw JSON →