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

# Blaze App: createApp, Routing, and CF Workers Export

> createApp<Env>() is Blaze's entry point. Register routes, mount middleware, handle errors, and export the fetch handler for Cloudflare Workers.

`createApp<Env>()` is the entry point for every Blaze application. It returns a fully-configured app instance with Express-style routing methods, a composable middleware stack, and the `app.fetch` handler you export directly to the Cloudflare Workers runtime. Providing your `Env` type once here propagates type safety through every route, middleware, and sub-router in your app.

***

## Creating an app

Pass your typed `Env` interface — mirroring the bindings declared in `wrangler.toml` — as the generic argument to `createApp`. Export `app.fetch` as the `fetch` property of the module's default export to wire it into the Workers runtime.

```ts theme={null}
import { createApp } from 'blaze'

type Env = {
  DB: D1Database
  KV: KVNamespace
  R2: R2Bucket
  QUEUE: Queue
  AI: Ai
  JWT_SECRET: string
}

const app = createApp<Env>()

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Blaze!' })
})

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

<Note>
  Always use `export default { fetch: app.fetch }` (Module Worker syntax) rather
  than `addEventListener('fetch', …)`. Module Workers have lower cold-start
  latency and get first-class access to `env` bindings.
</Note>

***

## App methods

Every method on the app object returns `app` (except `app.Router()`, which returns a new sub-router, and `app.fetch` / `app.scheduled`, which are the Workers entrypoints), so you can chain registrations fluently.

| Method                           | Description                                                                                                                                                               |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app.get(path, ...fns)`          | Register a GET route. Accepts one or many handlers forming an inline middleware stack.                                                                                    |
| `app.post(path, ...fns)`         | Register a POST route.                                                                                                                                                    |
| `app.put(path, ...fns)`          | Register a PUT route.                                                                                                                                                     |
| `app.patch(path, ...fns)`        | Register a PATCH route.                                                                                                                                                   |
| `app.delete(path, ...fns)`       | Register a DELETE route.                                                                                                                                                  |
| `app.all(path, ...fns)`          | Register a route that matches any HTTP method.                                                                                                                            |
| `app.use([path], ...fns)`        | Register middleware, optionally scoped to a path prefix. Accepts plain handlers, error handlers, and sub-routers.                                                         |
| `app.route(path)`                | Return a chainable `Route` builder — call `.get()`, `.post()`, `.put()`, etc. on the same path without repeating it.                                                      |
| `app.Router()`                   | Create a new sub-router (mini-app). Mount it with `app.use(prefix, router)`.                                                                                              |
| `app.onError(fn)`                | Register a global 4-argument error handler: `(err, req, res, next)`.                                                                                                      |
| `app.notFound(fn)`               | Register a handler called when no route matched. Defaults to a 404 JSON response.                                                                                         |
| `app.fetch(req, env, ctx)`       | The Cloudflare Workers fetch entrypoint. Pass as `fetch: app.fetch`.                                                                                                      |
| `app.scheduled(event, env, ctx)` | The Cloudflare Workers cron trigger entrypoint. Pass as `scheduled: app.scheduled`. Signature: `(event: ScheduledEvent, env: E, ctx: ExecutionContext) => Promise<void>`. |

***

## Registering routes

Use the HTTP-method shortcuts to register routes. Each accepts the path pattern followed by one or more handler functions. When you pass multiple handlers, Blaze runs them as an inline middleware stack for that route — each must call `next()` to continue.

<CodeGroup>
  ```ts GET theme={null}
  app.get('/users/: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)
  })
  ```

  ```ts POST theme={null}
  app.post('/users', 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 })
  })
  ```

  ```ts PUT theme={null}
  app.put('/users/: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 })
  })
  ```

  ```ts DELETE theme={null}
  app.delete('/users/:id', async (req, res) => {
    await req.env.DB
      .prepare('DELETE FROM users WHERE id = ?')
      .bind(req.params.id)
      .run()

    res.status(204).send()
  })
  ```
</CodeGroup>

### Inline middleware on a route

Pass multiple functions to add per-route middleware — useful for auth guards or validators that apply to a single endpoint:

```ts theme={null}
import { requireAuth } from './middleware/auth'
import { validateBody } from './middleware/validate'

app.post(
  '/posts',
  requireAuth,                        // runs first — calls next() or rejects
  validateBody(postSchema),           // runs second — calls next() or returns 422
  async (req, res) => {               // final handler
    const body = await req.json()
    const post = await createPost(req.env.DB, body)
    res.status(201).json(post)
  },
)
```

