Validation
Chapter 25: Input Validation & Error Handling
Section titled “Chapter 25: Input Validation & Error Handling”Building Robust TypeScript Applications
Section titled “Building Robust TypeScript Applications”25.1 Validation Overview
Section titled “25.1 Validation Overview”Proper validation and error handling are crucial for building reliable applications. This chapter covers both input validation and comprehensive error handling.
Validation & Error Handling Flow ================================================================================
HTTP Request | v +------------------+ | Input Check | ----> Validate content type +------------------+ | v +------------------+ | DTO Validation | ----> Validate required fields +------------------+ Validate types | Validate ranges v Validate formats +------------------+ | Business Logic | ----> Validate business rules +------------------+ | v +------------------+ | Database | ----> Validate constraints +------------------+ | v HTTP Response
================================================================================25.2 Input Validation Utilities
Section titled “25.2 Input Validation Utilities”// String validatorsexport function isString(value: any): value is string { return typeof value === 'string';}
export function isNotEmpty(value: any): boolean { return isString(value) && value.trim().length > 0;}
export function minLength(value: any, min: number): boolean { return isString(value) && value.length >= min;}
export function maxLength(value: any, max: number): boolean { return isString(value) && value.length <= max;}
export function isEmail(value: any): boolean { if (!isString(value)) return false; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(value);}
export function isUrl(value: any): boolean { if (!isString(value)) return false; try { new URL(value); return true; } catch { return false; }}
// Number validatorsexport function isNumber(value: any): value is number { return typeof value === 'number' && !isNaN(value);}
export function isInteger(value: any): boolean { return isNumber(value) && Number.isInteger(value);}
export function minValue(value: any, min: number): boolean { return isNumber(value) && value >= min;}
export function maxValue(value: any, max: number): boolean { return isNumber(value) && value <= max;}
// Array validatorsexport function isArray(value: any): value is any[] { return Array.isArray(value);}
export function arrayMinLength(value: any, min: number): boolean { return isArray(value) && value.length >= min;}
export function arrayMaxLength(value: any, max: number): boolean { return isArray(value) && value.length <= max;}
// Date validatorsexport function isDate(value: any): value is Date { return value instanceof Date;}
export function isValidDate(value: any): boolean { if (isString(value)) { const date = new Date(value); return !isNaN(date.getTime()); } return isDate(value) && !isNaN(value.getTime());}25.3 Validation Result & Errors
Section titled “25.3 Validation Result & Errors”export interface ValidationErrorItem { field: string; message: string; value?: any;}
export interface ValidationResult { isValid: boolean; errors: ValidationErrorItem[];}
export class ValidationError extends Error { public errors: ValidationErrorItem[];
constructor(errors: ValidationErrorItem[]) { super('Validation failed'); this.name = 'ValidationError'; this.errors = errors; }}
export class ValidationHelper { private errors: ValidationErrorItem[] = [];
addError(field: string, message: string, value?: any): this { this.errors.push({ field, message, value }); return this; }
addErrorIf( condition: boolean, field: string, message: string, value?: any ): this { if (condition) { this.addError(field, message, value); } return this; }
isValid(): boolean { return this.errors.length === 0; }
getErrors(): ValidationErrorItem[] { return this.errors; }
getResult(): ValidationResult { return { isValid: this.isValid(), errors: this.errors, }; }
throwIfInvalid(): void { if (!this.isValid()) { throw new ValidationError(this.errors); } }}25.4 Complete Validator Example
Section titled “25.4 Complete Validator Example”import { isString, isNotEmpty, isEmail, minLength, maxLength, isNumber, minValue, maxValue, isArray, arrayMinLength, arrayMaxLength, isValidDate,} from '../utils/validation.util';import { ValidationHelper } from '../utils/validation-error.util';import { CreateUserDto } from '../dto/create-user.dto';
export function validateCreateUserDto(data: any): ValidationHelper { const v = new ValidationHelper();
// Name validation v.addErrorIf( !isNotEmpty(data.name), 'name', 'Name is required' );
v.addErrorIf( data.name && !minLength(data.name, 2), 'name', 'Name must be at least 2 characters' );
v.addErrorIf( data.name && !maxLength(data.name, 100), 'name', 'Name must not exceed 100 characters' );
// Email validation v.addErrorIf( !isNotEmpty(data.email), 'email', 'Email is required' );
v.addErrorIf( data.email && !isEmail(data.email), 'email', 'Invalid email format' );
// Password validation v.addErrorIf( !isNotEmpty(data.password), 'password', 'Password is required' );
v.addErrorIf( data.password && !minLength(data.password, 8), 'password', 'Password must be at least 8 characters' );
v.addErrorIf( data.password && !maxLength(data.password, 128), 'password', 'Password must not exceed 128 characters' );
// Age validation (optional) v.addErrorIf( data.age !== undefined && !isNumber(data.age), 'age', 'Age must be a number' );
v.addErrorIf( data.age && (data.age < 0 || data.age > 150), 'age', 'Age must be between 0 and 150' );
// Role validation (optional) const validRoles = ['user', 'admin', 'moderator']; v.addErrorIf( data.role && !validRoles.includes(data.role), 'role', `Role must be one of: ${validRoles.join(', ')}` );
// Tags validation (optional) v.addErrorIf( data.tags && !isArray(data.tags), 'tags', 'Tags must be an array' );
v.addErrorIf( data.tags && !arrayMinLength(data.tags, 1), 'tags', 'Tags must contain at least 1 item' );
v.addErrorIf( data.tags && !arrayMaxLength(data.tags, 10), 'tags', 'Tags must not exceed 10 items' );
// Birth date validation (optional) v.addErrorIf( data.birthDate && !isValidDate(data.birthDate), 'birthDate', 'Invalid birth date' );
return v;}25.5 Error Handling
Section titled “25.5 Error Handling”// Base error classesexport class AppError extends Error { constructor( message: string, public statusCode: number = 500, public code?: string ) { super(message); this.name = 'AppError'; }}
export class NotFoundError extends AppError { constructor(message: string = 'Resource not found') { super(message, 404, 'NOT_FOUND'); this.name = 'NotFoundError'; }}
export class ValidationError extends AppError { constructor(message: string = 'Validation failed') { super(message, 400, 'VALIDATION_ERROR'); this.name = 'ValidationError'; }}
export class UnauthorizedError extends AppError { constructor(message: string = 'Unauthorized') { super(message, 401, 'UNAUTHORIZED'); this.name = 'UnauthorizedError'; }}
export class ForbiddenError extends AppError { constructor(message: string = 'Forbidden') { super(message, 403, 'FORBIDDEN'); this.name = 'ForbiddenError'; }}
export class ConflictError extends AppError { constructor(message: string = 'Conflict') { super(message, 409, 'CONFLICT'); this.name = 'ConflictError'; }}
export class InternalServerError extends AppError { constructor(message: string = 'Internal server error') { super(message, 500, 'INTERNAL_SERVER_ERROR'); this.name = 'InternalServerError'; }}
// Express error handler middlewareimport { Request, Response, NextFunction } from 'express';
export function errorHandler( err: Error, req: Request, res: Response, next: NextFunction) { console.error('Error:', err);
// Handle known errors if (err instanceof AppError) { return res.status(err.statusCode).json({ success: false, error: { message: err.message, code: err.code, }, }); }
// Handle validation errors with multiple messages if (err.name === 'ValidationError' && (err as any).errors) { return res.status(400).json({ success: false, error: { message: err.message, code: 'VALIDATION_ERROR', errors: (err as any).errors, }, }); }
// Handle unknown errors return res.status(500).json({ success: false, error: { message: 'Internal server error', code: 'INTERNAL_SERVER_ERROR', }, });}
// Async handler wrapperexport function asyncHandler( fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); };}25.6 Using Validators and Error Handlers
Section titled “25.6 Using Validators and Error Handlers”import { Request, Response, NextFunction } from 'express';import { UserService } from '../services/user.service';import { validateCreateUserDto } from '../validators/user.validator';import { asyncHandler } from '../utils/error-handler.util';import { ApiResponseDto } from '../dto';
export class UserController { constructor(private userService: UserService) {}
createUser = asyncHandler(async (req: Request, res: Response) => { // Validate input const validation = validateCreateUserDto(req.body); validation.throwIfInvalid();
// Create user const user = await this.userService.create(req.body);
// Return response res.status(201).json( ApiResponseDto.success(user, 'User created successfully') ); });
getUserById = asyncHandler(async (req: Request, res: Response) => { const id = parseInt(req.params.id);
if (isNaN(id)) { return res.status(400).json( ApiResponseDto.error('Invalid user ID') ); }
const user = await this.userService.findById(id);
if (!user) { return res.status(404).json( ApiResponseDto.error('User not found') ); }
res.json(ApiResponseDto.success(user)); });}25.7 Summary
Section titled “25.7 Summary”This chapter covered:
- Validation Utilities: Reusable validation functions
- Error Classes: Custom error types for different scenarios
- Validator Implementation: Complete input validation
- Error Handling: Middleware for Express.js
- Best Practices: Using async handlers and proper error responses
Key takeaways:
- Always validate input at the entry point
- Use custom error classes for different error types
- Implement consistent error response format
- Log errors for debugging while hiding sensitive info from users
End of Part 5
Section titled “End of Part 5”This concludes Part 5: TypeScript Application Structure. You now have a solid foundation for building TypeORM applications with pure TypeScript.
Last Updated: February 2026