npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bookish-potato-dto

v1.3.0

Published

## Overview A TypeScript decorators-based API for defining Data Transfer Object (DTO) classes, types, and parsers. Simplifies schema validation and type enforcement using intuitive decorators and TypeScript classes.

Readme

TypeScript Decorators API for DTOs

Overview

A TypeScript decorators-based API for defining Data Transfer Object (DTO) classes, types, and parsers. Simplifies schema validation and type enforcement using intuitive decorators and TypeScript classes.

Table of Contents

Feature Requests, Bugs Reports, and Contributions

Please use the GitHub Issues repository to report bugs, request features, or ask questions.

Usage Guide

Defining DTOs

How to define DTO classes with decorators.

Decorators allow you to define properties with validation and transformation rules:

class PersonDTO {
  @StringProperty()
  readonly name!: string;

  @IntegerProperty({
    strictDataTypes: true,
  })
  readonly age!: number;

  @NumberProperty()
  readonly height!: number;

  @NumberProperty({
    defaultValue: 70
  })
  readonly weight!: number;

  @StringProperty({
    isOptional: true
  })
  readonly eyeColor?: string;
  
  @BooleanProperty()
  readonly active!: boolean;
}

const person = parseObject(PersonDTO, {
  name: "John Doe",
  age: 30,
  height: "180.5",
  active: true,
  email: "[email protected]" // won't be assigned to the object as it is not a property of PersonDTO
});

// person is now an instance of PersonDTO with the values set.
// person.name === "John Doe"
// person.age === 30
// person.height === 180
// person.active === true
// person.weight === 70
// person.eyeColor === undefined

Supported types and examples.

String
class ExampleDTO {
    @StringProperty()
    readonly name!: string;
}

The StringProperty decorator validates and assigns a required string value to the property, ensuring it is of type string.

Regular Expressions
class ExampleDTO {
  @RegexProperty(/^[a-zA-Z0-9]{3,10}$/)
  readonly name!: string;
}

The RegexProperty decorator validates the string value against a regular expression.

Number
class ExampleDTO {
    @NumberProperty()
    readonly age!: number;
}

The NumberProperty decorator validates and assigns a required number value to the property, ensuring it is of type number.

Integer
class ExampleDTO {
    @IntegerProperty()
    readonly height!: number;
}

The IntegerProperty decorator validates and assigns a required integer value. Strings are converted to integers if possible.

Boolean
class ExampleDTO {
    @BooleanProperty()
    readonly active!: boolean;
}

The BooleanProperty decorator validates and assigns a required boolean value to the property. Strings such as "true" and "false" will be converted to boolean values by default.

Enum
enum Colors {
    Red = 'red',
    Green = 'green',
    Blue = 'blue'
}

enum ColorType {
    Primary = 1,
    Secondary = 2
}

class ButtonDTO {
    @EnumProperty(Colors)
    readonly color!: Colors;
    
    @EnumProperty(ColorType)
    readonly colorType!: ColorType;
}

const example = parseObject(ExampleDTO, {
  color: 'red',
  colorType: 1
});

// example.color === Colors.Red
// example.colorType === ColorType.Primary

The EnumProperty decorator validates and assigns a required enum value to the property.

Date
class ExampleDTO {
    @DateProperty()
    readonly date!: Date;
    
    @DateProperty({
        defaultValue: new Date('2022-01-01')
    })
    readonly defaultDate!: Date;
    
    @DateProperty({
        defaultValue: "January 3, 2025"
    })
    readonly stringDate!: Date;
}

The DateProperty decorator validates and assigns a required date value to the property.

Arrays of primitives
class ExampleDTO {
    @ArrayProperty('string')
    readonly names!: string[];
    
    @ArrayProperty('number')
    readonly ages!: number[];
    
    @ArrayProperty('boolean')
    readonly active!: boolean[];
}

The ArrayProperty decorator validates arrays of primitive types such as string, number, or boolean.

Arrays of DTOs
class AddressDTO {
  @StringProperty()
  readonly street!: string;
}

class PersonDTO {
  @ArrayDtoProperty(AddressDTO)
  readonly addresses!: AddressDTO[];
}

The ArrayDtoProperty decorator is used for arrays of DTOs.

