Introduction
Project structure
A conventional folder layout that Routecraft expects out of the box.
Folder layout
Each capability is its own folder, grouped under a domain folder. route.ts is the
capability's public surface; everything else in the folder is private to it.
my-app
├── craft.config.ts
├── capabilities
│ ├── comms
│ │ └── send-email
│ │ ├── route.ts
│ │ ├── route.test.ts
│ │ └── README.md
│ └── reports
│ └── daily-summary
│ ├── route.ts
│ ├── route.test.ts
│ ├── summarise.ts # internal helper, private to this capability
│ └── __fixtures__
├── shared
│ └── amount.ts # pure helper shared by several capabilities
├── adapters
│ └── google-sheets
│ ├── index.ts # the googleSheets() factory, the only file imported
│ ├── source.ts
│ ├── destination.ts
│ └── types.ts
├── plugins
│ └── logger.ts
├── package.json
├── tsconfig.json
└── .env
All application code can live at the project root or inside an optional src folder.
Routecraft treats both layouts identically.
The capability folder
A capability is a folder under capabilities/, named for its id, grouped beneath a domain
folder. bunx create-routecraft scaffolds this shape for you.
The file is named route.ts because that is what the craft() builder returns. The
user-facing noun for the unit of work is still "capability"; "route" is just the name of the
public-surface file.
// capabilities/comms/send-email/route.ts
import { craft, http } from '@routecraft/routecraft'
import { z } from 'zod'
export const SendEmailInput = z.object({ to: z.string().email(), subject: z.string() })
export type SendEmailInput = z.infer<typeof SendEmailInput>
export default craft()
.id('send-email')
.input({ body: SendEmailInput })
.from<SendEmailInput>(/* source */)
.to(http({ method: 'POST', url: 'https://api.example.com/send' }))
Reuse between capabilities
Capabilities never import each other's internal files. To call one capability from another,
use direct() with the callee's id, and import its
types from its route.ts:
// capabilities/reports/daily-summary/route.ts
import { craft, direct } from '@routecraft/routecraft'
import { type SendEmailInput } from '../../comms/send-email/route'
export default craft()
.id('daily-summary')
.from(/* ... */)
.to(direct<SendEmailInput>('send-email'))
This keeps the contract (the id plus the exported types) the only coupling between capabilities. Internals stay free to change.
Shared helpers
A helper used by a single capability stays inside that capability's folder. When two or more
capabilities need the same pure helper (validate an amount, parse a date, a shared domain
type), put it in a top-level shared/ folder next to capabilities/:
shared
├── amount.ts # parseAmount, assertPositive
└── dates.ts # toIsoDate
Any capability may import from shared/. Keep it pure: validators, parsers, formatters, and
types, with no side effects and no imports back into a capability's internals. shared/ is the
single-project answer, so a one-app repo never needs workspace tooling just to share a date
parser.
When the repo grows into multiple runtimes (several apps under apps/), shared code graduates
from shared/ to a workspace package that each app depends on as a local dependency, so the
boundary stays explicit across app lines.
Single-file shorthand
A trivial capability with no internal files can be a single file, capabilities/<id>.ts,
that default-exports the route. This is fine for small or example-only capabilities. The
folder shape is the default once a capability grows a test, a README, or any private helper.
Sub-folders inside capabilities/ are supported to any depth. The capability id set in
.id() is what identifies it at runtime, not the path or filename.
Other folders
Adapters vs plugins: an adapter connects to an external system (a queue, an API, a file system). A plugin extends the runtime itself (exposing MCP, adding metrics, wiring up observability).
Files
craft.config.ts
The config file is the entry point for the Routecraft runtime. A minimal setup:
// craft.config.ts
import type { CraftConfig } from "@routecraft/routecraft";
const config: CraftConfig = {};
export default config;
Related
Composing Capabilities
Reuse capabilities with direct() and exported contract types.
Configuration reference
craft.config.ts options and context settings.