bookish-potato-dto v1.0.0
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
- Usage Guide
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@mail.com" // 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. |
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. |
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
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
5 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago