/ Docs

Setting Up the Project

Overview

This tutorial walks through building a task management system with Bounda. By the end, you will have a working application that creates, assigns, and completes tasks using event sourcing and CQRS.

In this first step, you will scaffold the project, install dependencies, and generate the initial type definitions.

Create the Project

Start by creating a new directory and initializing it as an npm project:

mkdir task-manager
cd task-manager
npm init -y

Install Dependencies

Bounda is split into a small set of focused packages. Install the runtime packages and the dev tooling:

npm i @bounda-dev/core @bounda-dev/config
npm i -D @bounda-dev/codegen @bounda-dev/ops typescript
PackagePurpose
@bounda-dev/coreRuntime: boots the app, dispatches commands, runs queries
@bounda-dev/configTyped configuration schema
@bounda-dev/codegenCLI tooling and code generation
@bounda-dev/opsRuntime operations tools (DLQ management, process manager inspection)
typescriptTypeScript compiler

Configure TypeScript

Create a tsconfig.json at the project root:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "dist",
    "rootDir": ".",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src", ".bounda"]
}

Note: The include array contains .bounda because the code generator writes type definitions there. Without it, TypeScript will not find the generated types.

Create the Bounda Configuration

Create bounda.config.ts at the project root. This file tells Bounda where your source code lives, which aggregates exist, and how data is stored:

import type { Config } from "@bounda-dev/config";

export default {
  rootDir: "src",
  domain: {
    task: {
      eventStore: { type: "in-memory" },
    },
  },
  read: {
    "task-board": {
      type: "sqlite",
      path: "./db/task-board.db",
    },
  },
} satisfies Config;

The configuration declares:

  • rootDir — the base directory for all source files.
  • domain.task — an aggregate called task that stores events in memory.
  • read.task-board — a read model backed by a local SQLite database.

Create the Directory Structure

Bounda uses a file-system convention to discover events, commands, policies, projections, and queries. Create the expected directories:

mkdir -p src/domain/task/commands
mkdir -p src/domain/task/policies
mkdir -p src/read/task-board/projections
mkdir -p src/read/task-board/queries

The resulting layout looks like this:

task-manager/
  bounda.config.ts
  tsconfig.json
  src/
    domain/
      task/
        commands/
        policies/
    read/
      task-board/
        projections/
        queries/

Run the Code Generator

Bounda generates type-safe interfaces from your file structure and configuration. Run it now to bootstrap the initial types:

npx bounda generate

This creates a .bounda directory at the project root containing generated type definitions. You will re-run this command whenever you add new events, commands, or queries.

What We Built

  • Initialized an npm project with Bounda’s core packages.
  • Configured TypeScript with NodeNext module resolution.
  • Declared a task aggregate and a task-board read model in bounda.config.ts.
  • Created the directory structure that Bounda’s file-system conventions expect.
  • Ran the code generator to produce initial type definitions.

The project is scaffolded and ready. Next, you will define the first aggregate by writing events that describe what can happen to a task.

Next step: Defining Your First Aggregate