Skip to main content
This guide will walk you through installing decoders, creating your first decoder, and validating data.

Installation

Install decoders using your preferred package manager:
npm install decoders
Make sure you have "strict": true in your tsconfig.json for proper type inference.

Your first decoder

Let’s create a simple decoder to validate user data.
1

Import decoder functions

Start by importing the decoder functions you need:
import { object, string, number, optional } from 'decoders';
These functions are the building blocks for creating decoders.
2

Define your decoder

Create a decoder that describes the structure of your data:
const userDecoder = object({
  id: number,
  name: string,
  email: optional(string),
});
This decoder expects an object with:
  • id - a number (required)
  • name - a string (required)
  • email - an optional string
3

Validate data

Use .verify() to validate data and get a type-safe result:
const externalData = {
  id: 123,
  name: 'Alice',
  email: '[email protected]',
};

const user = userDecoder.verify(externalData);
// TypeScript knows: { id: number; name: string; email?: string }

console.log(user.name); // 'Alice'
If validation fails, .verify() throws an error with a detailed message.
4

Handle validation errors

For non-throwing validation, use .decode() to get a result object:
const result = userDecoder.decode(externalData);

if (result.ok) {
  console.log('Valid user:', result.value);
} else {
  console.error('Validation failed:', result.error);
}
The result is a discriminated union that you can check with result.ok.

Complete example

Here’s a complete working example you can run:
import { object, string, number, optional, array } from 'decoders';

// Define a decoder for user objects
const userDecoder = object({
  id: number,
  name: string,
  email: optional(string),
  age: optional(number),
  roles: array(string),
});

// Some data that needs validation (from API, user input, etc.)
const externalData = {
  id: 123,
  name: 'Alice Roberts',
  email: '[email protected]',
  roles: ['admin', 'user'],
};

// Validate and use the data
try {
  const user = userDecoder.verify(externalData);
  console.log('Valid user!');
  console.log('Name:', user.name);
  console.log('Roles:', user.roles);
  // TypeScript knows all the types correctly!
} catch (error) {
  console.error('Invalid user data:', error);
}

Common patterns

Validating API responses

Use decoders to validate data from external APIs:
import { object, string, number, array } from 'decoders';

const postDecoder = object({
  id: number,
  title: string,
  body: string,
  userId: number,
});

async function fetchPost(id: number) {
  const response = await fetch(`https://api.example.com/posts/${id}`);
  const data = await response.json();
  
  // Validate the API response
  return postDecoder.verify(data);
}

// Now you have a type-safe post object
const post = await fetchPost(1);
console.log(post.title); // TypeScript knows this is a string

Nested objects

Handle complex nested structures:
import { object, string, number, array, optional } from 'decoders';

const addressDecoder = object({
  street: string,
  city: string,
  zipCode: string,
});

const companyDecoder = object({
  name: string,
  address: addressDecoder, // Nested decoder
  employees: array(
    object({
      id: number,
      name: string,
      role: string,
    })
  ),
});

const company = companyDecoder.verify(externalData);
console.log(company.address.city); // Fully type-safe

Optional fields with defaults

Provide default values for optional fields:
import { object, string, number, optional } from 'decoders';

const configDecoder = object({
  host: string,
  port: optional(number, 3000), // Default to 3000 if missing
  debug: optional(boolean, false), // Default to false
});

const config = configDecoder.verify({ host: 'localhost' });
console.log(config.port); // 3000 (default value)
console.log(config.debug); // false (default value)

Handling errors

When validation fails, you get detailed error messages:
import { object, string, number } from 'decoders';

const userDecoder = object({
  id: number,
  name: string,
});

const invalidData = {
  id: '123', // Wrong type - should be number
  name: 'Alice',
};

const result = userDecoder.decode(invalidData);

if (!result.ok) {
  console.log(result.error);
  // Shows exactly what failed and where
}
Error messages include:
  • Which field failed validation
  • What type was expected vs. what was received
  • The path to the failed field in nested objects

Next steps

Now that you’ve created your first decoder, explore more features:

Core concepts

Understand how decoders work under the hood

Basic decoders

Learn about all the built-in primitive decoders

Object validation

Deep dive into validating complex objects

Custom decoders

Build your own decoders for custom types
Need help? Check out the API reference or explore more guides.

Build docs developers (and LLMs) love