How to fix Error: FST_ERR_REP_ALREADY_SENT: Reply was already sent (reply.send called twice) in Node.js

Node.jsINTERMEDIATEMEDIUMUpdated Dec 11, 2025· 155 views

This Fastify error occurs when your route handler or hook attempts to send a response multiple times for the same HTTP request. Fastify prevents duplicate responses, which would cause protocol violations and unpredictable client behavior.

Quick fix

Fixes FST_ERR_REP_ALREADY_SENT

// ❌ WRONG - execution continues after send
fastify.get('/users/:id', async (request, reply) => {
  const user = await findUser(request.params.id);

  if (!user) {
    reply.code(404).send({ error: 'User not found' });
    // Code continues executing!
  }

  // This will execute even if user is null, causing double send
  reply.send({ user });
});

// ✅ CORRECT - return prevents further execution
fastify.get('/users/:id', async (request, reply) => {
  const user = await findUser(request.params.id);

  if (!user) {
    return reply.code(404).send({ error: 'User not found' });
  }

  return reply.send({ user });
});
  1. Add return statement before reply.send() calls
  2. Remove done() callback when using async/await
  3. Return reply when sending outside promise chain
  4. Fix error handlers to check if reply was sent
node.js — error
// ❌ WRONG - execution continues after send
fastify.get('/users/:id', async (request, reply) => {
const user = await findUser(request.params.id);
if (!user) {
reply.code(404).send({ error: 'User not found' });
// Code continues executing!
}
// This will execute even if user is null, causing double send
reply.send({ user });
});
// ✅ CORRECT - return prevents further execution
fastify.get('/users/:id', async (request, reply) => {
const user = await findUser(request.params.id);
if (!user) {
return reply.code(404).send({ error: 'User not found' });
}
return reply.send({ user });
});
Error: FST_ERR_REP_ALREADY_SENT: Reply was already sent (reply.send called twice)

What this error means

The FST_ERR_REP_ALREADY_SENT error is Fastify's protection mechanism against sending multiple HTTP responses to the same request. Once reply.send() is called and the response is sent to the client, the reply object is marked as "already sent" and any subsequent attempts to send data will trigger this error. This error commonly occurs in async route handlers where execution continues after reply.send() has been called, or when multiple code paths inadvertently attempt to send responses. In Fastify, once a reply is sent, the request lifecycle is complete and no further modifications to the response are allowed. Understanding Fastify's request lifecycle is crucial: hooks like preHandler, handler, and onSend execute in sequence, and each can potentially send a reply. If any hook or handler sends a response without properly stopping execution (using return), subsequent code may attempt to send again, triggering this error.

Typical symptoms

  • Request fails with FST_ERR_REP_ALREADY_SENT error
  • Error message indicates "Reply was already sent"
  • API endpoint returns 500 Internal Server Error to client
  • Application logs show "reply.send called twice" message
  • Route handler appears to execute normally but throws error
  • WebSocket connections fail with "Reply already sent" error
  • Error occurs intermittently under specific conditions

Common causes

  1. Calling reply.send() multiple times in the same route handler
  2. Not using return statement when calling reply.send() in conditional blocks
  3. Mixing async/await with the done() callback in hooks or handlers
  4. Error thrown after reply.send() has already been called
  5. Multiple lifecycle hooks attempting to send responses independently
  6. Using reply.send() outside promise chain without returning the reply object
  7. Plugin conflicts where multiple plugins try to send responses
  8. Error handler calling reply.send() when response was already sent in route
  9. Sending response in onRequest hook while allowing execution to continue
  10. Validation errors triggering reply.send() twice due to improper error handling

How to fix "Error: FST_ERR_REP_ALREADY_SENT: Reply was already sent (reply.send called twice)"

1Add return statement before reply.send() calls

The most common fix is to ensure you return immediately after calling reply.send() to prevent further code execution:

javascript
// ❌ WRONG - execution continues after send
fastify.get('/users/:id', async (request, reply) => {
  const user = await findUser(request.params.id);

  if (!user) {
    reply.code(404).send({ error: 'User not found' });
    // Code continues executing!
  }

  // This will execute even if user is null, causing double send
  reply.send({ user });
});

// ✅ CORRECT - return prevents further execution
fastify.get('/users/:id', async (request, reply) => {
  const user = await findUser(request.params.id);

  if (!user) {
    return reply.code(404).send({ error: 'User not found' });
  }

  return reply.send({ user });
});

Always use return reply.send() to ensure the function exits immediately after sending the response.

2Remove done() callback when using async/await

Mixing async functions with the done() callback causes Fastify to process requests twice:

javascript
// ❌ WRONG - mixing async with done()
fastify.addHook('preHandler', async (request, reply, done) => {
  try {
    await authenticateUser(request);
    done(); // This causes double execution!
  } catch (error) {
    done(error);
  }
});

// ✅ CORRECT - use async without done()
fastify.addHook('preHandler', async (request, reply) => {
  try {
    await authenticateUser(request);
    // No done() needed - just return or throw
  } catch (error) {
    throw error; // Fastify handles this automatically
  }
});

When using async/await, never use the done() callback. Fastify automatically handles promise resolution.