***

## Mounting middleware

`app.use()` registers middleware that runs for every request, or — when you supply a path prefix — for requests whose URL starts with that prefix.

<Steps>
  <Step title="Global middleware">
    Middleware registered without a path prefix runs on every request, in registration order:

    ```ts theme={null}
    import { requestId } from 'blaze/middleware/request-id'
    import { logger }    from 'blaze/middleware/logger'
    import { cors }      from 'blaze/middleware/cors'

    app.use(requestId())
    app.use(logger())
    app.use(cors({ origins: (req) => req.env.ALLOWED_ORIGINS.split(',') }))
    ```
  </Step>

  <Step title="Path-scoped middleware">
    Middleware registered with a path prefix runs only when the request URL begins with that prefix. The prefix is stripped from `req.path` inside the middleware, mirroring Express behaviour:

    ```ts theme={null}
    import { basicAuth } from 'blaze/middleware/basic-auth'

    app.use('/admin', basicAuth({
      username: (req) => req.env.ADMIN_USER,
      password: (req) => req.env.ADMIN_PASS,
    }))
    ```
  </Step>

  <Step title="Custom middleware">
    Write middleware as any `(req, res, next)` function and pass it to `app.use()`:

    ```ts theme={null}
    function requestTimer(req, res, next) {
      req.startTime = Date.now()
      res.onSend((response) => {
        response.headers.set('X-Response-Time', `${Date.now() - req.startTime}ms`)
        return response
      })
      next()
    }

    app.use(requestTimer)
    ```
  </Step>
</Steps>

***

## Route chaining

`app.route(path)` returns a chainable `Route` object. Register multiple HTTP methods on the same path without repeating the path string:

```ts theme={null}
app
  .route('/posts/:id')
  .get(async (req, res) => {
    const post = await req.env.DB
      .prepare('SELECT * FROM posts WHERE id = ?')
      .bind(req.params.id)
      .first()

    if (!post) return res.status(404).json({ error: 'Post not found' })
    res.json(post)
  })
  .put(async (req, res) => {
    const { title, body } = await req.json<{ title: string; body: string }>()

    await req.env.DB
      .prepare('UPDATE posts SET title = ?, body = ? WHERE id = ?')
      .bind(title, body, req.params.id)
      .run()

    res.json({ ok: true })
  })
  .delete(async (req, res) => {
    await req.env.DB
      .prepare('DELETE FROM posts WHERE id = ?')
      .bind(req.params.id)
      .run()

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

<Tip>
  Route chaining is ideal for REST resource endpoints — it keeps GET, PUT,
  PATCH, and DELETE handlers co-located and reduces visual noise from repeated
  path strings.
</Tip>

***

## Scheduled (cron) support

Blaze exposes `app.scheduled` as a second Workers entrypoint. Export it alongside `app.fetch` to handle Cloudflare Cron Triggers declared in `wrangler.toml`.

```toml wrangler.toml theme={null}
[triggers]
crons = ["0 * * * *"]  # every hour
```

```ts src/index.ts theme={null}
import { createApp } from 'blaze'

type Env = { DB: D1Database; KV: KVNamespace }

const app = createApp<Env>()

app.get('/health', (req, res) => res.json({ ok: true }))

// Export both entrypoints
export default {
  fetch: app.fetch,
  scheduled: app.scheduled,
}
```

<Note>
  The default `app.scheduled` implementation is a no-op. Override it by
  replacing `app.scheduled` with your own `async (event, env, ctx) => void`
  function, or handle cron logic inline in the export:

  ```ts theme={null}
  export default {
    fetch: app.fetch,
    async scheduled(event, env, ctx) {
      ctx.waitUntil(runHourlySync(env))
    },
  }
  ```
</Note>

***

## Not found handler

`app.notFound()` registers a handler called when the router exhausts its entire layer stack without sending a response. The built-in default returns `{ error: 'Not Found' }` with a 404 status.

```ts theme={null}
app.notFound((req, res) => {
  res.status(404).json({
    error: 'Not Found',
    path: req.url,
    hint: 'Check the API reference at https://docs.example.com',
  })
})
```

<Tip>
  Register `app.notFound()` after all routes and middleware so Blaze only calls
  it when nothing else has responded.
</Tip>
