@libs-for-dev/nestjs-ddd-library
v1.3.2
Published
NestJS DDD library
Readme
NestJS DDD Library
A comprehensive library providing Domain-Driven Design (DDD) building blocks for NestJS applications. This library helps you implement DDD patterns such as Value Objects, Entities, and Aggregates in your NestJS projects with TypeScript.
Features
- Entities: Objects that have a distinct identity that runs through time and different states
- Aggregates: Cluster of domain objects that can be treated as a single unit
- Result types: Using oxide.ts for type-safe error handling
- Value Objects: Immutable objects that contain attributes but have no conceptual identity with examples:
- UUID: Built-in UUID v7 generation and validation
- Email: Email value object with validation
- Money: Money value object with amount and currency (ISO 4217) validation
- PhoneNumber: Phone number value object with mobile phone validation
- Url: URL value object with protocol validation
Installation
# Using npm
npm install @libs-for-dev/nestjs-ddd-library
# Using yarn
yarn add @libs-for-dev/nestjs-ddd-library
# Using pnpm
pnpm add @libs-for-dev/nestjs-ddd-libraryPeer Dependencies
This library requires the following peer dependencies:
yarn add @nestjs/cqrs class-validator fast-equals oxide.ts ui7Usage Examples
Value Objects
UUID
import { Uuid } from '@libs-for-dev/nestjs-ddd-library';
// Generate a new UUID (v7)
const uuid = Uuid.generate();
console.log(uuid.value); // Generated UUID v7 string
// Create from existing UUID string
const uuidResult = Uuid.create('de49eb8d-9199-4046-a784-d015148f95a7');
if (uuidResult.isOk()) {
const uuid = uuidResult.unwrap();
console.log(uuid.value); // de49eb8d-9199-4046-a784-d015148f95a7
} else {
// Handle error
console.error(uuidResult.unwrapErr());
}import { Email } from '@libs-for-dev/nestjs-ddd-library';
// Create an Email value object
const emailResult = Email.create('[email protected]');
if (emailResult.isOk()) {
const email = emailResult.unwrap();
console.log(email.value); // [email protected]
} else {
// Handle error
console.error(emailResult.unwrapErr());
}Money
import { Money } from '@libs-for-dev/nestjs-ddd-library';
// Create a Money value object
const moneyResult = Money.create(100.50, 'USD');
if (moneyResult.isOk()) {
const money = moneyResult.unwrap();
console.log(money.amount); // 100.50
console.log(money.currency); // USD
console.log(money.value); // { amount: 100.50, currency: 'USD' }
} else {
// Handle error
console.error(moneyResult.unwrapErr());
}PhoneNumber
import { PhoneNumber } from '@libs-for-dev/nestjs-ddd-library';
// Create a PhoneNumber value object
const phoneResult = PhoneNumber.create('+1234567890');
if (phoneResult.isOk()) {
const phone = phoneResult.unwrap();
console.log(phone.value); // +1234567890
} else {
// Handle error
console.error(phoneResult.unwrapErr());
}Url
import { Url } from '@libs-for-dev/nestjs-ddd-library';
// Create a Url value object
const urlResult = Url.create('https://example.com');
if (urlResult.isOk()) {
const url = urlResult.unwrap();
console.log(url.value); // https://example.com
} else {
// Handle error
console.error(urlResult.unwrapErr());
}Entities
AbstractEntity provides immutable properties through DeepReadonly. All properties, including nested objects, are readonly and cannot be modified directly. Changes must be made through entity domain methods.
Basic Usage
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';
// Define entity properties
interface UserProps {
email: string;
name: string;
}
// Create an entity class
class User extends AbstractEntity<Uuid, UserProps> {
public static create(id: Uuid, props: UserProps): User {
return new User(id, props);
}
// Add domain methods here
public updateName(name: string): void {
this.propsData = {
props: {
...this.props,
name
}
};
}
}
// Usage
const userId = Uuid.generate();
const user = User.create(userId, {
email: '[email protected]',
name: 'John Doe'
});
// Access properties
console.log(user.id.value); // UUID
console.log(user.props.name); // John Doe
// Update properties using domain methods
user.updateName('Jane Doe');
console.log(user.props.name); // Jane DoeReadonly Properties with Nested Objects
Properties returned by props getter are deeply readonly, including nested objects. This ensures immutability and prevents accidental modifications:
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';
interface UserProfileProps {
personal: {
firstName: string;
lastName: string;
};
settings: {
theme: string;
};
}
class UserProfile extends AbstractEntity<Uuid, UserProfileProps> {
public static create(id: Uuid, props: UserProfileProps): UserProfile {
return new UserProfile(id, props);
}
public updateFirstName(firstName: string): void {
this.propsData = {
props: {
...this.props,
personal: {
...this.props.personal,
firstName
}
}
};
}
}
// Usage
const profile = UserProfile.create(Uuid.generate(), {
personal: {
firstName: 'John',
lastName: 'Doe'
},
settings: {
theme: 'dark'
}
});
// All properties are readonly, including nested objects
// This will throw an error at runtime:
// profile.props.personal.firstName = 'Jane'; // ❌ Error: Cannot assign to read-only property
// This will also throw an error:
// profile.props.settings.theme = 'light'; // ❌ Error: Cannot assign to read-only property
// Changes must be made through domain methods
profile.updateFirstName('Jane');
console.log(profile.props.personal.firstName); // Jane
// Previous references remain unchanged (immutability)
const oldProps = profile.props;
profile.updateFirstName('Bob');
console.log(oldProps.personal.firstName); // Jane (unchanged)
console.log(profile.props.personal.firstName); // Bob (updated)Domain Events
The library integrates with NestJS CQRS to support domain events:
import { AbstractEntity, Uuid } from '@libs-for-dev/nestjs-ddd-library';
import { IEvent } from '@nestjs/cqrs';
// Define a domain event
class UserCreatedEvent implements IEvent {
constructor(public readonly userId: string, public readonly email: string) {}
}
// Entity with domain events
class User extends AbstractEntity<Uuid, UserProps> {
public static create(id: Uuid, props: UserProps): User {
const user = new User(id, props);
user.apply(new UserCreatedEvent(id.value, props.email));
return user;
}
}
// In your NestJS application
@EventsHandler(UserCreatedEvent)
export class UserCreatedHandler implements IEventHandler<UserCreatedEvent> {
handle(event: UserCreatedEvent) {
console.log(`User created: ${event.userId} with email ${event.email}`);
}
}License
MIT
