> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Monorepo

> How an Ooneex application is structured as a Bun workspace monorepo — modules as packages, Nx task orchestration, shared root tooling, and TypeScript path aliases.

Every Ooneex application is a **monorepo**: a single repository, a single dependency tree, and a single set of tooling that hosts many self-contained modules. [`app:create`](/cli/commands/app-create) scaffolds the whole structure, and [`app:init`](/cli/commands/app-init) lays down the workspace root that holds it together. This page explains how that monorepo is wired and how to work inside it.

## Why a monorepo

An Ooneex app grows by adding **modules** — `app`, `shared`, then one per business domain (`movie`, `billing`, `user`). A monorepo keeps all of them in one place while letting each evolve independently:

* **One install, one lockfile** — `bun install` resolves every module's dependencies into a single `node_modules` at the root, so versions stay consistent across the whole app.
* **Shared tooling** — formatter, linter, TypeScript config, commit rules, and git hooks are defined once at the root and apply to every module.
* **Cross-module imports without publishing** — modules reference each other through path aliases and Bun workspaces, with no build-and-publish step in between.
* **Cached, incremental tasks** — [Nx](https://nx.dev) runs `lint`, `test`, and `build` across every module, only re-running what changed.
* **Independent slices** — microservices and SPAs live as modules in the same repo yet build and deploy on their own.

## Anatomy of the workspace

The monorepo is a [Bun workspace](https://bun.sh/docs/install/workspaces). The root `package.json` declares the workspace, the scripts, and the shared `lint-staged` config; everything under `modules/*` is a workspace member.

```
movie-app/
├── modules/                    # Workspace members — one folder per module
│   ├── app/                    # API module — the application entrypoint
│   ├── shared/                 # Shared module — cross-cutting code, env, roles
│   └── movie/                  # A business-domain module you add
├── .commitlintrc.ts            # Conventional commit rules
├── .gitignore
├── .husky/                     # Git hooks (pre-commit, commit-msg)
├── .zed/settings.json          # Editor settings
├── biome.jsonc                 # Formatter + linter config
├── bunfig.toml                 # Bun config (test coverage on)
├── nx.json                     # Nx task orchestration + caching
├── package.json                # Workspace root: scripts, workspaces, lint-staged
├── README.md
└── tsconfig.json               # Root TS config with module path aliases
```

The root `package.json` is the heart of the workspace:

```json package.json theme={null}
{
  "name": "movie-app",
  "scripts": {
    "fmt": "bunx biome check --write",
    "lint": "bunx nx run-many -t lint --output-style=stream --verbose",
    "test": "bunx nx run-many -t test --output-style=stream --verbose",
    "check": "bun install && bun run build && bun run lint && bun run test",
    "commit": "bunx commit"
  },
  "workspaces": ["modules/*"],
  "lint-staged": {
    "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
      "biome check --files-ignore-unknown=true"
    ]
  }
}
```

The `workspaces: ["modules/*"]` glob is what makes this a monorepo: Bun treats every folder under `modules/` as a package, hoists their dependencies into the root `node_modules`, and links them together.

## Modules as workspace packages

Each module under `modules/<name>/` is its own package with its own `package.json` and `tsconfig.json`, but it shares the root's install, tooling, and TypeScript config. A module owns a complete vertical slice of your domain — controllers, services, repositories, entities, migrations, and seeds — so all the code for one feature lives in one folder rather than being scattered across global `controllers/` and `services/` directories.

Modules come in a few flavors, all of them members of the same workspace:

| Type           | Runs as                                               | Registered into `AppModule` / `SharedModule` | Created with                                                      |
| -------------- | ----------------------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------- |
| `api`          | Part of the main app process                          | Yes                                          | [`module:create`](/cli/commands/module-create) (the `app` module) |
| `module`       | Part of the main app process                          | Yes                                          | [`module:create`](/cli/commands/module-create)                    |
| `microservice` | Standalone HTTP service (own entrypoint + Dockerfile) | Yes                                          | [`microservice:create`](/cli/commands/microservice-create)        |
| `spa`          | Standalone Vite front-end (own dev server)            | No                                           | [`spa:create`](/cli/commands/spa-create)                          |

<Note>
  `microservice` and `spa` modules live in the same monorepo and reuse the same
  install, aliases, and tooling, but they build and deploy independently. See
  [Microservices](/microservice/overview) and [Single Page App](/spa/overview).
</Note>

### Cross-module imports with path aliases

The root `tsconfig.json` maps each module to a `@module/<name>` alias, so modules import each other by name instead of brittle relative paths:

```json tsconfig.json theme={null}
{
  "compilerOptions": {
    "paths": {
      "@module/app/*": ["./modules/app/src/*"],
      "@module/shared/*": ["./modules/shared/src/*"]
    }
  }
}
```

```typescript theme={null}
// From any module, import shared code by alias
import { SharedDatabase } from "@module/shared/databases/SharedDatabase";
```

When you run [`module:create`](/cli/commands/module-create) or [`microservice:create`](/cli/commands/microservice-create), the generator **adds the new alias to `tsconfig.json` automatically** (and registers the module in `AppModule`, its entities in `SharedModule`, and its scope in the commitlint config). SPAs are deliberately left out of the path aliases and module registries so they never pull server-side wiring into the front-end.

## Task orchestration with Nx

Tooling tasks run across the whole monorepo through [Nx](https://nx.dev). The `lint` and `test` scripts use `nx run-many` to execute the matching target in every module at once, while `nx.json` configures dependency order and caching:

```json nx.json theme={null}
{
  "targetDefaults": {
    "build": { "dependsOn": ["^build"], "cache": true },
    "fmt": { "cache": true },
    "lint": { "dependsOn": ["^build"], "cache": true },
    "test": { "dependsOn": ["^build"], "cache": true }
  },
  "packageManager": "bun"
}
```

* **`dependsOn: ["^build"]`** ensures a module's dependencies are built before it is linted, tested, or built.
* **`cache: true`** lets Nx skip any task whose inputs haven't changed, so repeated runs only touch what you actually modified.

Run them from the root:

```bash theme={null}
bun run fmt     # format the whole repo with Biome
bun run lint    # lint every module via Nx
bun run test    # test every module via Nx
bun run check   # install + build + lint + test — the full gate
```

## Shared root tooling

Because it's a monorepo, configuration is defined once at the root and inherited by every module:

* **`biome.jsonc`** — a single formatter and linter for all modules. `bun run fmt` applies it everywhere.
* **`tsconfig.json`** — the strict, shared TypeScript baseline plus the module path aliases. Each module's `tsconfig.json` extends from this root.
* **`.commitlintrc.ts`** — conventional-commit rules. Module names become valid commit scopes, kept in sync as you add and remove modules.
* **`.husky/`** — git hooks installed at the root: `pre-commit` runs `lint-staged` (Biome on staged files) and `commit-msg` runs commitlint.
* **`bunfig.toml`** — Bun config; enables test coverage across the workspace.

This is what [`app:init`](/cli/commands/app-init) writes — see below.

## How the monorepo is generated

Two commands build the monorepo, one layered on the other.

<Steps>
  <Step title="app:init lays down the workspace root">
    [`app:init`](/cli/commands/app-init) writes the root tooling — `package.json` (with the `modules/*` workspace), `tsconfig.json`, `biome.jsonc`, `nx.json`, `bunfig.toml`, `.commitlintrc.ts`, `.gitignore`, `.zed/settings.json`, and an `.env.yml` — installs the shared dev dependencies (Biome, commitlint, husky, lint-staged, **nx**, TypeScript), initializes the git repository, and configures the husky hooks. It can optionally add the Claude and Codex skills.

    Run it on its own to turn an existing folder into an Ooneex workspace:

    ```bash theme={null}
    ooneex app:init --name=MovieApp --destination=movie-app
    ```
  </Step>

  <Step title="app:create builds the full app on top of it">
    [`app:create`](/cli/commands/app-create) creates the `app` and `shared` modules, the entrypoint, the shared database, `roles.yml`, and Docker files, then calls `app:init` internally for the workspace root, installs the runtime dependencies, and offers CI/CD files. This is the one command you normally run:

    ```bash theme={null}
    ooneex app:create --name=MovieApp --destination=movie-app
    ```
  </Step>

  <Step title="Add modules as the app grows">
    Each new domain becomes another workspace member, wired into the monorepo automatically:

    ```bash theme={null}
    ooneex module:create --name=movie --destination=app
    ```

    The generator scaffolds `modules/movie/`, registers it in `AppModule` and `SharedModule`, adds its `@module/movie` path alias to `tsconfig.json`, and adds its commit scope.
  </Step>
</Steps>

<Tip>
  `app:init` is the workspace root; `app:create` is `app:init` plus the `app`
  and `shared` modules and dependencies. Use `app:init` directly only when you
  want the monorepo scaffold without the starter modules.
</Tip>

## Working in the monorepo day to day

```bash theme={null}
bun install                      # resolve every module's deps into one node_modules
ooneex app:start                 # bring up Docker, then run every api/microservice/spa
ooneex module:create --name user # add a module — aliases and registries updated for you
bun run lint                     # Nx lints every module, skipping unchanged ones
bun run test                     # Nx tests every module
bun run check                    # the full install + build + lint + test gate
bunx commit                      # commitlint-guided conventional commit
```

<Note>
  A single `bun install` at the root installs dependencies for every module.
  You almost never run `bun install` inside a module folder — the workspace
  hoists and links everything from the root.
</Note>

## Use with Claude and Codex

Initialize the AI skills, then ask your agent to work across the monorepo in natural language — it uses the project's real module structure, aliases, and tooling.

<Tabs>
  <Tab title="Claude">
    ```bash theme={null}
    ooneex claude:init
    ```

    ```text Prompt icon="terminal" wrap theme={null}
    Add a `billing` module to my Ooneex monorepo, register it in AppModule
    and SharedModule, wire its @module/billing path alias, then run the
    formatter, linter, and tests across the workspace.
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    ooneex codex:init
    ```

    ```text Prompt icon="terminal" wrap theme={null}
    Add a `billing` module to my Ooneex monorepo, register it in AppModule
    and SharedModule, wire its @module/billing path alias, then run the
    formatter, linter, and tests across the workspace.
    ```
  </Tab>
</Tabs>

## External resources

<CardGroup cols={2}>
  <Card title="Bun Workspaces" icon={<Icon icon="cubes" size={16} />} href="https://bun.sh/docs/install/workspaces">
    How Bun resolves and links the `modules/*` members.
  </Card>

  <Card title="Nx" icon={<Icon icon="diagram-project" size={16} />} href="https://nx.dev">
    Task orchestration and caching for `lint`, `test`, and `build`.
  </Card>

  <Card title="TypeScript Paths" icon={<Icon icon="folder-tree" size={16} />} href="https://www.typescriptlang.org/tsconfig#paths">
    How the `@module/*` aliases map to module source.
  </Card>

  <Card title="Conventional Commits" icon={<Icon icon="code-commit" size={16} />} href="https://www.conventionalcommits.org/">
    The commit format enforced across the monorepo.
  </Card>
</CardGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Create your app" icon={<Icon icon="rocket" size={16} />} href="/getting-started/create-app">
    Scaffold the monorepo and build your first module.
  </Card>

  <Card title="Configuration" icon={<Icon icon="gear" size={16} />} href="/getting-started/configuration">
    The `.env.yml` and `roles.yml` files in the `shared` module.
  </Card>

  <Card title="Module overview" icon={<Icon icon="cube" size={16} />} href="/module/overview">
    What a module is and how it slices your domain.
  </Card>

  <Card title="app:create" icon={<Icon icon="terminal" size={16} />} href="/cli/commands/app-create">
    The command that generates the whole monorepo.
  </Card>
</CardGroup>
