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 rootpackage.json declares the workspace, and everything under modules/* is a workspace member.
package.json is the heart of the workspace:
package.json
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 undermodules/<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 roottsconfig.json maps each module to a @module/<name> alias, so modules import each other by name instead of brittle relative paths:
tsconfig.json
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-houseworkspace: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:
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 See
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: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.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.- Claude
- Codex
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.