Custom Server Handlers

Beyond JSON — Streams, Files, Redirects

Custom Server Handlers

Handlers can return any h3-compatible value — objects become JSON, ReadableStreams stream, raw Response objects pass through unchanged.

4 min read Level 3/5 #nuxt#server#advanced
What you'll learn
  • Return a streamed response for server-sent events
  • Send a file as a downloadable stream
  • Return a raw Response object for full control over the wire

Most handlers return a plain object and let Nitro serialize it as JSON. When that’s not enough — SSE, file downloads, custom content types — handlers can return streams or raw Response objects.

Server-Sent Events

// server/api/events.ts
export default defineEventHandler((event) => {
  const stream = new ReadableStream({
    start(controller) {
      let n = 0
      const id = setInterval(() => {
        controller.enqueue(`data: tick ${n++}\n\n`)
      }, 1000)
      event.node.req.on('close', () => {
        clearInterval(id)
        controller.close()
      })
    },
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  })
})

The browser-side EventSource('/api/events') will receive each tick.

Streaming Files

For large files, stream them instead of loading into memory:

// server/api/download.ts
import { createReadStream } from 'node:fs'

export default defineEventHandler((event) => {
  setHeader(event, 'Content-Type', 'application/pdf')
  setHeader(event, 'Content-Disposition', 'attachment; filename="report.pdf"')
  return sendStream(event, createReadStream('/tmp/report.pdf'))
})

Note: node:fs only works on Node-based presets — skip this pattern on edge runtimes.

Raw Response

When you need full control over status, headers, and body shape, return a Web Response:

// server/api/csv.ts
export default defineEventHandler(() => {
  const csv = 'id,name\n1,Ada\n2,Grace\n'
  return new Response(csv, {
    status: 200,
    headers: {
      'Content-Type': 'text/csv; charset=utf-8',
      'Content-Disposition': 'attachment; filename="users.csv"',
    },
  })
})

Web Streams Everywhere

ReadableStream and Response are Web standards — they work on Node, Bun, Deno, and Workers without changes. Prefer them over Node-specific streams when targeting multiple runtimes.

Typed API Routes →