I have a Fastify project where I use better-auth to expose the authentication endpoints.
Currently, the standard approach is using toNodeHandler along with Fastify's reply.hijack(), in order to pass the raw request and reply, releasing Fastify from the duty of managing the request internal state.
The problem with this approach is that it completely bypasses Fastify's outbound lifecycle. Because of this, standard plugins like @fastify/cors or @fastify/compress silently break on these routes, and we are forced to manually extract and pipe headers to the raw Node res object to make things work:
fastify.all(
'/api/auth/*',
{ config: { skipSessionPopulation: true } },
async (request: FastifyRequest, reply: FastifyReply) => {
// Since we hijack the reply, we must manually flush headers set by Fastify (e.g. CORS)
for (const [key, value] of Object.entries(reply.getHeaders())) {
if (value !== undefined) {
reply.raw.setHeader(key, value as string | number | readonly string[]);
}
}
await nodeHandler(request.raw, reply.raw);
// We use reply.hijack() so Fastify does not attempt to serialise the
// response after better-auth has already written to "reply".
reply.hijack();
},
);
I think that a nativetoFastifyHandler adapter is needed in order to avoid the explicit copy of headers towards the raw object, and to prevent lifecycle-related plugins to break.
Instead of taking over the raw Node socket, it could map the incoming request to a standard Web Request, get the Response from better-call, and simply pass it back to Fastify using reply.send().
This would keep Fastify in charge and plays nicely with the rest of its ecosystem.
I have a Fastify project where I use
better-authto expose the authentication endpoints.Currently, the standard approach is using
toNodeHandleralong with Fastify'sreply.hijack(), in order to pass the raw request and reply, releasing Fastify from the duty of managing the request internal state.The problem with this approach is that it completely bypasses Fastify's outbound lifecycle. Because of this, standard plugins like @fastify/cors or @fastify/compress silently break on these routes, and we are forced to manually extract and pipe headers to the raw Node
resobject to make things work:I think that a native
toFastifyHandleradapter is needed in order to avoid the explicit copy of headers towards the raw object, and to prevent lifecycle-related plugins to break.Instead of taking over the raw Node socket, it could map the incoming request to a standard Web Request, get the Response from better-call, and simply pass it back to Fastify using
reply.send().This would keep Fastify in charge and plays nicely with the rest of its ecosystem.