Nested DTOs
class AddressDTO {
  @StringProperty()
  readonly street!: string;
}

class PersonDTO {
  @DtoProperty(AddressDTO)
  readonly address!: AddressDTO;
}

The DtoProperty decorator is used for nested DTOs.

Custom
class CustomParser implements PropertyParser<boolean> {
  parse(value: unknown): boolean {
    if (value === "1") return true;
    if (value === "0") return false;
    throw new Error("Invalid value: " + value);
  }
}

class PersonDTO {
  @CustomProperty({ parser: new CustomParser() })
  readonly active!: boolean;
}

const example = parseObject(PersonDTO, {
  active: '1'
});

// example.active === true

Extending DTOs

Use extends keyword to extend DTOs.

class PersonDTO {
  @StringProperty()
  readonly name!: string;

  @IntegerProperty()
  readonly age!: number;
}

class EmployeeDTO extends PersonDTO {
  @StringProperty()
  readonly position!: string;
}

Decorating the DTO class (deprecated)

(!) The DtoClass decorator is deprecated. Use the extends keyword instead..

You can extend DTOs to reuse properties and add new ones. The following example demonstrates how to extend a DTO:

class PersonDTO {
  @StringProperty()
  readonly name!: string;

  @IntegerProperty()
  readonly age!: number;
}

@DtoClass({
  extends: [PersonDTO]
})
class EmployeeDTO {
  @StringProperty()
  readonly position!: string;
}

The DtoClass decorator extends a DTO class using the extends option to specify the parent class(es). Multiple parents can be defined as well.

Note: Parent and child DTO classes cannot have overlapping property names. Defining properties with the same name in both will cause an error.

Possible Use Cases

  • API requests and responses: Validate and parse API requests and responses.
  • Configuration files: Validate and parse configuration files.
  • Data transformation: Transform data from one format to another.

API requests and responses

The following example demonstrates how to use DTOs to validate and parse API requests and responses:


enum Roles {
  ADMIN = 'admin',
  USER = 'user',
  GUEST = 'guest',
}

class RoleDTO {
  @StringProperty()
  readonly name!: string;

  @StringProperty()
  readonly description!: string;
  
  @EnumProperty(Roles)
  readonly role!: Roles;
}

class UserDTO {
  @StringProperty()
  readonly name!: string;

  @StringProperty()
  readonly email!: string;

  @IntegerProperty()
  readonly age!: number;
  
  @DtoProperty(RoleDTO)
  readonly role!: RoleDTO;
}

const req = fetch('https://api.example.com/user/1')
  .then(response => response.json())
  .then(data => {
    const user = parseObject(UserDTO, data);
    console.log('User:', user);
  });

Configuration files

The following example demonstrates how to use DTOs to validate and parse configuration files.

Imagine you have the following environment variables:

PORT=8080
LOG_LEVEL=debug
MAX_CONNECTIONS=20
DATA_BASE_SECRET=secret
WHITE_LISTED_URLS=example.com,example.org

You can use the following DTO to parse the environment variables:

enum LogLevel {
  INFO = 'info',
  DEBUG = 'debug',
  WARN = 'warn',
  ERROR = 'error',
}

class ArrayParser implements PropertyParser<string[]> {

  constructor(private readonly separator: string = ',') {
  }

  parse(value: unknown): string[] {

    if (typeof value !== 'string') {
      throw new ParsingError(`Value is not a string! Cannot parse to array.`);
    }

    const array = value.split(this.separator);
    if (array.length === 1 && array[0] === '') {
      return [];
    }

    return array;
  }
}

/**
 * The configuration for the application.
 */
export class EnvironmentConfig {

  /**
   * The port the application should listen on.
   */
  @IntegerProperty({
    mapFrom: 'PORT',
    defaultValue: 3000
  })
  readonly port!: number;

  /**
   * The log level for the application.
   */
  @EnumProperty(LogLevel, {
    mapFrom: 'LOG_LEVEL',
    defaultValue: LogLevel.INFO,
  })
  readonly logLevel!: LogLevel;

  /**
   * The maximum number of connections to the data source.
   */
  @IntegerProperty({
    mapFrom: 'MAX_CONNECTIONS',
    defaultValue: 10,
  })
  readonly maxConnections!: number;

