denoland/deno · error · TypeError

The body of the Response returned from the serve handler has

Error message

The body of the Response returned from the serve handler has already been consumed

What it means

A Response body can be read exactly once. serve checks responseBodyUsed before sending, and throws this TypeError when the handler returns a Response whose body was already consumed by earlier code, because there is nothing left to put on the wire. The error is routed to onError (default: 500).

Source

Thrown at ext/http/00_serve.ts:747

      // The Response prototype check above passes for Response-like objects
      // (e.g. a subclass that skipped super(), or a Response from a different
      // realm/polyfill). Those don't carry the internal slot we read from
      // below, so reject them with a clear error instead of crashing later.
      inner = getInnerResponse(response);
      if (inner === undefined) {
        throw new TypeError(
          "Return value from serve handler must be a Response constructed via the Response constructor in this realm",
        );
      }

      if (inner.type === "error") {
        throw new TypeError(
          "Return value from serve handler must not be an error response (like Response.error())",
        );
      }

      if (responseBodyUsed(response)) {
        throw new TypeError(
          "The body of the Response returned from the serve handler has already been consumed",
        );
      }
    } catch (error) {
      try {
        response = await onError(error);
        if (!ObjectPrototypeIsPrototypeOf(ResponsePrototype, response)) {
          throw new TypeError(
            "Return value from onError handler must be a response or a promise resolving to a response",
          );
        }
        inner = toInnerResponse(response);
        if (inner === undefined) {
          throw new TypeError(
            "Return value from onError handler must be a Response constructed via the Response constructor in this realm",
          );
        }
      } catch (error) {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Read from a clone when you must inspect: await res.clone().text(), then return the original
  2. Cache the source data and construct a fresh Response per request
  3. In middleware, pass res.clone() downstream when you consume one copy
  4. Check res.bodyUsed before returning and rebuild the Response if it is true

Example fix

// before
Deno.serve(async () => {
  const res = Response.json({ ok: true });
  console.log(await res.text()); // consumes the body
  return res;
});

// after
Deno.serve(async () => {
  const res = Response.json({ ok: true });
  console.log(await res.clone().text()); // read the clone
  return res;
});
Defensive patterns

Strategy: validation

Validate before calling

// Tripwire before returning from the handler
if (response instanceof Response && response.bodyUsed) {
  throw new Error("Response body was consumed before returning");
}
return response;

Type guard

function isUnconsumedResponse(v) {
  return v instanceof Response && !v.bodyUsed;
}

Try / catch

try {
  return await handler(req);
} catch (err) {
  if (err instanceof TypeError && err.message.includes("already been consumed")) {
    return new Response("Response body already consumed", { status: 500 });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling res.text()/res.json()/res.arrayBuffer() on the response before returning it; caching a single Response object and returning it for multiple requests; middleware that inspects a response body without cloning it.

Common situations: Logging response payloads (await res.text() then return res); singleton pre-built responses reused across requests; test code that verifies the body of the very object it returns.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/9eb19c85196fef12. Report an issue: GitHub.