Under the Hood: How I Structure Real DDD Code in TypeScript
You’ve heard the terms — domains, commands, events, repositories. But what does a real DDD-style codebase look like? Let’s break it down, file by file.
🧠 Who This Is For
This post is for junior and mid-level devs — or anyone uninitiated in Domain-Driven Design — who’ve heard about DDD and CQRS but don’t know how to organize their own codebase yet.
You can read the theory all day — but unless you’ve seen a system in the wild, it’s hard to know where to start.
So I’m walking you through mine: github.com/geggleto/cursor-typescript-rules
🧱 The Core Structure
This is a simplified example, but this layout reflects:
Explicit domains (quest, auth, points, etc.)
Command + event separation
Handler boundaries
Infrastructure decoupled from business logic
🧩 What Goes Where
/domains/<thing>/commands/
Pure DTOs (Data Transfer Objects)
Shape of the input you expect
No logic here — just structure
Think of this like a contract: this is what the command must contain to be valid for processing
/domains/<thing>/command-handlers/
One file per command
All the orchestration logic lives here
Calls into services, repositories, etc.
This is where the actual behavior of the command is defined — think of it as the entry point for business operations
/domains/<thing>/events/
The domain events that describe what happened
These are not side effects — they are facts
Emit these after meaningful state changes to reflect domain truth, not just to trigger reactions
/domains/<thing>/event-subscribers/
React to domain events
Side effects, like logging, notifications, integrations
Keeps side effects decoupled from core logic — lets your domain stay clean while still reacting to change
/domains/<thing>/services/
Domain logic or operations that cross multiple commands
Reusable business rules
Use services when logic doesn't neatly belong inside a single command or entity but still belongs in the domain
/domains/<thing>/<Thing>.ts
The root entity / aggregate (if used)
Models the core concept with internal consistency
If you need to enforce rules across multiple fields or entities within the domain, this is your go-to place
/messaging/
Infra layer
CommandBus, EventBus, ExecutionContext
Manages how commands and events flow, but knows nothing about domains
Think of this as the backbone — it wires up execution, but it doesn’t care what you're executing
/http/actions/
Adapters
Parse HTTP input
Turn them into commands
Call the bus and handle the result
These are the only files that know HTTP exists — they adapt the outside world to your internal system
🧼 Why This Matters
This structure:
Keeps business logic isolated
Makes responsibilities obvious
Allows for easy LLM-assisted codegen
Helps Cursor figure out where things belong
Cursor can scaffold a new feature because I have this structure.
The AI isn’t guessing — it’s following a pattern.
It also:
Keeps testing focused and simple
Makes refactoring safe
Speeds up onboarding for new devs
🏁 TL;DR
If you're building with TypeScript and want real-world DDD, this structure:
Scales with teams
Plays well with LLM tools
Keeps things clear and testable
You don’t need a framework. You just need boundaries.
Structure isn’t overhead — it’s scaffolding for velocity.