noddde

Introduction

What is noddde and why use it

What is noDDDe?

noDDDe is a TypeScript framework for building applications using Domain-Driven Design (DDD), Command Query Responsibility Segregation (CQRS), and Event Sourcing patterns.

It provides building blocks — not an opinionated runtime. You define your domain as typed objects and pure functions, and noDDDe gives you the type infrastructure to wire them together safely.

Core Ideas

Aggregates

Aggregates are consistency boundaries — the units that process commands and enforce business rules. In noDDDe, an aggregate is defined using the functional Decider pattern:

const BankAccount = defineAggregate<BankAccountDef>({
  initialState: { balance: 0, transactions: [] },
  commands: {
    AuthorizeTransaction: (command, state, infra) => {
      /* return events */
    },
  },
  apply: {
    TransactionAuthorized: (event, state) => {
      /* return new state */
    },
  },
});

No base class. No decorators. No this. Just an object with handler maps.

Commands

Commands express intent to change state. They are typed messages with a name, a targetAggregateId (for routing), and an optional payload:

type BankAccountCommand = DefineCommands<{
  CreateBankAccount: void;
  AuthorizeTransaction: { amount: number; merchant: string };
}>;

Events

Events are immutable facts about what happened. They use past tense and are produced by command handlers:

type BankAccountEvent = DefineEvents<{
  BankAccountCreated: { id: string };
  TransactionAuthorized: { id: string; amount: number; merchant: string };
}>;

Projections

Projections build read-optimized views from event streams. They are the query side of CQRS:

const BankAccountProjection = defineProjection<BankAccountProjectionDef>({
  reducers: {
    BankAccountCreated: (event, view) => ({ ...view, id: event.payload.id }),
    TransactionAuthorized: (event, view) => ({
      ...view,
      balance: view.balance + event.payload.amount,
    }),
  },
  queryHandlers: {
    /* ... */
  },
});

Sagas

Sagas are event-driven process managers that coordinate workflows across multiple aggregates. They are the structural inverse of aggregates — events in, commands out:

const OrderFulfillmentSaga = defineSaga<OrderFulfillmentSagaDef>({
  initialState: { status: null },
  startedBy: ["OrderPlaced"],
  associations: { OrderPlaced: (event) => event.payload.orderId },
  handlers: {
    OrderPlaced: (event, state) => ({
      state: { ...state, status: "awaiting_payment" },
      commands: { name: "RequestPayment", targetAggregateId: "...", payload: { ... } },
    }),
  },
});

Domain Configuration

configureDomain wires aggregates, projections, sagas, and infrastructure into a running domain:

const domain = await configureDomain<MyInfrastructure>({
  writeModel: { aggregates: { BankAccount } },
  readModel: { projections: { BankAccount: BankAccountProjection } },
  processModel: { sagas: { OrderFulfillment: OrderFulfillmentSaga } },
  infrastructure: {
    /* persistence, buses, custom infra */
  },
});

How noDDDe Differs

Traditional DDDnoDDDe
AggregatesClasses extending AggregateRootPlain objects via defineAggregate
State changesthis.apply(event) mutates stateApply handlers return new state (immutable)
Command handlingDecorated methodsTyped handler maps
Type safetyManual generic parametersInferred from AggregateTypes bundle
Event definitionsEnum + interface + unionDefineEvents<{ ... }> single declaration
TestingRequires framework setupCall functions directly
InfrastructureDecorator-based injectionFunction parameter injection

noDDDe draws inspiration from modern TypeScript API design (Zod, tRPC, Drizzle) — declarative, inference-heavy, zero-boilerplate.

Who Is This For?

noDDDe is for TypeScript developers building event-driven or domain-rich applications who want:

  • Type-safe domain modeling without class hierarchies
  • Testable business logic without mock frameworks
  • Flexible persistence (event sourcing or state storage) with built-in adapters for Drizzle, Prisma, and TypeORM
  • A functional approach to DDD patterns

Next Steps

On this page