Skip to main content

Request Validation

Muzu provides a powerful, zero-dependency validation system with build-time compilation for maximum performance.

Quick Start

Define DTOs using validation decorators:

import {
Controller,
Post,
ValidateBody,
IsString,
IsEmail,
IsNumber,
Min,
Max,
MinLength
} from 'muzu';

class CreateUserDto {
@IsString()
@MinLength(3)
username: string;

@IsEmail()
email: string;

@IsNumber()
@Min(18)
@Max(100)
age: number;
}

@Controller('/users')
class UserController {
@Post()
@ValidateBody(CreateUserDto)
createUser(req: Request) {
// If validation passes, req.body is guaranteed to be valid
const { username, email, age } = req.body;

return {
id: '123',
username,
email,
age
};
}
}

Validation Decorators

Type Validators

Validate basic types:

class UserDto {
@IsString()
name: string;

@IsNumber()
age: number;

@IsBoolean()
active: boolean;

@IsDate()
birthDate: Date;

@IsInt()
score: number; // Must be integer
}

String Validators

Validate string properties:

class ArticleDto {
@IsString()
@MinLength(5)
@MaxLength(100)
title: string;

@IsEmail()
authorEmail: string;

@IsUrl()
websiteUrl: string;

@IsUUID()
id: string;

@Matches(/^[a-z0-9-]+$/)
slug: string;
}

Number Validators

Validate numeric properties:

class ProductDto {
@IsNumber()
@Min(0)
@Max(999999)
price: number;

@IsInt()
@Min(0)
stock: number;

@IsPositive()
rating: number; // Must be > 0

@IsNegative()
discount: number; // Must be < 0
}

Array Validators

Validate array properties:

class PostDto {
@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(10)
@ArrayItem(() => TagDto)
tags: TagDto[];
}

class TagDto {
@IsString()
@MinLength(1)
name: string;
}

Body Validation

Validate request body using @ValidateBody:

class CreateProductDto {
@IsString()
@MinLength(3)
name: string;

@IsNumber()
@Min(0)
price: number;

@IsString()
description: string;
}

@Controller('/products')
class ProductController {
@Post()
@ValidateBody(CreateProductDto)
createProduct(req: Request) {
// req.body is validated
return {
id: '123',
...req.body
};
}
}

Query Validation

Validate query parameters using @ValidateQuery:

class SearchQueryDto {
@IsString()
@MinLength(1)
query: string;

@IsNumber()
@Min(1)
@Max(100)
limit?: number;

@IsString()
sort?: string;
}

@Controller('/search')
class SearchController {
@Get()
@ValidateQuery(SearchQueryDto)
search(req: Request) {
const { query, limit, sort } = req.params;

return {
results: [],
query,
limit: limit || 10,
sort: sort || 'desc'
};
}
}

Optional Fields

Use @IsOptional() for optional fields:

class UpdateUserDto {
@IsString()
@MinLength(3)
@IsOptional()
username?: string;

@IsEmail()
@IsOptional()
email?: string;

@IsNumber()
@Min(18)
@IsOptional()
age?: number;
}

Required Fields

Use @IsRequired() to explicitly mark required fields:

class CreateUserDto {
@IsString()
@IsRequired()
username: string;

@IsEmail()
@IsRequired()
email: string;

@IsString()
@IsOptional()
bio?: string;
}

Enum Validation

Validate against enum values:

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

class CreateUserDto {
@IsString()
username: string;

@IsEnum(UserRole)
role: UserRole;
}

Nested Object Validation

Validate nested objects:

class AddressDto {
@IsString()
street: string;

@IsString()
city: string;

@IsString()
country: string;
}

class CreateUserDto {
@IsString()
name: string;

@ValidateNested()
address: AddressDto;
}

Array of Objects Validation

Validate arrays of nested objects:

class TagDto {
@IsString()
@MinLength(1)
name: string;
}

class CreatePostDto {
@IsString()
@MinLength(5)
title: string;

@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(10)
@ArrayItem(() => TagDto)
tags: TagDto[];
}

@Controller('/posts')
class PostController {
@Post()
@ValidateBody(CreatePostDto)
createPost(req: Request) {
const { title, tags } = req.body;
return {
id: '123',
title,
tags
};
}
}

Validation Error Response

When validation fails, Muzu returns a detailed error response:

{
"status": 400,
"message": "Body validation failed",
"kind": "MuzuException",
"errors": [
{
"field": "username",
"constraint": "minLength",
"value": "ab",
"message": "username must be at least 3 characters"
},
{
"field": "email",
"constraint": "isEmail",
"value": "not-an-email",
"message": "email must be a valid email"
},
{
"field": "age",
"constraint": "min",
"value": 15,
"message": "age must be at least 18"
}
]
}