3Return reply when sending outside promise chain

When calling reply.send() asynchronously outside the normal promise chain, you must return the reply object:

javascript
// ❌ WRONG - async send without returning reply
fastify.addHook('preHandler', async (request, reply) => {
  setImmediate(() => {
    reply.send({ message: 'Async response' });
  });
  // Fastify thinks handler completed without sending
});

// ✅ CORRECT - return reply to signal async send
fastify.addHook('preHandler', async (request, reply) => {
  setImmediate(() => {
    reply.send({ message: 'Async response' });
  });
  return reply; // Tells Fastify to wait for the async send
});

This signals to Fastify that the reply will be sent asynchronously and prevents the framework from trying to send a default response.

4Fix error handlers to check if reply was sent

Ensure error handlers don't attempt to send responses if one was already sent:

javascript
// ❌ WRONG - error handler doesn't check reply status
fastify.setErrorHandler(async (error, request, reply) => {
  console.error(error);
  reply.code(500).send({ error: error.message });
  // Fails if route already sent a response
});

// ✅ CORRECT - check if reply was already sent
fastify.setErrorHandler(async (error, request, reply) => {
  console.error(error);

  // Check if reply was already sent
  if (reply.sent) {
    return; // Don't try to send again
  }

  reply.code(500).send({ error: error.message });
});

The reply.sent property indicates whether a response has already been sent, preventing duplicate send attempts.

5Review lifecycle hooks for multiple send calls

Check that only one hook or handler sends a response per request:

javascript
// ❌ WRONG - multiple hooks sending responses
fastify.addHook('onRequest', async (request, reply) => {
  if (!request.headers.authorization) {
    return reply.code(401).send({ error: 'Unauthorized' });
    // This sends a response
  }
});

fastify.get('/data', async (request, reply) => {
  const data = await fetchData();
  return reply.send({ data });
  // This tries to send again if auth failed
});

// ✅ CORRECT - only one sends, others throw errors
fastify.addHook('onRequest', async (request, reply) => {
  if (!request.headers.authorization) {
    throw new Error('Unauthorized'); // Let error handler send response
  }
});

fastify.get('/data', async (request, reply) => {
  const data = await fetchData();
  return reply.send({ data });
});

Use hooks to validate and throw errors, not to send responses directly (unless terminating the request).

6Enable debug logging to trace double sends

Enable Fastify's debug logging to see exactly where responses are being sent:

javascript
import fastify from 'fastify';

const app = fastify({
  logger: {
    level: 'debug', // Enable debug logging
    prettyPrint: true
  }
});

// Or use environment variable
// LOG_LEVEL=debug npm start

Debug logs will show the complete request lifecycle, making it easier to identify where multiple reply.send() calls occur. Look for log entries showing "Reply sent" appearing twice for the same request ID.

Advanced notes

Understanding Fastify's Reply Lifecycle:
Fastify tracks the reply state internally through the reply.sent flag. Once set to true, any subsequent send operation will fail. This is enforced at the framework level to comply with HTTP protocol requirements that each request receives exactly one response.

Common Plugin Conflicts:
Plugins like fastify-static, fastify-auth, and fastify-websocket can cause this error if not configured correctly. For example, fastify-static's sendFile in an onRequest hook may conflict with normal route handlers. Always check plugin documentation for proper lifecycle hook usage.

Performance Implications:
While the error prevents actual duplicate sends, the fact that your code is attempting double sends indicates a logic flaw that may impact performance. Each reply.send() call triggers serialization and validation - unnecessary duplicate calls waste CPU cycles even if they error out.

WebSocket Special Case:
With fastify-websocket, the "Reply already sent" error often occurs when mixing HTTP replies with WebSocket upgrades. Once a WebSocket connection is established, the HTTP reply is considered sent. Don't call reply.send() after accepting a WebSocket connection.

Testing Strategy:
Write unit tests that verify handlers only send one response per code path. Use Fastify's inject() method for testing and assert that reply.send() is called exactly once. Consider using test coverage tools to ensure all code paths are tested for proper reply handling.

TypeScript Type Safety:
TypeScript users can leverage Fastify's type system to catch some of these issues at compile time. The FastifyReply type includes methods that return the reply object, encouraging the return pattern that prevents double sends.

Frequently asked questions

What does "FST_ERR_REP_ALREADY_SENT" mean?
The FST_ERR_REP_ALREADY_SENT error is Fastify's protection mechanism against sending multiple HTTP responses to the same request. send() is called and the response is sent to the client, the reply object is marked as "already sent" and any subsequent attempts to send data will trigger this error. send() has been called, or when multiple code paths inadvertently attempt to send responses.
How do I fix "FST_ERR_REP_ALREADY_SENT"?
This Fastify error occurs when your route handler or hook attempts to send a response multiple times for the same HTTP request. Fastify prevents duplicate responses, which would cause protocol violations and unpredictable client behavior.
What causes "FST_ERR_REP_ALREADY_SENT"?
Calling reply.send() multiple times in the same route handler Not using return statement when calling reply.send() in conditional blocks Mixing async/await with the done() callback in hooks or handlers
Is the "FST_ERR_REP_ALREADY_SENT" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading