Skip to main content

Value Objects

Value objects are immutable objects that represent validated domain concepts. They encapsulate validation logic and ensure data integrity throughout the application.

Email

Represents a validated email address.

Class Definition

src/domain/value-objects/Email.ts
export class Email {
  private readonly value: string;

  constructor(email: string) {
    if (!this.isValid(email)) {
      throw new Error(`Invalid email: ${email}`);
    }
    this.value = email.toLowerCase().trim();
  }

  private isValid(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  toString(): string {
    return this.value;
  }
}

Constructor

email
string
required
Raw email address to validate and normalize
Behavior:
  • Validates the email format using regex pattern
  • Throws an error if the email is invalid
  • Normalizes the email by converting to lowercase and trimming whitespace
  • Stores the normalized value immutably

Methods

toString(): string
Returns the normalized email address as a string.Returns: The validated and normalized email address (lowercase, trimmed)Example:
const email = new Email('  [email protected]  ');
console.log(email.toString()); // "[email protected]"

Validation Rules

The email must match the following regex pattern:
/^[^\s@]+@[^\s@]+\.[^\s@]+$/

Usage Examples

import { Email } from '@/domain/value-objects/Email';

try {
  const email = new Email('[email protected]');
  console.log(email.toString()); // "[email protected]"
  
  // Email is normalized
  const email2 = new Email('  [email protected]  ');
  console.log(email2.toString()); // "[email protected]"
} catch (error) {
  console.error(error.message);
}

Phone

Represents a validated phone number.

Class Definition

src/domain/value-objects/Phone.ts
export class Phone {
  private readonly value: string;

  constructor(phone: string) {
    const cleaned = phone.replace(/\D/g, "");
    if (!this.isValid(cleaned)) {
      throw new Error(`Invalid phone: ${phone}`);
    }
    this.value = cleaned;
  }

  private isValid(phone: string): boolean {
    return phone.length >= 10 && phone.length <= 15;
  }

  toString(): string {
    return this.value;
  }
}

Constructor

phone
string
required
Raw phone number to validate and normalize
Behavior:
  • Removes all non-digit characters (spaces, dashes, parentheses, etc.)
  • Validates that the cleaned number has between 10-15 digits
  • Throws an error if the phone number is invalid
  • Stores the cleaned value immutably

Methods

toString(): string
Returns the normalized phone number as a string.Returns: The validated phone number containing only digitsExample:
const phone = new Phone('+1 (555) 123-4567');
console.log(phone.toString()); // "15551234567"

Validation Rules

The phone number must meet the following criteria after removing non-digits:
length
number
required
Must be between 10 and 15 digits (inclusive)
Valid Examples:
  • 10 digits: "1234567890"
  • 11 digits: "12345678901" (e.g., with country code)
  • 15 digits: "123456789012345"
Invalid Examples:
  • Too short: "123456789" (9 digits)
  • Too long: "1234567890123456" (16 digits)

Usage Examples

import { Phone } from '@/domain/value-objects/Phone';

try {
  // Various formats are accepted
  const phone1 = new Phone('+1 (555) 123-4567');
  console.log(phone1.toString()); // "15551234567"
  
  const phone2 = new Phone('555-123-4567');
  console.log(phone2.toString()); // "5551234567"
  
  const phone3 = new Phone('5551234567');
  console.log(phone3.toString()); // "5551234567"
} catch (error) {
  console.error(error.message);
}

Value Object Characteristics

Value objects are immutable. Once created, their internal state cannot be changed. This ensures data integrity and prevents unexpected side effects.
const email = new Email('[email protected]');
// email.value is private and readonly
// No methods exist to modify the internal value
Value objects validate their input in the constructor and throw errors for invalid data. This ensures that invalid objects cannot exist in the system.
try {
  const email = new Email('invalid');
} catch (error) {
  // Handle validation error
  console.error('Email validation failed');
}
Value objects normalize their input to a canonical form. This ensures consistency throughout the application.
const email1 = new Email('[email protected]');
const email2 = new Email('[email protected]');
console.log(email1.toString() === email2.toString()); // true
Validation logic is encapsulated within the value object, keeping domain rules close to the data they govern.
// Validation logic is hidden in private methods
// Business logic doesn't need to know HOW validation works
const email = new Email(userInput);
const phone = new Phone(userInput);

Build docs developers (and LLMs) love