  /**
   * The base URL for the data source.
   */
  @StringProperty({
    mapFrom: 'DATA_BASE_URL',
    defaultValue: 'localhost:5432',
  })
  readonly dataBaseUrl!: string;

  @StringProperty({
    mapFrom: 'DATA_BASE_SECRET',
  })
  readonly dataBaseSecret!: string;

  @CustomProperty({
    mapFrom: 'WHITE_LISTED_URLS',
    parser: new ArrayParser(),
  })
  readonly whiteListedUrls!: string[];
}

// Create an instance of the configuration.
console.log('Parse the environment variables...');
export const environmentConfig =
        parseObject(EnvironmentConfig, process.env);

console.log('Environment configuration:', environmentConfig);
// Output the configuration.
// environmentConfig.port === 8080
// environmentConfig.logLevel === LogLevel.DEBUG
// environmentConfig.maxConnections === 20
// environmentConfig.dataBaseUrl === 'localhost:5432'
// environmentConfig.dataBaseSecret === 'secret'
// environmentConfig.whiteListedUrls === ['example.com', 'example.org']

The advantage of using DTOs is that you can define the configuration schema in one place and reuse it throughout the application.

See also the MiddlewareConfiguration class in the Next.js example

Data transformation

The following example demonstrates how to use DTOs to transform data from one format to another:

interface User {
  readonly uuid: string;
  readonly name: string;
  readonly age: number;
  readonly lastLogin: Date;
}

class Person {
  @StringProperty({
    mapFrom: 'uuid',
  })
  readonly id!: string;

  @StringProperty()
  readonly name!: string;

  @StringProperty({
    defaultValue: 'active',
  })
  readonly status!: string;

  @CustomProperty({
    defaultValue: 1,
    useDefaultValueOnParseError: true,
    mapFrom: 'lastLogin',
    parser: {
      parse(value: unknown): number {
        if (value instanceof Date) {
          return new Date(Date.now()).getDay() - value.getDay();
        }

        throw new ParsingError('Value is not a date!');
      }
    },
  })
  readonly lastSeenOnlineDaysAgo!: number;
}

const user: User = {
  uuid: '123',
  name: 'John Doe',
  age: 30,
  lastLogin: new Date('2024-01-01'),
}

export const person = parseObject(Person, user);
console.log('Person:', person);
// Output the person object.
// person.id === '123'
// person.name === 'John Doe'
// person.status === 'active'
// person.lastSeenOnlineDaysAgo === 2 (some value based on the current date and the last login date)

Configuration

Decorators Options

Common configurable options

| Option | Type | Description | Default Behavior | |----------------|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------| | isOptional | boolean | Indicates if the property is optional. If true, the property is not required. | By default, all properties are required unless specified as optional. | | isNullable | boolean | Indicates if the property can accept null values. If true, null values are allowed; if false, null values will cause a parsing error. | By default, properties cannot be null unless explicitly specified. | | defaultValue | number \| string \| boolean \| (any valid type) | Specifies a default value for the property if it's not provided. The data type should match the expected type. | Default value is empty by default. | | useDefaultValueOnParseError | boolean | If true, the default value is used when parsing fails. If false, an error is thrown. | By default, an error is thrown when parsing fails. | | mapFrom | string | Specifies the key in the input object to map the property from. | By default, the property name is used as the key in the input object. | | parsingErrorMessage | (key: string, valueToParse: unknown, causedBy: unknown) => string | Defines a custom error message function to use when the property cannot be parsed. The function receives the property key, the value being parsed, and the original error. | By default, a standard error message is generated. |

Strict data types

| Option | Type | Description | Default Behavior | |-------------------|-----------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | strictDataTypes | boolean | Enforces strict type matching without conversions. When true, values must match the expected type exactly. | By default, the library will attempt to convert values to the expected type. |

@StringProperty

Includes common options and the following:

| Option | Type | Description | Default Behavior | |------------|-----------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | minLength | number | Specifies the minimum length of the string. | By default, there is no minimum length requirement. | | maxLength | number | Specifies the maximum length of the string. | By default, there is no maximum length requirement. |

@RegexProperty

Includes common options

@NumberProperty

Includes common options, strict data types, and the following:

| Option | Type | Description | Default Behavior | |------------|-----------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | minValue | number | Specifies the minimum value of the number. | By default, there is no minimum value requirement. | | maxValue | number | Specifies the maximum value of the number. | By default, there is no maximum value requirement. |

@IntegerProperty

Includes common options, strict data types, and the following:

| Option | Type | Description | Default Behavior | |------------|-----------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | minValue | number | Specifies the minimum value of the integer. | By default, there is no minimum value requirement. | | maxValue | number | Specifies the maximum value of the integer. | By default, there is no maximum value requirement. |

@BooleanProperty

Includes common options, strict data types

@EnumProperty

Includes common options

@DateProperty

Includes common options

@ArrayProperty

Includes common options, strict data types, and the following:

| Option | Type | Description | Default Behavior | |------------|-----------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | minLength | number | Specifies the minimum length of the array. | By default, there is no minimum length requirement. | | maxLength | number | Specifies the maximum length of the array. | By default, there is no maximum length requirement. | | stringsLength | object | Specifies the minimum and maximum length of the strings in the array. | By default, there is no minimum or maximum length requirement. | | numbersRange | object | Specifies the minimum and maximum value of the numbers in the array. | By default, there is no minimum or maximum value requirement. |

@ArrayDtoProperty

Includes common options and the following:

| Option | Type | Description | Default Behavior | |------------|-----------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | minLength | number | Specifies the minimum length of the array. | By default, there is no minimum length requirement. | | maxLength | number | Specifies the maximum length of the array. | By default, there is no maximum length requirement. |

@DtoProperty

Includes common options

@CustomProperty

Includes common options

@DtoClass - (!) deprecated

| Option | Type | Description | Default Behavior | |------------|-----------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| | extends | Class[] | Specifies the parent classes to extend. | By default, the class does not extend any other classes. |

Parsing and Validation

Using nullable properties

Enable isNullable to allow null values for properties:

class UserDTO {
    @StringProperty()
    readonly name!: string; // Required, cannot be null
    
    @StringProperty({ isNullable: true })
    readonly middleName!: string | null; // Required, but can be null
    
    @StringProperty({ 
        isNullable: true, 
        isOptional: true,
        defaultValue: null 
    })
    readonly nickname?: string | null; // Optional, can be null, defaults to null
}

// This works - providing null for nullable property
const user1 = parseObject(UserDTO, {
    name: "John",
    middleName: null // ✓ Allowed because isNullable: true
});

// This also works - providing valid values
const user2 = parseObject(UserDTO, {
    name: "Jane",
    middleName: "Marie",
    nickname: "Janie"
});

// This throws an error - null for non-nullable property
try {
    const user3 = parseObject(UserDTO, {
        name: null, // ❌ Error: Property cannot be null!
        middleName: "Smith"
    });
} catch (error) {
    console.log(error.message); // Property "name" parsing error
}

// ❌ Common mistake: Setting defaultValue to null without enabling isNullable
class ProblematicUserDTO {
    @StringProperty({ 
        defaultValue: null,  // This will cause an error!
        isOptional: true 
    })
    readonly description?: string; // isNullable is false by default
    
    @StringProperty({ 
        defaultValue: null,  // This will also cause an error!
        isNullable: false,   // Explicitly disabled
        isOptional: true 
    })
    readonly notes?: string;
}

try {
    // This will throw an error because default values are validated too
    const problematicUser = parseObject(ProblematicUserDTO, {});
} catch (error) {
    console.log(error.message); // Property parsing error - default value cannot be null
}

Use strict data types

Enable strictDataTypes to enforce exact type matching without conversions:

class PersonDTO {
    @IntegerProperty({
        strictDataTypes: true
    })
    readonly age!: number;
}

const _person = parseObject(PersonDTO, {
  age: '30' // throws an error since the value is a string
});

const person = parseObject(PersonDTO, {
  age: 30 // works fine
});

Using custom parsers.

You can use the CustomProperty decorator for advanced parsing:

class PersonDTO {
    @CustomProperty({
        parser: new CustomParser()
    })
    readonly active!: boolean;
}

Advanced Features

Custom decorators for additional functionality.

You can create custom decorators to add additional functionality to DTO properties.

See the StringToArrayOfStrings example in the Next.js example

Back to Top

Table of Contents