fastify/fastify · error · Error

FST_ERR_REP_RESPONSE_BODY_CONSUMED

FST_ERR_REP_RESPONSE_BODY_CONSUMED

Error message

Response.body is already consumed.

What it means

Thrown in onSendEnd when you reply.send() a WHATWG Response object whose body has already been consumed (response.bodyUsed === true). Fastify uses response.body as the payload stream, so a locked/consumed body cannot be re-read. This protects against double-read of a fetch Response that was already .json()/.text()/.arrayBuffer()'d.

Source

Thrown at lib/reply.js:626

  // we need to update the status, add the headers and use it's body as payload
  // before continuing
  if (payload != null && typeof payload === 'object' && toString.call(payload) === '[object Response]') {
    // https://developer.mozilla.org/en-US/docs/Web/API/Response/status
    if (typeof payload.status === 'number') {
      reply.code(payload.status)
    }

    // https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
    if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') {
      for (const [headerName, headerValue] of payload.headers) {
        reply.header(headerName, headerValue)
      }
    }

    // https://developer.mozilla.org/en-US/docs/Web/API/Response/body
    if (payload.body !== null) {
      if (payload.bodyUsed) {
        throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED()
      }
    }
    // Keep going, body is either null or ReadableStream
    payload = payload.body
  }
  const statusCode = res.statusCode

  if (payload === undefined || payload === null) {
    // according to https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
    // we cannot send a content-length for 304 and 204, and all status code
    // < 200
    // A sender MUST NOT send a Content-Length header field in any message
    // that contains a Transfer-Encoding header field.
    // For HEAD we don't overwrite the `content-length`
    if (statusCode >= 200 && statusCode !== 204 && statusCode !== 304 && req.method !== 'HEAD' && reply[kReplyTrailers] === null) {
      reply[kReplyHeaders]['content-length'] = '0'
    }

View on GitHub (pinned to 7299a57d3f)

Solutions

  1. Do not consume response.body before reply.send() — pass the Response untouched if you only need to forward it.
  2. If you must read it, reconstruct a new Response: reply.send(new Response(payload, { status, headers })).
  3. Clone the Response with response.clone() before consuming one copy, and send the other.

Example fix

// before
const upstream = await fetch(url)
const body = await upstream.json() // body now consumed
reply.send(upstream)

// after
const upstream = await fetch(url)
const clone = upstream.clone()
const body = await clone.json()
reply.send(upstream)
Defensive patterns

Strategy: validation

Validate before calling

function sendableResponse(response) {
  return response && !response.bodyUsed
}
if (sendableResponse(upstream)) reply.send(upstream)
else reply.send(new Response(reconstructedBody, { status: upstream.status, headers: upstream.headers }))

Type guard

function isResponseBodyAvailable(response) {
  return response != null && response.bodyUsed === false
}

Try / catch

try {
  reply.send(response)
} catch (err) {
  if (err.code === 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') {
    reply.code(500).send({ error: 'upstream body already consumed' })
  } else throw err
}

Prevention

When it happens

Trigger: Calling const data = await res.json() on a fetch Response and then passing the same Response to reply.send(response); or reading response.body with a reader elsewhere before sending it.

Common situations: Logging middleware that inspects a fetched response body for logging and then forwards the Response to the client; proxy handlers that consume the body for transformation but forget to reconstruct a fresh Response.

Related errors


AI-assisted analysis of fastify/fastify@7299a57d3f (2026-08-03). Data as JSON: /data/errors/84b195c528f062b0.json. Report an issue: GitHub.