skip to content

Search

Fastify gotcha: reply.raw bypasses middleware

2 min read

A gotcha with Fastify's reply.raw and missing CORS headers when implementing SSE.

While implementing Server-Sent Events (SSE) using Fastify, I ran into a subtle but important gotcha: using reply.raw, bypasses Fastify’s built-in middleware and response handling. This happens because reply.raw gives you direct access to Node.js’s native http.ServerResponse object.

The Scenario

I was setting up an SSE endpoint, and naturally reached for the low-level reply.raw to manually manage the connection and keep it open:

fastify.get("/events", (req, reply) => {
  reply.raw.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });
 
  res.write(`data: hello\n\n`);
});

This seemed fine at first, but when I tested it in the browser, the frontend was throwing CORS errors, and the SSE connection wasn’t working properly.

It turned out that since I was using reply.raw, Fastify’s CORS middleware was not applying the Access-Control-Allow-Origin header. That header is required for the browser to accept the connection.

What You’re Bypassing

By using reply.raw, you’re skipping:

  • Fastify’s reply.send(), which handles serialisation
  • Any plugin-added headers or logic (e.g. compression, CORS, etc.)
  • Lifecycle hooks like onSend or onResponse

In this case, the missing CORS header was the issue. Without it, the browser rejected the connection.

The Safer Alternative

If you don’t need low-level control, stick with the built-in reply API:

fastify.get("/standard-endpoint", (req, reply) => {
  reply.code(200).send({
    hello: "world",
  });
});

This ensures:

  • Proper Content-Type headers
  • JSON serialisation
  • Compatibility with plugins like CORS

If you do use reply.raw, be aware that you’re responsible for setting everything yourself, including security-related headers like CORS.

Takeaway

Using reply.raw gives you full control over the response, but it also means you are opting out of Fastify’s default behaviors. That includes things like automatic headers, serialisation, and plugin support. Make sure to account for that if you run into unexpected behavior, especially with browser-facing features like SSE or CORS.