Project structure
TypeScript projects keep sources under src/; JavaScript projects put the same directories at the project root. Both layouts are detected automatically — you never configure which one you are using.
src/
api/ route handlers -> HTTP endpoints under /api
web/ page handlers -> HTTP endpoints under /
ws/ socket handlers -> WebSocket endpoints
bus/ broker connections -> ctx.bus.<filename>
consumers/ message handlers -> subscriptions
mcp/ tools, resources,
prompts -> MCP server
di/ injectable values
services/ injectable services
middlewares/ request middlewares
main.ts bootstrap()
.clove/ generated types (gitignored)The directories
| Directory | Contains | Becomes |
|---|---|---|
api/ | Modules whose default export is get(), post(), … | HTTP routes under /api, path mirroring the file path |
web/ | Same as api/ | The same, but mounted at the root / — for HTML pages |
ws/ | Modules whose default export is ws() | WebSocket endpoints under /ws/… |
bus/ | Modules whose default export is bus() | ctx.bus.<filename>, one broker connection each — see Message bus |
consumers/ | Modules whose default export is consume() | A subscription each. The path names the consumer; the channel is declared in the file |
mcp/ | tools/, resources/ and prompts/ subdirectories | An MCP server at /mcp |
services/ | Modules whose default export is service() | ctx.<filename>, a singleton created at boot |
di/ | Modules whose default export is di() | ctx.<filename>, scoped per its declared lifetime |
middlewares/ | Modules whose default export is middleware() | Wrappers around every route, run in order |
Files anywhere else are ignored by the scanner — put helpers, types and constants wherever you like.
Naming is the API
A file's name determines its key on ctx, and a route file's path determines its URL. services/auth.ts becomes ctx.auth; api/v1/login.post.ts becomes POST /api/v1/login.
This means renaming a file is a breaking change to your own code — which is the point. There is exactly one place a name is declared.
consumers/ is the deliberate exception: a channel is a contract shared with whoever publishes it, not a name this project owns, so it is declared in the file rather than derived from the path. See Message bus.
.clove/
clove dev, clove build and clove types write .clove/types.d.ts, which augments the Ctx interface with one entry per file in services/ and di/, and the BusRegistry interface with one per file in bus/. The scaffolded tsconfig.json includes it, and the scaffolded .gitignore excludes it — it is a build artefact, regenerated from the filesystem. See Typed context.
main.ts
The entry point. In the default layout it does one thing:
import { bootstrap } from "clovejs"
bootstrap()Everything else is discovered. See Bootstrap for the options it accepts, and Express interop if Clove is not going to own the process.
JavaScript layout
Identical, minus src/ and tsconfig.json:
api/
ws/
bus/
consumers/
di/
services/
middlewares/
main.jsRun clove scaffold --js to create it.