All Available Validators

Type Validators

  • @IsString() - Must be a string
  • @IsNumber() - Must be a number (excludes NaN)
  • @IsBoolean() - Must be a boolean
  • @IsDate() - Must be a Date instance
  • @IsInt() - Must be an integer

String Validators

  • @MinLength(length) - Minimum string length
  • @MaxLength(length) - Maximum string length
  • @IsEmail() - Must be valid email format
  • @IsUrl() - Must be valid URL format
  • @IsUUID() - Must be valid UUID format
  • @Matches(pattern) - Must match regex pattern

Number Validators

  • @Min(value) - Minimum number value
  • @Max(value) - Maximum number value
  • @IsPositive() - Must be positive (> 0)
  • @IsNegative() - Must be negative (< 0)

Array Validators

  • @IsArray() - Must be an array
  • @ArrayMinSize(size) - Minimum array length
  • @ArrayMaxSize(size) - Maximum array length
  • @ArrayItem(DtoClass) - Validate array items

Other Validators

  • @IsOptional() - Field is optional
  • @IsRequired() - Field is required
  • @IsEnum(enumObject) - Must be enum value
  • @ValidateNested() - Validate nested object

Performance

Muzu's validation is extremely fast due to build-time compilation:

Build-Time Compilation

Validators are compiled to optimized functions at startup:

// Your code
class UserDto {
@IsString()
@MinLength(3)
username: string;

@IsEmail()
email: string;
}

// Compiled to (simplified)
function validateUser(data) {
const errors = [];

if (typeof data.username !== 'string') {
errors.push({ field: 'username', constraint: 'isString', ... });
}
if (data.username.length < 3) {
errors.push({ field: 'username', constraint: 'minLength', ... });
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.push({ field: 'email', constraint: 'isEmail', ... });
}

return errors;
}

Performance Characteristics

  • No function call overhead - All checks are inlined
  • Single-pass validation - All fields validated in one iteration
  • Zero runtime dependencies - Pure TypeScript validation code
  • ~5% overhead compared to no validation (vs 50-100% with traditional validators)

Complex Validation Example

Combining multiple validators:

class CreateOrderDto {
@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(20)
@ArrayItem(() => OrderItemDto)
items: OrderItemDto[];

@IsString()
@MinLength(10)
@MaxLength(200)
shippingAddress: string;

@IsEnum(PaymentMethod)
paymentMethod: PaymentMethod;

@IsString()
@IsOptional()
@MaxLength(500)
notes?: string;
}

class OrderItemDto {
@IsString()
@IsUUID()
productId: string;

@IsInt()
@Min(1)
@Max(100)
quantity: number;

@IsNumber()
@Min(0)
price: number;
}

enum PaymentMethod {
CREDIT_CARD = 'credit_card',
PAYPAL = 'paypal',
BANK_TRANSFER = 'bank_transfer'
}

@Controller('/orders')
class OrderController {
@Post()
@ValidateBody(CreateOrderDto)
createOrder(req: Request) {
const order = req.body;
// Order is fully validated
return {
orderId: '123',
total: calculateTotal(order.items)
};
}
}

Best Practices

Use DTOs for All Inputs

// ✅ Good - Validated DTO
class CreateUserDto {
@IsString()
username: string;

@IsEmail()
email: string;
}

@Post()
@ValidateBody(CreateUserDto)
createUser(req: Request) {
// Type-safe and validated
const { username, email } = req.body;
}

// ❌ Bad - No validation
@Post()
createUser(req: Request) {
const { username, email } = req.body; // Unvalidated!
}

Separate DTOs by Purpose

// ✅ Good - Separate DTOs
class CreateUserDto {
@IsString()
username: string;

@IsString()
password: string;
}

class UpdateUserDto {
@IsString()
@IsOptional()
username?: string;

@IsEmail()
@IsOptional()
email?: string;
}

// ❌ Bad - Single DTO with all optional
class UserDto {
@IsString()
@IsOptional()
username?: string;

@IsString()
@IsOptional()
password?: string; // Should be required for create!
}

Reuse Common DTOs

// ✅ Good - Reusable DTOs
class PaginationDto {
@IsNumber()
@Min(1)
page: number;

@IsNumber()
@Min(1)
@Max(100)
limit: number;
}

@Get('/users')
@ValidateQuery(PaginationDto)
getUsers() { /* ... */ }

@Get('/products')
@ValidateQuery(PaginationDto)
getProducts() { /* ... */ }

Next Steps