> ## Documentation Index
> Fetch the complete documentation index at: https://wemstudios.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Recommended Project Structure for Blaze Cloudflare Workers

> Organize a Blaze Worker with feature-based sub-routers, a shared types directory, and dedicated middleware files that grow cleanly with your project.

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

```
my-worker/
├── src/
│   ├── index.ts                  # Entry — createApp<Env>(), global middleware, sub-router mounts
│   ├── types/
│   │   ├── env.ts                # Env type (mirrors wrangler.toml exactly)
│   │   └── blaze.d.ts            # BlazeRequest augmentation (req.user, req.id, etc.)
│   ├── middleware/
│   │   ├── auth.ts               # requireAuth, requireAdmin
│   │   └── validate.ts           # Zod / Valibot schema validators
│   ├── routes/
│   │   ├── users.ts              # Router() for /users — CRUD handlers
│   │   ├── posts.ts              # Router() for /posts — CRUD handlers
│   │   └── admin.ts              # Router() for /admin — protected routes
│   └── lib/
│       ├── db.ts                 # Reusable D1 query helpers
│       └── errors.ts             # Domain-specific BlazeError subclasses
├── test/
│   ├── users.test.ts
│   └── posts.test.ts
├── wrangler.toml
├── tsconfig.json
└── package.json
```

Each layer has one job:

| Directory         | Responsibility                                                   |
| ----------------- | ---------------------------------------------------------------- |
| `src/types/`      | TypeScript contracts — Env bindings, request augmentations       |
| `src/middleware/` | Reusable middleware functions shared across routers              |
| `src/routes/`     | Feature routers — one file per resource, one `Router()` per file |
| `src/lib/`        | Pure helper functions — database utilities, domain errors        |
| `test/`           | Vitest test files that mirror the routes directory               |

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

```ts src/index.ts theme={null}
import { createApp } from 'blaze';
import { cors }      from 'blaze/middleware/cors';
import { logger }    from 'blaze/middleware/logger';
import { requestId } from 'blaze/middleware/request-id';

import { users } from './routes/users';
import { posts } from './routes/posts';

import type { Env } from './types/env';

const app = createApp<Env>();

// ── Global middleware — runs for every request ──────────────────────
app.use(requestId());                                  // attaches req.id + X-Request-Id header
app.use(logger());                                     // logs method, path, status, duration
app.use(cors({
  origins: (req) => req.env.ALLOWED_ORIGINS.split(','),
}));

// ── Feature routers ─────────────────────────────────────────────────
app.use('/users', users);
app.use('/posts', posts);

// ── Global error handler (must be last) ─────────────────────────────
app.onError((err, req, res, next) => {
  res.status(err.status ?? 500).json({ error: err.message ?? 'Server error' });
});

export default { fetch: app.fetch };
```

<Note>
  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.
</Note>

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

```ts src/routes/users.ts theme={null}
import { createApp }   from 'blaze';
import { requireAuth } from '../middleware/auth';
import type { Env }    from '../types/env';

export const users = createApp<Env>().Router();

// ── Scoped middleware — only runs for routes mounted under /users ───
users.use(requireAuth);

// ── Routes ─────────────────────────────────────────────────────────
users.get('/', async (req, res) => {
  const page  = Number(req.query.get('page')  ?? 1);
  const limit = Number(req.query.get('limit') ?? 20);

  const { results } = await req.env.DB
    .prepare('SELECT id, name, email FROM users LIMIT ? OFFSET ?')
    .bind(limit, (page - 1) * limit)
    .all();

  res.json({ page, results });
});

users.get('/:id', async (req, res) => {
  const user = await req.env.DB
    .prepare('SELECT * FROM users WHERE id = ?')
    .bind(req.params.id)
    .first();

  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json(user);
});

users.post('/', async (req, res) => {
  const { name, email } = await req.json<{ name: string; email: string }>();

  const result = await req.env.DB
    .prepare('INSERT INTO users (name, email) VALUES (?, ?) RETURNING id')
    .bind(name, email)
    .first();

  res.status(201).json({ id: result.id });
});

users.put('/:id', async (req, res) => {
  const body = await req.json<{ name?: string; email?: string }>();

  await req.env.DB
    .prepare('UPDATE users SET name = ?, email = ? WHERE id = ?')
    .bind(body.name, body.email, req.params.id)
    .run();

  res.json({ ok: true });
});

users.delete('/:id', async (req, res) => {
  await req.env.DB
    .prepare('DELETE FROM users WHERE id = ?')
    .bind(req.params.id)
    .run();

  res.status(204).send();
});
```

Then mount it in `src/index.ts`:

```ts src/index.ts (excerpt) theme={null}
import { users } from './routes/users';

app.use('/users', users);
// GET  /users       → users.get('/')
// GET  /users/:id   → users.get('/:id')
// POST /users       → users.post('/')
// etc.
```

<Tip>
  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.
</Tip>

## Shared types

Two files in `src/types/` keep the TypeScript story clean across your entire codebase.

<Tabs>
  <Tab title="types/env.ts">
    `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.

    ```ts src/types/env.ts theme={null}
    export type Env = {
      // KV Namespaces
      SESSION_KV:    KVNamespace;
      RATE_LIMIT_KV: KVNamespace;

      // D1 Databases
      DB: D1Database;

      // R2 Buckets
      ASSETS: R2Bucket;

      // Durable Objects
      ROOMS: DurableObjectNamespace;

      // Queues
      JOBS: Queue<{ type: string; payload: unknown }>;

      // Workers AI
      AI: Ai;

      // Secrets / plain-text vars
      JWT_SECRET:      string;
      ALLOWED_ORIGINS: string;
    };
    ```
  </Tab>

  <Tab title="types/blaze.d.ts">
    `blaze.d.ts` augments the `BlazeRequest` interface so that properties attached by middleware — like `req.user` or `req.id` — are typed everywhere without explicit imports.

    ```ts src/types/blaze.d.ts theme={null}
    declare module 'blaze' {
      interface BlazeRequest {
        /** Set by requestId middleware */
        id: string;

        /** Set by requireAuth middleware */
        user?: {
          id:      string;
          email:   string;
          isAdmin: boolean;
        };

        /** Set by a timing middleware */
        startTime: number;
      }
    }
    ```

    TypeScript picks this up automatically if `types/` is included in `tsconfig.json`:

    ```json tsconfig.json (excerpt) theme={null}
    {
      "include": ["src", "types"]
    }
    ```
  </Tab>
</Tabs>

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

```toml wrangler.toml theme={null}
name            = "my-worker"
main            = "src/index.ts"
compatibility_date = "2024-09-23"

[[kv_namespaces]]
binding = "SESSION_KV"
id      = "your-kv-namespace-id"

[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id      = "your-rate-limit-kv-id"

[[d1_databases]]
binding      = "DB"
database_name = "myapp"
database_id  = "your-d1-database-id"

[[r2_buckets]]
binding     = "ASSETS"
bucket_name = "my-assets"

[[queues.producers]]
binding    = "JOBS"
queue      = "my-jobs-queue"

[ai]
binding = "AI"

[vars]
ALLOWED_ORIGINS = "https://example.com,https://app.example.com"
```

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.
