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

# BlazeResponse: Sending JSON, HTML, Streams, and More

> BlazeResponse provides Express-style res.json(), res.send(), res.html(), res.stream(), and res.redirect() methods on top of the Web Platform Response.

`BlazeResponse` is constructed once per request inside `app.fetch` and passed alongside `req` through every handler and middleware. Internally it holds a `Promise` that `app.fetch` awaits. Calling any terminal method — `res.json()`, `res.send()`, `res.html()`, `res.redirect()`, `res.stream()`, or `res.raw()` — resolves that promise and sends the response to the client, mirroring the Express `res.send()` pattern. Calling a terminal method more than once is safe: subsequent calls are silently ignored.

***

## Response methods

| Method                           | Description                                                                                                                                                                                                                                |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `res.json(data, status?)`        | Respond with JSON. Sets `Content-Type: application/json; charset=utf-8`.                                                                                                                                                                   |
| `res.send(body?, status?)`       | Respond with text or a buffer. Auto-detects `Content-Type` when not already set — `text/plain` for strings, `application/octet-stream` for buffers.                                                                                        |
| `res.html(markup, status?)`      | Respond with `Content-Type: text/html; charset=utf-8`.                                                                                                                                                                                     |
| `res.redirect(url, status?)`     | Issue a redirect. Defaults to 302. Accepts any 3xx status code.                                                                                                                                                                            |
| `res.stream(fn, status?)`        | Stream a response. `fn` receives a `WritableStream` — write to it and close when done. The HTTP response begins sending immediately.                                                                                                       |
| `res.status(code)`               | Set the HTTP status code. **Chainable** — returns `res`.                                                                                                                                                                                   |
| `res.header(name, value)`        | Set a response header. **Chainable** — returns `res`.                                                                                                                                                                                      |
| `res.cookie(name, value, opts?)` | Set a `Set-Cookie` header. **Chainable** — returns `res`.                                                                                                                                                                                  |
| `res.vary(field)`                | Append a field to the `Vary` response header. **Chainable** — returns `res`.                                                                                                                                                               |
| `res.type(mime)`                 | Set `Content-Type` by shorthand (`'json'`, `'html'`, `'text'`, `'png'`, etc.) or full MIME string. **Chainable** — returns `res`.                                                                                                          |
| `res.raw(response)`              | Pass through a raw Web Platform `Response` (e.g. from a Durable Object `fetch()`).                                                                                                                                                         |
| `res.onSend(fn)`                 | Register a synchronous hook called just before the response is resolved. `fn` receives the `Response` and must return a (possibly modified) `Response`. Hooks run in registration order. **Chainable is not applicable** — returns `void`. |
| `res.headersSent`                | `true` after any terminal method has been called. Use this guard in middleware to avoid attempting a second response.                                                                                                                      |

### onSend hook

`res.onSend()` lets middleware transform the outgoing `Response` — for example, to inject headers — without needing to intercept the terminal call itself. Register hooks before the terminal method is called; each hook receives the current `Response` and must return a `Response`.

```ts theme={null}
function responseTime(req, res, next) {
  const start = Date.now()
  res.onSend((response) => {
    // Clone headers so we can mutate them
    const headers = new Headers(response.headers)
    headers.set('X-Response-Time', `${Date.now() - start}ms`)
    return new Response(response.body, { status: response.status, headers })
  })
  next()
}

app.use(responseTime)
```

### headersSent guard

Check `res.headersSent` in middleware that may execute after a response has already been sent — for example, in cleanup logic:

```ts theme={null}
app.use((req, res, next) => {
  next()
  // After the downstream handler returns, check if a response was sent
  if (!res.headersSent) {
    // Nothing responded — fall through to the next handler or notFound
  }
})
```

***

## Sending JSON

`res.json()` is the most common response method. Pass any serialisable value and Blaze handles `JSON.stringify` and the `Content-Type` header for you.

<CodeGroup>
  ```ts 200 OK 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)  // 200 by default
  })
  ```

  ```ts 201 Created theme={null}
  app.post('/users', async (req, res) => {
    const { name, email } = await req.json()

    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 })
  })
  ```
</CodeGroup>

***

## Sending text and HTML

Use `res.send()` for plain text and `res.html()` for full HTML documents or fragments.

<CodeGroup>
  ```ts Plain text theme={null}
  app.get('/robots.txt', (req, res) => {
    res.send('User-agent: *\nDisallow: /admin\n')
  })
  ```

  ```ts HTML theme={null}
  app.get('/welcome', (req, res) => {
    res.html(`
      <!doctype html>
      <html>
        <head><title>Welcome</title></head>
        <body><h1>Hello from Blaze 🔥</h1></body>
      </html>
    `)
  })
  ```

  ```ts HTML with status theme={null}
  app.get('/maintenance', (req, res) => {
    res.status(503).html('<h1>Down for maintenance</h1>')
  })
  ```
</CodeGroup>

***

## Redirects

`res.redirect()` issues an HTTP redirect. The default status is `302` (temporary). Pass a second argument to use a different 3xx code.

```ts theme={null}
// 302 Temporary redirect (default)
app.get('/old-path', (req, res) => {
  res.redirect('/new-path')
})

// 301 Permanent redirect
app.get('/legacy', (req, res) => {
  res.redirect('https://example.com/docs', 301)
})

// Redirect after form submission
app.post('/login', async (req, res) => {
  const { email, password } = await req.json()
  const session = await authenticate(req.env.DB, email, password)

  if (!session) return res.status(401).json({ error: 'Invalid credentials' })

  res
    .cookie('session', session.token, { httpOnly: true, secure: true, sameSite: 'Lax' })
    .redirect('/dashboard')
})
```

***

## Streaming responses

`res.stream()` starts the HTTP response immediately and pipes data to the client as your `fn` writes to a `WritableStream`. Use it for server-sent events (SSE), large file transfers, or streaming AI completions.

<Tabs>
  <Tab title="Server-sent events">
    ```ts theme={null}
    app.get('/events', (req, res) => {
      res.header('Cache-Control', 'no-cache')
      res.header('X-Accel-Buffering', 'no')
      res.type('text/event-stream')

      res.stream(async (writable) => {
        const writer = writable.getWriter()
        const encoder = new TextEncoder()

        for (let i = 1; i <= 5; i++) {
          await writer.write(encoder.encode(`data: {"count":${i}}\n\n`))
          await new Promise((r) => setTimeout(r, 1000))
        }

        await writer.close()
      })
    })
    ```
  </Tab>

  <Tab title="Streaming AI">
    ```ts theme={null}
    app.post('/ai/stream', async (req, res) => {
      const { prompt } = await req.json<{ prompt: string }>()

      const aiStream = await req.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
        messages: [{ role: 'user', content: prompt }],
        stream: true,
      })

      res.header('Content-Type', 'text/event-stream')
      res.stream((writable) => aiStream.pipeTo(writable))
    })
    ```
  </Tab>

  <Tab title="R2 file stream">
    ```ts theme={null}
    app.get('/files/:key', async (req, res) => {
      const obj = await req.env.R2.get(req.params.key)
      if (!obj) return res.status(404).json({ error: 'Not found' })

      res
        .header('Content-Type', obj.httpMetadata?.contentType ?? 'application/octet-stream')
        .header('ETag', obj.httpEtag)
        .stream((writable) => obj.body.pipeTo(writable))
    })
    ```
  </Tab>
</Tabs>

***

## Setting headers and cookies

`res.header()` and `res.cookie()` are chainable and can be combined with any terminal method.

### Headers

```ts theme={null}
app.get('/api/data', async (req, res) => {
  const data = await fetchData(req.env.DB)

  res
    .header('X-Request-Id', req.id)
    .header('Cache-Control', 'public, max-age=60')
    .vary('Accept-Encoding')
    .json(data)
})
```

### Cookies

`res.cookie()` accepts a `CookieOptions` object as its third argument:

<ResponseField name="domain" type="string">
  The `Domain` attribute of the cookie. Restricts the cookie to the given domain and its subdomains.
</ResponseField>

<ResponseField name="expires" type="Date">
  The `Expires` attribute. Sets an absolute expiry time for the cookie.
</ResponseField>

<ResponseField name="httpOnly" type="boolean">
  When `true`, sets the `HttpOnly` attribute to prevent client-side script access.
</ResponseField>

<ResponseField name="maxAge" type="number">
  The `Max-Age` attribute in seconds. Takes precedence over `expires` in modern browsers.
</ResponseField>

<ResponseField name="path" type="string">
  The `Path` attribute. Restricts the cookie to the given path prefix. Defaults to `/`.
</ResponseField>

<ResponseField name="secure" type="boolean">
  When `true`, sets the `Secure` attribute so the cookie is only sent over HTTPS.
</ResponseField>

<ResponseField name="sameSite" type="&#x22;Strict&#x22; | &#x22;Lax&#x22; | &#x22;None&#x22;">
  The `SameSite` attribute. Controls cross-site cookie sending. Use `'None'` with `secure: true` for cross-origin requests.
</ResponseField>

<ResponseField name="partitioned" type="boolean">
  When `true`, sets the `Partitioned` attribute (CHIPS). Required for cross-site cookies in some browser privacy modes.
</ResponseField>

```ts theme={null}
app.post('/auth/login', async (req, res) => {
  const { email, password } = await req.json()
  const token = await issueToken(req.env.DB, email, password)

  if (!token) return res.status(401).json({ error: 'Invalid credentials' })

  res
    .cookie('session', token, {
      httpOnly: true,
      secure: true,
      sameSite: 'Lax',
      maxAge: 60 * 60 * 24 * 7,  // 7 days
      path: '/',
    })
    .status(200)
    .json({ ok: true })
})
```

***

## Chaining status

`res.status(code)` returns `res`, so you can chain it directly before any terminal method. You can also pass `status` as the second argument to `res.json()` and `res.send()` for a more compact style.

<CodeGroup>
  ```ts Chain style theme={null}
  res.status(201).json({ id: newId })
  res.status(204).send()
  res.status(403).json({ error: 'Forbidden' })
  ```

  ```ts Inline style theme={null}
  res.json({ id: newId }, 201)
  res.send(null, 204)
  ```
</CodeGroup>

<Note>
  `res.status()` only sets the status code — it does not send the response.
  You must always follow it with a terminal method (`json`, `send`, `html`,
  `redirect`, or `stream`).
</Note>
