Skip to content

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.

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 generate writes 36 kinds of building blocks with their spec, fake and registration, and leaves TODO(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.

Terminal window
mkdir orders-api && cd orders-api
npx ddd-ts-kit init
pnpm install
pnpm run verify # types, lint, format, tests, e2e, architecture, import graph, dead code, documentation drift

init 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:

Terminal window
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-supplier
pnpm exec ddd-ts-kit generate aggregate ordering Order
pnpm exec ddd-ts-kit generate repository ordering Order
pnpm exec ddd-ts-kit generate command ordering PlaceOrder --permission order.place --grant customer --aggregate Order
pnpm exec ddd-ts-kit generate route ordering PlaceOrder --method POST --path /orders
pnpm run docs:generate # the OpenAPI contract follows the new route
pnpm run verify
pnpm exec ddd-ts-kit status # what is left: TODO(domain), TODO(platform) and it.todo per context

These 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.

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 nine steps: four written in the words of the business under docs/architecture/ (discover the domain, find the contexts, write the glossaries, draw the context map), then five with the generators (bootstrap, plan the slices, build a slice, pass the gate, document); steps 7 and 8 repeat for each slice

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.

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 way
import { 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 same rules checked at three moments: at every file write by the hook of the Claude Code plugin, before each merge by npm run verify, and on every push by the CI pipeline, all three applying the rules defined in docs/ARCHITECTURE.md

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.

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.

Where each package is imported: inside a bounded context the domain imports only the kernel, the application adds auth, the presentation imports http and validation, the infrastructure one ORM package, the published language events and validation; the platform composes the contexts with di and the packages of its technical capabilities

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.

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.

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.

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.

The contributors of ddd-ts-kit

Star history of ddd-ts-kit

MIT © Gary Bourgeat and the contributors.