Handling Business Rules and Validation in NestJS with DDD


"Good architecture allows for changes in technology without affecting business rules."
In Domain-Driven Design (DDD), business rules and validation ensure that your system remains robust, reliable, and easy to maintain. In this article, we explore how to implement and enforce business rules and validation in a NestJS application following DDD principles.
Why Business Rules and Validation Matter in DDD
At the heart of Domain-Driven Design (DDD) lies the domain model, representing the core business logic of your system. Business rules define the valid behaviors, while validation ensures inputs and states adhere to those rules.
When applied correctly, these concepts help:
Avoid errors
Ensure data integrity
Maintain clean, scalable code
Without enforcing them properly, systems risk inconsistent states and unpredictable failures.
"An ounce of prevention is worth a pound of cure."
Key Areas to Enforce Business Rules and Validation
In a DDD architecture, enforcement happens at different levels:
Domain Layer
Application Layer
Event Layer
1. Domain Layer — Enforcing Business Rules
In the Domain Layer, business rules live inside Entities and Aggregates. They ensure any state changes conform to domain invariants.
Example: E-commerce system ensuring sufficient balance before placing an order.
class Order {
constructor(
public id: string,
public totalAmount: number,
public userBalance: number,
) {}
placeOrder() {
if (this.userBalance < this.totalAmount) {
throw new Error("Insufficient balance to place the order");
}
// Proceed with placing the order...
}
}
Explanation:
The
Order
entity enforces that a user cannot place an order without sufficient balance.This keeps the core domain logic consistent and reliable.
2. Application Layer — Handling Validation Logic
The Application Layer handles validation of external inputs, such as API requests.
Example: Using DTOs with class-validator
.
import { IsNotEmpty, IsNumber, Min } from 'class-validator';
class CreateOrderDto {
@IsNotEmpty()
@IsNumber()
userId: number;
@IsNotEmpty()
@IsNumber()
@Min(1)
totalAmount: number;
}
Explanation:
Validates incoming data.
Acts as a gatekeeper before handing data to the domain.
"Validate early. Fail fast. Protect your domain."
3. Domain Events — Triggering Business Rules
Domain Events capture important domain moments and trigger additional workflows.
Example: OrderPlacedEvent after an order is placed.
class OrderPlacedEvent {
constructor(public orderId: string, public totalAmount: number) {}
}
// Publish the event
eventBus.publish(new OrderPlacedEvent(orderId, totalAmount));
Explanation:
The event encapsulates critical data.
Enables loose coupling and scalable workflows.
How to Enforce Validation in NestJS
NestJS offers robust validation capabilities that extend naturally across layers.
1. Custom Validation Decorators
Create more complex or domain-specific validation rules.
Example: Custom Positive Number Decorator
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
export function IsPositiveNumber(validationOptions?: ValidationOptions) {
return (object: Object, propertyName: string) => {
registerDecorator({
name: 'IsPositiveNumber',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
return typeof value === 'number' && value > 0;
},
},
});
};
}
2. Error Handling
Gracefully manage validation errors using exception filters.
Example: Custom Validation Exception Filter
import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { ValidationError } from 'class-validator';
@Catch(ValidationError)
export class ValidationExceptionFilter implements ExceptionFilter {
catch(exception: ValidationError, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse();
response.status(400).json({
message: exception.constraints,
});
}
}
Explanation:
Catches validation errors globally.
Provides structured, client-friendly feedback.
"Errors are inevitable. How you handle them defines your system's resilience."
Conclusion
In Domain-Driven Design with NestJS, enforcing business rules and validation is essential for:
Correct system behavior
Scalable architecture
Maintainable codebases
By embedding rules within your domain, validating inputs at the application layer, and leveraging domain events, you create robust software ecosystems.
Incorporating tools like class-validator
, custom decorators, and exception filters further strengthens the integrity of your system.
Build systems that don't just work — build systems that endure.
Further Reading and Connect With Us
If you enjoyed diving deep into Domain-Driven Design with NestJS, there’s a lot more coming your way!
Explore our previous series Real-Time Node.js: WebSockets where we built real-time systems from scratch with native WebSocket clients and Node.js servers.
Stay connected with us for more deep dives into modern backend architectures:
Follow Codanyks on Twitter for regular insights, tips, and behind-the-scenes content.
Read more articles and tutorials on Dev.to/codanyks and join a growing community of passionate developers.
"Knowledge grows when it's shared."
Subscribe to my newsletter
Read articles from codanyks directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
