ddd-ts-kit
Domain-Driven Design (DDD), Clean Architecture (hexagonal, ports and adapters) and Clean Code for TypeScript and Node.js backends, held by tools rather than by good intentions, for developers and for the AI coding agents they work with: Claude Code, Codex and GitHub Copilot.
Why ddd-ts-kit
Section titled “Why ddd-ts-kit”Domain-Driven Design and Clean Architecture erode one shortcut at a time: a new Date() in a
handler, an import across two contexts, a rule checked in a controller. Under a deadline, and even
more with an AI agent that writes a hundred files an hour, a guideline in a wiki does not hold.
ddd-ts-kit turns the architecture into rules with ids, and checks them at every file write, before
every merge and on every push.
- Rules, not guidelines. Each rule has an id, a reason and a fix
(
npx ddd-ts-kit explain <rule-id>), checked by a zero-dependency CLI, an ESLint plugin and dependency-cruiser. - Generate, do not type.
ddd-ts-kit generatewrites 36 kinds of building blocks with their spec, fake and registration, and leavesTODO(domain)where a business decision is expected. - Made for coding agents. A plugin for Claude Code, Codex and GitHub Copilot: skills that conduct the work step by step, agents for parallel work and review, and a hook that refuses a write which breaks a rule.
- Your stack, not ours. Express, Fastify, Hono or NestJS; zod, class-validator, TypeBox or any Standard Schema library; tsyringe, NestJS or manual wiring; Drizzle, Knex, Prisma or TypeORM; RabbitMQ, Kafka, NATS JetStream or Redis Streams; JWT or Keycloak; roles, attributes or relationships.
- Production concerns built in. Transactional outbox and idempotent consumers, a permission per use case, audit trail, OpenTelemetry, multitenancy, OpenAPI and AsyncAPI contracts, a CI pipeline for GitHub and GitLab.
- Ready to split. Every bounded context owns its data and talks to the others by events: it can run as its own service the day the team needs it.
The story behind it, on Medium: Your AI writes code fast. Your architecture decides whether it lasts. (in French).
The documentation is also a website, with search, in English and in French: https://gbourgeat.github.io/ddd-ts-kit/. Every page has its French translation next to it, starting from README.fr.md.
Quick start
Section titled “Quick start”mkdir orders-api && cd orders-apinpx ddd-ts-kit initpnpm installpnpm run verify # types, lint, format, tests, e2e, architecture, import graph, dead code, documentation driftinit without a flag takes Express, zod, tsyringe, Drizzle on PostgreSQL, Vitest and pnpm,
which corepack provides at the release package.json pins (corepack enable, once per machine).
The project is green from that first verify, before a line of business code, and stays so at
every step. Then, the ubiquitous language first, code second:
pnpm exec ddd-ts-kit generate context ordering --classification core \ --term "Order=A customer's request to buy products" --term "Place=To submit an order for fulfilment"pnpm exec ddd-ts-kit generate context billing --classification supporting --upstream ordering:customer-supplierpnpm exec ddd-ts-kit generate aggregate ordering Orderpnpm exec ddd-ts-kit generate repository ordering Orderpnpm exec ddd-ts-kit generate command ordering PlaceOrder --permission order.place --grant customer --aggregate Orderpnpm exec ddd-ts-kit generate route ordering PlaceOrder --method POST --path /orderspnpm run docs:generate # the OpenAPI contract follows the new routepnpm run verifypnpm exec ddd-ts-kit status # what is left: TODO(domain), TODO(platform) and it.todo per contextThese two blocks are run as they are written, line by line, by the end-to-end suite of this
repository, on pnpm and on npm. With npx ddd-ts-kit init --package-manager npm the same lines
read npm install, npm run <script> and npx ddd-ts-kit <command>. To see the application
run: cp .env.example .env, pnpm run infra:up (PostgreSQL, in Docker),
pnpm run db:generate initial, pnpm run db:migrate, then pnpm run dev (auto reload; API
documentation on /docs).
A name that is not in the context's glossary is refused, and so is a command or a query that
declares neither a permission nor --public. One slice, end to end
follows the request POST /orders through the four layers and shows the code once the
TODO(domain) places are filled.
Start a project
Section titled “Start a project”Follow these steps in order: it is the supported path, and the tools are built to hold it. Each
step is a skill of the plugin (/ddd-ts-kit:<skill>), or a command, and leaves a file the next
one reads. /ddd-ts-kit:start conducts steps 1 to 7 one at a time, and npx ddd-ts-kit status
says at any time which step is next.
| # | Step | Skill or command | Produces |
|---|---|---|---|
| 1 | Discover the domain | discover-domain |
docs/architecture/discovery.md: actors, commands, events, policies |
| 2 | Find the bounded contexts | bounded-contexts |
docs/architecture/contexts.md: each boundary and its reason |
| 3 | Write the glossaries | glossary |
the ubiquitous language of each context |
| 4 | Draw the context map | context-map |
who is upstream of whom, and by which pattern |
| 5 | Choose the stack and bootstrap | bootstrap, ddd-ts-kit init, generate context |
the project, green on verify, one CONTEXT.md per context |
| 6 | Plan the slices | plan-slices |
docs/architecture/slices.md: one use case per slice, in order |
| 7 | Build slice by slice | use-case (parallel-work for several workers) |
the code and its tests, one commit per slice |
| 8 | Pass the quality gate | quality-gate, npm run verify |
a green gate before each merge |
| 9 | Document | document, npm run docs:generate |
current contracts, context map and decision records |
The methodology is the reference for each step and says what skipping one costs; which command, when gives the command for each moment of a project.
What is enforced
Section titled “What is enforced”A handler written the quick way breaks four rules at once, and each finding names its rule:
// application/commands/place-order/place-order.handler.ts, written the quick wayimport { CommandHandler } from '@ddd-ts-kit/kernel/declarations';import { PlaceOrderCommand } from '@/contexts/ordering/application/commands/place-order/place-order.command';import { db, orders } from '@/contexts/ordering/infrastructure/database'; // layer/application-inward
@CommandHandler(PlaceOrderCommand)export class PlaceOrderHandler implements CommandHandler<PlaceOrderCommand> { async execute({ customerId, productIds }: PlaceOrderCommand): Promise<string> { if (productIds.length === 0) { throw new Error('Empty order'); // domain/no-generic-error }
const id = crypto.randomUUID(); // purity/no-ambient-randomness
await db.insert(orders).values({ id, customerId, productIds, placedAt: new Date() }); // purity/no-system-clock
return id; }}| Instead of | Write | Rule |
|---|---|---|
new Date() |
this.clock.now(), the Clock port received in the constructor |
purity/no-system-clock |
crypto.randomUUID() |
this.ids.next(), the IdGenerator port |
purity/no-ambient-randomness |
throw new Error('Empty order') |
throw OrderError.empty(), a refusal with a stable code |
domain/no-generic-error |
an import of infrastructure/ in a handler |
the OrderRepository port, bound to its adapter by the platform |
layer/application-inward |
an import of another context's domain/ |
that context's published-language/, or an integration event |
context/published-language-only |
The same use case written inside the rules is in One slice, end to end. The rules cover, among others:
- Layers and contexts (
layer/*,context/*,tree/*): the domain imports nothing but itself and the kernel; another context is reached only through its published language; no cycle. - Ubiquitous language (
language/*): domain classes and use cases are named with the terms of the context's glossary. - Tactical DDD and purity (
domain/*,application/*,events/*,purity/*): encapsulated aggregates, immutable value objects, specific errors, time and identifiers through ports. - Ports and wiring (
ports/*,declarations/*,composition/*): a port is an abstract class and its own injection token, assembled in one place. - Access control (
authz/*): every use case declares its permission or says it is public, decided in the application layer, with an explicit actor. - Clean code and tests (
clean/*,test/*): small functions in stepdown order, no escape hatch, a spec for every unit of behaviour, no mocks.
npx ddd-ts-kit rules lists every id with its severity; the
architecture reference defines them.
The plugin for coding agents
Section titled “The plugin for coding agents”This repository is the marketplace of the plugin for Claude Code, Codex and GitHub Copilot:
| Agent | Install | A skill is called |
|---|---|---|
| Claude Code | /plugin marketplace add gbourgeat/ddd-ts-kit, then /plugin install ddd-ts-kit@ddd-ts-kit |
/ddd-ts-kit:start |
| Codex | codex plugin marketplace add gbourgeat/ddd-ts-kit, then install ddd-ts-kit from /plugins and trust its hooks in /hooks |
$start |
| Copilot CLI | copilot plugin marketplace add gbourgeat/ddd-ts-kit, then copilot plugin install ddd-ts-kit@ddd-ts-kit |
by its name, start |
| Copilot cloud agent, VS Code | declared by the .github/copilot/settings.json that init writes |
by its name, start |
Twenty-two skills conduct a project from a statement of needs to working contexts, four agents
explore, build one context in its own worktree, write the specs and review, and the hook refuses
a hand-written file where a generator exists and checks every file written. Without any plugin,
the AGENTS.md and the rules by layer that init writes still guide every agent.
The plugin for coding agents lists the skills and says what each
host runs.
Packages
Section titled “Packages”| Package | What it gives |
|---|---|
ddd-ts-kit |
the CLI: init, generate, check, docs, sdk, status, rules, explain and more |
@ddd-ts-kit/kernel |
entities, aggregates, value objects, errors, ports, the declarations, command, query and event buses, the standard interfaces; no dependency |
@ddd-ts-kit/events |
typed integration events, transactional outbox and relay, AsyncAPI and event catalog |
@ddd-ts-kit/rabbitmq, kafka, nats, redis/event-bus |
broker providers behind one contract: acknowledged publishing, durable subscriptions, retries, dead letters |
@ddd-ts-kit/scheduling |
jobs run by time that only trigger use cases, one run across instances |
@ddd-ts-kit/auth |
actors, a permission per use case, deny by default; role, attribute and relationship authorizers; JWT |
@ddd-ts-kit/openfga |
the relationships of auth on an OpenFGA server |
@ddd-ts-kit/aspects |
logging, metrics, retry, timeout, circuit breaker, cache and bulkhead applied from outside |
@ddd-ts-kit/audit |
hash-chained, append-only audit trail |
@ddd-ts-kit/redis |
cache, lock, rate limiter and relay signal on Redis, Dragonfly or Valkey |
@ddd-ts-kit/s3 |
file storage on AWS S3, MinIO, OVHcloud, Scaleway or Cloudflare R2 |
@ddd-ts-kit/http |
routes declared once, mounted on Express, Fastify, Hono or NestJS; RFC 9457 errors; OpenAPI 3.1; HTTP caching |
@ddd-ts-kit/validation |
one Schema port over zod, class-validator, TypeBox and Standard Schema |
@ddd-ts-kit/di |
context modules on tsyringe, port classes as tokens, a test application with fakes |
@ddd-ts-kit/pino, winston |
the kernel Logger port on pino or winston |
@ddd-ts-kit/sentry |
the kernel ErrorReporter port on Sentry or GlitchTip |
@ddd-ts-kit/tracing |
OpenTelemetry wired once by the platform |
@ddd-ts-kit/testing |
builders, given/when/then scenarios, contract suites, end-to-end harness |
@ddd-ts-kit/drizzle, knex, prisma, typeorm |
unit of work, outbox store, read models, tenant isolation, event store |
@ddd-ts-kit/eslint-plugin |
the code rules on the syntax tree, with presets |
Everything talks through ports of the kernel, so each piece is replaceable: another broker, ORM or HTTP framework is an adapter that passes the same contract suite. Each package README says its limits and what was verified.
Pipeline
Section titled “Pipeline”init --ci github|gitlab|both installs the same gates on either platform: type check, lint,
architecture, dead code, unit tests with coverage thresholds, integration tests on PostgreSQL,
end-to-end tests, documentation and contract drift, security and image build, behind a single
final gate. Every tool is free on any repository. Details: pipeline/README.md.
Documentation
Section titled “Documentation”- Documentation index: every page, and a reading order for a newcomer to DDD, a tech lead adopting the kit and a contributor.
- Architecture reference (normative) and configuration.
- Concepts: one page per idea, from bounded contexts to event sourcing.
- Guides and tool comparisons: the methodology, team and parallel work, and one page per technical choice.
Example application
Section titled “Example application”examples/scheduling is an appointment scheduling platform built with the kit the way a user would build it: from a statement of needs, through the nine steps, by several workers in parallel. Four bounded contexts on Express, class-validator, tsyringe, Knex on PostgreSQL, the transactional outbox, Keycloak and relationship-based authorization.
Requirements
Section titled “Requirements”Node 22.12 or later. The generated projects type-check with the native TypeScript 7 compiler and keep TypeScript 6 for the tools that still need its API.
Community and support
Section titled “Community and support”- Questions and ideas: GitHub Discussions.
- Bugs and proposals: issues, from their templates.
- Security: never in a public issue; see SECURITY.md.
- Everyone who takes part follows the code of conduct.
Contributing
Section titled “Contributing”ddd-ts-kit is built in the open, and contributions of every size are welcome: a typo, a translation, a test, an adapter or a new rule. Good places to start:
- issues labelled good first issue or help wanted;
- an adapter for a broker, an ORM or an HTTP framework the kit does not support yet: it passes the contract suite of its port;
- the documentation, in English or in French, and the example application;
- feedback from a real project, in Discussions: what held, what got in the way.
CONTRIBUTING.md gives the setup, the ground rules and how a change reaches
main. If ddd-ts-kit helps you, a star on GitHub helps others find it.
License
Section titled “License”MIT © Gary Bourgeat and the contributors.