Structuring Your Project with Bounded Contexts in NestJS


“Don’t share a model between unrelated concepts—split your domain. That’s how you scale both the code and the team.” – DDD wisdom
What is a Bounded Context?
In DDD, a Bounded Context is a logical boundary around a part of your system that has its own models, language, and rules.
Each context:
Defines its own domain terms
Is autonomous in behavior
Interacts with others through well-defined interfaces
Think of it as a “domain module” that owns its logic, instead of dumping everything into shared services or models.
Why You Need It
Without Bounded Contexts, things get messy:
Business terms collide (e.g., "User" means 3 different things)
Codebases become tightly coupled
Changes in one feature break others
Bounded Contexts allow teams and systems to scale independently.
How NestJS Makes This Easy
NestJS has a modular architecture baked in. You already use Modules — we’ll now align them with Bounded Contexts.
A typical project structure:
src/
├── user/
│ ├── user.module.ts
│ ├── user.controller.ts
│ ├── user.service.ts
│ ├── user.entity.ts
│ ├── user.value-object.ts
│ └── use-cases/
│ └── create-user.use-case.ts
├── auth/
├── payment/
└── shared/
Each folder under src/
is a Bounded Context and owns its:
Domain logic (Entities, Value Objects)
Application logic (Use cases)
API (Controllers)
In NestJS, you don’t need a separate domain/
folder — your feature module already encapsulates domain logic effectively. Keep your module clean, and you’re good.
Naming and Language Matter
In DDD, the language inside each context is sacred.
For example:
In
payments
, a “Transaction” might be a domain EntityIn
analytics
, “Transaction” might mean a record of user behavior
Keep models context-specific. Do NOT reuse entities across domains. If they must talk, use DTOs, events, or interfaces.
Shared Modules ≠ Shared Logic
Avoid putting core logic in shared/
:
Use
shared/
only for cross-cutting concerns (e.g., logger, config)NEVER put domain logic here
If two contexts need the same logic — extract it to a library, or rethink if they should even share it.
Example: User + Payment Contexts
Here’s how your Bounded Contexts might look:
src/
├── user/
│ ├── user.module.ts
│ ├── user.controller.ts
│ ├── user.service.ts
│ ├── user.entity.ts
│ ├── user.value-object.ts
│ └── use-cases/
├── payment/
│ ├── payment.module.ts
│ ├── payment.controller.ts
│ ├── payment.service.ts
│ ├── transaction.entity.ts
│ └── use-cases/
Each context is independent and focused. They don’t import each other’s models or services directly.
Communication Between Contexts
Contexts often need to talk. Here’s how to keep it clean:
REST or GraphQL: Expose endpoints for other services to consume
Domain Events: Publish events when something changes (e.g.,
UserCreated
)Interfaces/Ports: Use abstraction (Dependency Injection) to decouple logic
NestJS supports all of this out of the box — especially with its CQRS and EventEmitter modules.
Takeaways
Bounded Contexts are the foundation of DDD architecture
In NestJS, use Modules to define and isolate each context
Keep domain language pure and consistent inside each context
Avoid leaking models or services across boundaries
You don’t need a
domain/
folder if your module stays clean and modular
Coming Up Next
In the next post, we’ll dig into Entities, Value Objects, and Repositories — the core building blocks of your domain model.
What Do You Think?
Got questions on structuring modules or managing context boundaries in real-world apps?
X @codanyks — or share your current architecture, and let’s improve it together!
Subscribe to my newsletter
Read articles from codanyks directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
