Skip to main content
Blaze imposes no required directory layout — you could put everything in a single index.ts and it would work. But as your Worker grows, a feature-router layout scales cleanly and mirrors Express conventions that most teams already know. Each feature gets its own Router, its own file, and its own scoped middleware. The entry point wires them together.

Directory layout

Each layer has one job:

Entry point pattern

Your src/index.ts is the composition root. It creates the app, applies global middleware in order, mounts each feature router at its prefix, registers the global error handler, and exports the fetch entrypoint.
src/index.ts
Middleware registration order matters. requestId() runs before logger() so the logger can include req.id in its output. Always register your global error handler last — after all routes and sub-routers are mounted.

Feature routers

Each file under src/routes/ exports a single Router instance. The router handles its own scoped middleware, declares its own routes, and optionally defines a local error boundary for domain-specific errors.
src/routes/users.ts
Then mount it in src/index.ts:
src/index.ts (excerpt)
Co-locate your route handlers directly in the router file rather than splitting them into a separate controllers/ directory. For most Workers the added indirection costs more than it saves — a single routes/users.ts file with all five CRUD handlers is easy to navigate and test as a unit.

Shared types

Two files in src/types/ keep the TypeScript story clean across your entire codebase.
env.ts is the single source of truth for your Cloudflare bindings. It must stay in sync with wrangler.toml. Every binding name and type maps one-to-one.
src/types/env.ts

Wrangler configuration

wrangler.toml is the authoritative declaration of your Worker’s bindings, routes, and compatibility settings. Keep your src/types/env.ts in sync with every binding declared here.
wrangler.toml
When you add a new binding in wrangler.toml, add the matching property to Env in src/types/env.ts immediately. TypeScript will then surface every handler that needs updating — no grep required.