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

# TypeScript Integration: Env Types and Handlers in Blaze

> Define your Env type once with createApp<Env>() and get fully typed req.env, req.params, and handler signatures across your entire Blaze Worker.

Blaze propagates your `Env` generic through the entire application — no casting, no `any`. The moment you write `createApp<Env>()`, every `req.env` access, every `req.params` lookup, and every handler signature becomes fully typed. Blaze exports `Handler`, `ErrorHandler`, `Middleware`, `NextFunction`, and `CookieOptions` as type-only exports, and `BlazeRequest`, `BlazeResponse`, and `BlazeError` as concrete class exports, so you can annotate standalone functions and augment request types anywhere in your codebase.

## Defining your Env type

Create a dedicated `types/env.ts` that mirrors your `wrangler.toml` bindings exactly. Pass it as the generic argument to `createApp<Env>()` — the type flows to every handler automatically.

```ts 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 and plain-text vars
  JWT_SECRET: string;
  ALLOWED_ORIGINS: string;
};
```

```ts src/index.ts theme={null}
import { createApp } from 'blaze';
import type { Env } from './types/env';

const app = createApp<Env>();

// req.env is now fully typed everywhere — no casting needed
app.get('/ping', async (req, res) => {
  const cached = await req.env.SESSION_KV.get('ping'); // ✅ KVNamespace
  const row    = await req.env.DB.prepare('SELECT 1').first(); // ✅ D1Database
  res.json({ cached, row });
});

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

<Tip>
  Keep `types/env.ts` as the single source of truth for your bindings. When you add or rename a binding in `wrangler.toml`, update this file first — TypeScript will flag every stale reference immediately.
</Tip>

## Handler and middleware types

Import the exported types from `blaze` to annotate standalone handler and middleware functions. This is especially useful for handlers that live in separate route files.

```ts theme={null}
import type { Handler, ErrorHandler, Middleware, NextFunction } from 'blaze';
import type { Env } from './types/env';
```

<CodeGroup>
  ```ts Handler theme={null}
  // A typed route handler
  const getUser: Handler<Env> = async (req, res, next) => {
    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: 'Not found' });
    res.json(user);
  };

  app.get('/users/:id', getUser);
  ```

  ```ts Middleware theme={null}
  // A typed middleware function
  const requireAuth: Middleware<Env> = async (req, res, next) => {
    const token = req.header('Authorization')?.replace('Bearer ', '');
    if (!token) return res.status(401).json({ error: 'Unauthorized' });

    try {
      req.user = await verifyJwt(token, req.env.JWT_SECRET);
      next();
    } catch (err) {
      next(err); // forwards to the error handler
    }
  };

  app.use('/api', requireAuth);
  ```

  ```ts ErrorHandler theme={null}
  // A typed error handler (4 arguments)
  const globalError: ErrorHandler<Env> = (err, req, res, next) => {
    const status  = err instanceof BlazeError ? err.status  : 500;
    const message = err instanceof BlazeError ? err.message : 'Internal Server Error';
    res.status(status).json({ error: message });
  };

  app.onError(globalError);
  ```

  ```ts NextFunction theme={null}
  // Using NextFunction explicitly
  import type { BlazeRequest, BlazeResponse, NextFunction } from 'blaze';
  import type { Env } from './types/env';

  function logDuration(
    req: BlazeRequest<Env>,
    res: BlazeResponse,
    next: NextFunction,
  ) {
    req.startTime = Date.now();
    next();
  }
  ```
</CodeGroup>

## Augmenting BlazeRequest

Middleware often attaches custom properties to `req` — things like `req.user` after auth or `req.id` from the `requestId` middleware. Declare these additions by augmenting the `BlazeRequest` interface in a `.d.ts` file so TypeScript knows about them everywhere.

```ts types/blaze.d.ts theme={null}
declare module 'blaze' {
  interface BlazeRequest {
    /** Populated by requireAuth middleware */
    user?: {
      id: string;
      email: string;
      isAdmin: boolean;
    };

    /** Set by requestId middleware — always present after global middleware runs */
    id: string;

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

Place this file anywhere TypeScript picks it up — `types/blaze.d.ts` works well, and you can reference it with `"include": ["src", "types"]` in `tsconfig.json`.

**When to use augmentation:** every time a middleware sets a property on `req` that downstream handlers need to read. Without the augmentation, TypeScript will error on `req.user` even though the property exists at runtime. With it, `req.user` is typed everywhere with zero extra imports.

<Note>
  The augmentation applies globally. If you access `req.id` in a route that runs *before* the `requestId` middleware, TypeScript won't warn you — that's a runtime concern. Use middleware ordering to enforce correctness.
</Note>

## Typed route params

`req.params` is typed based on your route pattern. Named segments — `:id`, `:slug`, `:org` — become string-typed keys on the params object. Wildcard segments (`*`) become `{ '*': string }`.

```ts theme={null}
// req.params is { id: string }
app.get('/users/:id', (req, res) => {
  const id: string = req.params.id; // ✅ — no cast needed
  res.json({ id });
});

// req.params is { org: string; repo: string }
app.get('/orgs/:org/repos/:repo', (req, res) => {
  const { org, repo } = req.params; // ✅
  res.json({ org, repo });
});

// req.params is { filename?: string }
app.get('/files/:filename?', (req, res) => {
  const filename = req.params.filename ?? 'index.html'; // ✅ string | undefined
  res.json({ filename });
});
```

## Recommended tsconfig.json

Use these settings to get full type coverage for Blaze and the Cloudflare Workers runtime:

```json tsconfig.json theme={null}
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "WebWorker"],
    "module": "ES2022",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "types": ["@cloudflare/workers-types"],
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src", "types"],
  "exclude": ["node_modules", "dist"]
}
```

| Option                                 | Why it matters                                                              |
| -------------------------------------- | --------------------------------------------------------------------------- |
| `target: ES2022`                       | Workers runtime supports ES2022 natively — no downlevelling needed          |
| `lib: ["ES2022", "WebWorker"]`         | Enables `Request`, `Response`, `crypto`, `fetch`, and other Web APIs        |
| `moduleResolution: bundler`            | Resolves Blaze's subpath exports (`blaze/middleware/cors`) correctly        |
| `strict: true`                         | Catches null dereferences and missing return types before deployment        |
| `types: ["@cloudflare/workers-types"]` | Types for `KVNamespace`, `D1Database`, `R2Bucket`, `ExecutionContext`, etc. |

## Type-only imports

Use `import type` for all Blaze type imports. Because types are erased at compile time, `import type` has zero runtime cost and ensures the import is never accidentally bundled as a value.

```ts theme={null}
// ✅ Type-only — zero bundle impact
import type { Handler, ErrorHandler, Middleware, NextFunction } from 'blaze';
import type { Env } from './types/env';

// ✅ Value import — only when you need the actual class/function at runtime
import { createApp, BlazeError } from 'blaze';
```

This is especially important for middleware types that are only used in function signatures — the bundler can tree-shake freely and your Worker's boot time stays minimal.
