Hexagonal Architecture in Node.js: A Comprehensive Guide
The Hexagonal Architecture, also known as Ports and Adapters, was proposed by Alistair Cockburn with the aim of ensuring that the core of an application remains independent of external interfaces and technologies, such as databases, APIs, or even user interfaces. In the context of Node.js, this architecture provides significant benefits, including flexibility, scalability, and ease of maintenance—making it an ideal approach for modern, modular systems.
Why Use Hexagonal Architecture in Node.js?
By adopting this model, we can ensure that the core of the application (where the business logic resides) can evolve independently of external interfaces such as databases, APIs, authentication systems, etc.
How Hexagonal Architecture Works
Hexagonal Architecture consists of three main elements:
Ports: These are interfaces that define how the system interacts with the outside world. There are two main types of ports:
Inbound Ports: Responsible for receiving interactions from the outside world, such as API calls or user commands.
Outbound Ports: Responsible for interacting with external systems, such as databases or third-party services.
Adapters: Concrete implementations that connect the ports to the external world. They transform data between the system and external components.
Inbound Adapters: Connect the inbound interfaces (e.g., an HTTP controller in Express) to the inbound ports.
Outbound Adapters: Connect the outbound ports to external systems (such as a database).
Core of the Application: This is where the business logic of the application resides. The core is completely independent of the adapters and ports, allowing new technologies to be integrated easily without affecting the central logic.
The main advantage of this architecture is the decoupling between the core of the application and external technologies. This allows you to easily swap or replace external technologies (like databases, caching systems, or even front-end frameworks) without affecting the business logic of the application.
Example Technologies for Ports and Adapters
Inbound Ports:
Outbound Ports:
Inbound Adapters:
Outbound Adapters:
Advantages:
Disadvantages:
How to Implement Hexagonal Architecture in Node.js?
Implementing a Node.js application using Hexagonal Architecture involves creating a modular structure that separates the business logic from external components. Here’s a basic example of how to organize the code:
Project Structure
src/
├── core/
│ ├── domain/
│ ├── services/
│ └── useCases/
├── adapters/
│ ├── input/
│ │ └── routes.ts
│ └── output/
│ └── database.ts
├── ports/
│ ├── input/
│ │ └── UserPort.ts
│ └── output/
│ └── UserRepository.ts
└── index.ts
Inbound Port
// src/ports/input/UserPort.ts
export interface UserPort {
createUser(userData: { name: string; email: string }): Promise<void>;
}
Outbound Port
// src/ports/output/UserRepository.ts
export interface UserRepository {
saveUser(userData: { name: string; email: string }): Promise<void>;
}
Inbound Adapter
// src/adapters/input/routes.ts
import { UserPort } from '../../ports/input/UserPort';
import { Router } from 'express';
export class UserRoutes {
constructor(private userPort: UserPort) {}
public initRoutes(router: Router) {
router.post('/users', async (req, res) => {
await this.userPort.createUser(req.body);
res.status(201).send();
});
}
}
Outbound Adapter
// src/adapters/output/database.ts
import { UserRepository } from '../../ports/output/UserRepository';
export class MongoUserRepository implements UserRepository {
async saveUser(userData: { name: string; email: string }): Promise<void> {
// Here you can use Mongoose or any other database library
console.log('Saving user to MongoDB:', userData);
}
}
Core Application
// src/core/services/UserService.ts
import { UserPort } from '../../ports/input/UserPort';
import { UserRepository } from '../../ports/output/UserRepository';
export class UserService implements UserPort {
constructor(private userRepository: UserRepository) {}
async createUser(userData: { name: string; email: string }): Promise<void> {
await this.userRepository.saveUser(userData);
}
}
Changing the Database in Node.js with Hexagonal Architecture
Let’s say you are using MongoDB initially and want to switch to PostgreSQL in the future. With Hexagonal Architecture, you would only need to create a new outbound adapter for PostgreSQL and adjust the UserRepository. The core application, where the business logic resides, would remain untouched.
Conclusion
Hexagonal Architecture is an excellent choice for Node.js applications that need to be modular, scalable, and easy to maintain. By decoupling business logic from external interfaces, this architecture makes it easy to swap technologies, add new features, and perform unit testing. While there may be an initial learning curve and additional complexity for simple systems, it provides a robust foundation for applications that need long-term flexibility and scalability.
Hey Igor... Do you have a public github repo that implements a well strucuture and large project using Hexagonal architecture? I just found several repos but implementing basic examples. I was looking for a complex and huge project to see how to manage the complexity using hexagonal architecture in microservices.... thanks
Top 🚀
Thanks for sharing!!!
Thanks for sharing