Skip to main content
Every Talos 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 scaffolds the whole structure, and 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.

What the layout buys you

An Talos 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. There is one install and one lockfile, since bun install resolves every module’s dependencies into a single node_modules at the root, so versions stay consistent across the whole app. Tooling is shared too: the formatter, linter, TypeScript config, commit rules, and git hooks are defined once at the root and apply to every module. Modules reference each other through path aliases and Bun workspaces, so cross-module imports need no build-and-publish step in between. The in-house workspace:run task runner executes lint, test, and build across every module and only re-runs what changed. And because microservices and SPAs live as modules in the same repo, they can still build and deploy on their own.

Anatomy of the workspace

The monorepo is a Bun workspace. The root package.json declares the workspace, and everything under modules/* is a workspace member.
The root package.json is the heart of the workspace:
package.json
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. The root ships no scripts of its own — tooling runs through the talos CLI (talos workspace:run, talos workspace:check, bunx biome check --write), which reaches every module without wrapper scripts to maintain.

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:
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 and Single Page App.

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:
tsconfig.json
When you run module:create or microservice:create, the generator adds the new alias to tsconfig.json automatically, and registers the module in AppModule and its entities in SharedModule. 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 workspace:run

Tooling tasks run across the whole monorepo through the in-house workspace:run task runner shipped in the Talos CLI. Calling talos workspace:run --commands=<script> executes the matching package.json script in every package and module at once, in workspace dependency order, with a content-addressed cache:
Targets run in dependency order — a target waits for the workspace dependencies it relies on — so a module’s dependencies are built before it is linted, tested, or built. Independent targets run concurrently, up to the machine’s available parallelism. Every task is cached in var/cache/workspace/: the cache key hashes each of the target’s source files individually, the fingerprints of all its transitive workspace dependencies, the script text, and the root config files (package.json, bun.lock, tsconfig.json, biome.jsonc). A task therefore re-runs exactly when something it can observe has changed, so repeated runs only touch what you actually modified. There is no nx.json or any other extra config file — caching is on by default, and --no-cache turns it off. A target that declares no matching package.json script is skipped rather than failed. Rust crates are covered too: a crate with no script of its own falls back to the matching cargo command, so install runs cargo fetch, build runs cargo build, fmt runs cargo fmt, lint runs cargo clippy --all-targets --quiet, and test runs cargo test. Run the tasks from the root:
workspace:check is the full verification gate. It installs, then builds, then runs coverage and lint concurrently and prints them as one combined report, exiting non-zero if either fails. It uses the same dependency ordering and caching, so it only re-runs what changed.
You can also invoke the task runner directly for ad-hoc runs. It executes your scripts across every module in dependency order, with a granular, content-addressed cache in var/cache/workspace/. A live view shows a progress bar and each running task’s latest output as it runs; pass --logs for plain, label-prefixed streaming in CI:
See workspace:run and workspace:check for the full reference.

Shared root tooling

Because it’s a monorepo, configuration is defined once at the root and inherited by every module. biome.jsonc is the one formatter and linter for all modules, applied everywhere by bunx biome check --write. tsconfig.json holds the strict, shared TypeScript baseline plus the module path aliases, and each module’s tsconfig.json extends from it. Commit messages are linted by a git commit-msg hook installed with commitlint:init; the rules are built into the Talos CLI and accept common or any module/package name as the scope, so valid scopes track your modules automatically. bunfig.toml is the Bun config, where test coverage is turned on across the workspace. This is what app:init writes, described below.

How the monorepo is generated

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

app:init lays down the workspace root

app:init writes the root tooling (package.json with the modules/* workspace, tsconfig.json, biome.jsonc, bunfig.toml, .gitignore, .zed/settings.json, and an .env.yml), installs the shared dev dependencies (Biome, @talosjs/command, TypeScript), initializes the git repository, and installs the commit-msg hook. It also prompts you to scaffold skills for your coding assistants (Claude and Codex by default, plus Cursor, Gemini, Windsurf, and more).Run it on its own to turn an existing folder into a Talos workspace:
2

app:create builds the full app on top of it

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

Add modules as the app grows

Each new domain becomes another workspace member, wired into the monorepo automatically:
The generator scaffolds modules/movie/, registers it in AppModule and SharedModule, and adds its @module/movie path alias to tsconfig.json. Its module name is immediately valid as a commit scope — nothing to register.
app:init is the workspace root. app:create is app:init plus the app and shared modules and their dependencies. Use app:init directly only when you want the monorepo scaffold without the starter modules.

Working in the monorepo day to day

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.

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

External resources

Bun Workspaces

How Bun resolves and links the modules/* members.

TypeScript Paths

How the @module/* aliases map to module source.

Conventional Commits

The commit format enforced across the monorepo.

Next steps

Create your app

Scaffold the monorepo and build your first module.

Configuration

The .env.yml and roles.yml files in the shared module.

Module overview

What a module is and how it slices your domain.

app:create

The command that generates the whole monorepo.