Stop the Spaghetti! Architecting Scalable APIs with Entities and DTOs
A common challenge for developers is watching their API’s data layer devolve into a confusing, tangled mess. If your controllers are bogged down with business rules, it’s time to separate concerns using Entities and DTOs (Data Transfer Objects).
If your code is starting to look less like a structured system and more like a bowl of spaghetti, this architectural shift is crucial for building maintainable, scalable, and efficient APIs.
🚀 Complete JavaScript Guide (Beginner + Advanced)
🚀 NodeJS – The Complete Guide (MVC, REST APIs, GraphQL, Deno)
1. The Entity: Home for Core Business Logic
In Domain-Driven Design (DDD) principles, the Entity is your core domain object. It represents the crucial concepts of your application (like a Task, User, or Order).
The fundamental power of the Entity lies in its ability to co-locate data with its related business logic.
What an Entity Does:
- Enforces Consistency: An entity ensures that the rules around its data are always followed.
- Encapsulates Logic: It keeps your business logic isolated from the transport or presentation layer.
Real-World Example:
Imagine a Task entity. Besides holding properties like name, due_date, and completed_on, it includes a method: markAsCompleted().
TypeScript
class Task {
// ... properties
markAsCompleted(): void {
if (this.completed_on) {
throw new Error('Task is already completed.');
}
this.completed_on = new Date();
}
}
By putting this method inside the entity, you ensure that the rule—a task can only be completed once—is enforced automatically.
2. The DTO: Controlling Data Exposure
A Data Transfer Object (DTO) is a simple data structure used only to transfer data across process boundaries, such as from your server to the client via an API response.
Why You Need DTOs:
The DTO acts as your API contract. It defines exactly what data the client will receive, completely decoupling it from your internal database or entity structure.
A. Data Exclusion (Security and Simplicity)
The most classic use case is security. When an API returns a User object, it should never expose fields like password or internal audit fields.
- Goal: To hide internal or sensitive data.
- Example: Excluding the database field
created_at(which the client doesn’t need) from the final API response object.
B. Data Inclusion and Calculation
DTOs are also perfect for adding calculated or derived fields that are meaningful to the client but don’t exist as properties on the core entity.
- Goal: To provide the client with value-added data.
- Example: Calculating a
priority_level(high,low, ornull) based on the task’sdue_date, and including this field only in the DTO.
3. Streamlining the Conversion with Class Transformer
In languages like TypeScript or JavaScript with Node.js, you don’t have to manually map every field. Libraries like class-transformer can automate the conversion process from Entity $\rightarrow$ DTO.
By using decorators, you can precisely control the output:
| Decorator | Purpose |
@Exclude() | Tells the transformer to skip a property when converting the Entity to a plain DTO. |
@Expose() | Used on a getter method (e.g., get priorityLevel()) to ensure the calculated value is included in the DTO. |
This powerful pattern allows you to write the conversion logic once and use it consistently across your entire application.
The Ultimate Separation of Concerns
By adopting Entities and DTOs, you achieve a beautiful and robust separation of concerns:
| Layer | Object Type | Responsibility |
| Domain Layer | Entity | Business Logic and data integrity. |
| Presentation Layer | DTO | Data Representation and the API contract. |
This means your controllers are thin, your business rules are centralized, and your API response contract is stable and secure. You’ve successfully moved from spaghetti code to a well-architected system.
Ready to clean up your codebase? Start refactoring your domain models into Entities and defining your API responses with DTOs today.