A simple NestJS application demonstrating Hexagonal Architecture (Ports and Adapters) combined with CQRS (Command Query Responsibility Segregation) pattern.
This project showcases how to structure a NestJS application using two powerful architectural patterns:
- Hexagonal Architecture: Separates business logic from external concerns through ports and adapters
- CQRS: Separates read and write operations for better scalability and maintainability
Hexagonal Architecture, also known as Ports and Adapters, organizes code into distinct layers:
- Domain Layer: Contains business entities and value objects - the core business logic
- Application Layer: Contains use cases, commands, queries, and ports (interfaces)
- Infrastructure Layer: Contains adapters that implement ports (e.g., repositories, external services)
- Presentation Layer: Contains controllers and DTOs - the entry point for external requests
The key benefit is that your business logic remains independent of frameworks, databases, or external services. You can swap implementations without changing core business rules.
CQRS (Command Query Responsibility Segregation) separates operations into two categories:
- Commands: Operations that change state (Create, Update, Delete)
- Queries: Operations that read data (Get, List)
In this project, NestJS CQRS module is used to handle commands and queries through dedicated handlers, making the code more organized and easier to maintain.
src/user/
├── domain/ # Domain Layer
│ ├── entities/ # Business entities (User)
│ └── value-objects/ # Value objects (Email, UserId)
│
├── application/ # Application Layer
│ ├── commands/ # Command objects (CreateUserCommand, etc.)
│ ├── queries/ # Query objects (GetUserQuery, etc.)
│ ├── handler/ # CQRS handlers (CommandHandler, QueryHandler)
│ ├── use-cases/ # Business use cases
│ └── ports/ # Ports/interfaces (UserRepositoryPort)
│
├── infrastructure/ # Infrastructure Layer
│ └── adapters/ # Adapters implementing ports (InMemoryUserRepository)
│
└── presentation/ # Presentation Layer
├── dtos/ # Data Transfer Objects
└── user.controller.ts # REST API controller
- Controller receives HTTP request and creates a Command or Query
- CommandBus/QueryBus routes the Command/Query to the appropriate Handler
- Handler delegates to the Use Case
- Use Case contains business logic and uses Repository Port (interface)
- Repository Adapter (in Infrastructure) implements the Port and handles data persistence
POST /users
↓
UserController.createUser()
↓
CreateUserCommand → CommandBus
↓
CreateUserHandler.execute()
↓
CreateUserUseCase.execute()
↓
UserRepositoryPort.save() (interface)
↓
InMemoryUserRepository.save() (implementation)
- User CRUD operations (Create, Read, Update, Delete, List)
- Hexagonal Architecture with clear layer separation
- CQRS pattern implementation
- Swagger API documentation
- In-memory repository (easily replaceable with database)
- Node.js (v18 or higher)
- pnpm (or npm/yarn)
pnpm install# Development mode
pnpm run start:dev
# Production mode
pnpm run start:prodThe application will start on http://localhost:9999
Once the application is running, access Swagger documentation at:
http://localhost:9999/api
POST /users- Create a new userGET /users- List all usersGET /users/:id- Get user by IDPATCH /users/:id- Update userDELETE /users/:id- Delete user
Contains pure business logic with no dependencies on external frameworks:
- Entities: Rich domain models with business rules (e.g.,
User) - Value Objects: Immutable objects representing domain concepts (e.g.,
Email,UserId)
Orchestrates use cases and defines contracts:
- Use Cases: Application-specific business logic
- Ports: Interfaces that define what the application needs (e.g.,
UserRepositoryPort) - Commands/Queries: Data objects for CQRS operations
- Handlers: CQRS handlers that connect commands/queries to use cases
Implements the ports defined in the application layer:
- Adapters: Concrete implementations of ports (e.g.,
InMemoryUserRepository) - Can be easily swapped (e.g., replace in-memory with PostgreSQL, MongoDB, etc.)
Handles external communication:
- Controllers: REST endpoints
- DTOs: Data transfer objects for request/response validation
- Testability: Easy to test business logic in isolation
- Maintainability: Clear separation of concerns
- Flexibility: Swap implementations without changing business logic
- Scalability: CQRS allows independent scaling of read/write operations
- Independence: Business logic is framework-agnostic
To replace the in-memory repository with a real database:
- Create a new adapter in
infrastructure/adapters/(e.g.,PostgresUserRepository) - Implement the
UserRepositoryPortinterface - Update
user.module.tsto use the new adapter
The rest of your code remains unchanged!
- Create domain entities/value objects in
domain/ - Define use cases in
application/use-cases/ - Create commands/queries in
application/commands/orapplication/queries/ - Implement handlers in
application/handler/ - Add controller endpoints in
presentation/
# Development
pnpm run start:dev
# Build
pnpm run build
# Production
pnpm run start:prod
# Linting
pnpm run lint
MIT