denoland/deno · error · TypeError

The body of the Response returned from the serve handler…

Error message

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

What it means

Before serving, Deno.serve checks responseBodyUsed() and refuses a Response whose body stream has already been read or cancelled. Once a body is consumed it cannot be transmitted again, so the handler is returning an empty shell. The throw is routed to onError (500 by default).

Solutions

  1. Replace cached Response constants with factories: `const notFound = () => new Response('Not Found', { status: 404 })`
  2. Log via a clone: `await response.clone().text()`, return the original
  3. If you must read the body, rebuild the Response: `const t = await r.text(); return new Response(t, r)`
  4. Never hand the same Response object to two requests

Example fix

// before
const NOT_FOUND = new Response('Not Found', { status: 404 });
Deno.serve(() => NOT_FOUND); // second request throws: body already consumed

// after
const notFound = () => new Response('Not Found', { status: 404 });
Deno.serve(() => notFound()); // fresh Response every request
Defensive patterns

Strategy: validation

Validate before calling

// Validate before returning, and never reuse consumed Responses:
if (response.bodyUsed) {
  throw new Error('handler consumed the response body before returning it');
}
return response;

Try / catch

// Logging without consuming: read the clone, return the original
const copy = response.clone();
await logBody(await copy.text());
return response;

Prevention

When it happens

Trigger: Reading the body for logging/inspection (`await response.text()`) and then returning the same object, returning a module-level cached Response instance that was already served once, or middleware that inspects the body without cloning.

Common situations: Response-logging middleware, a shared `const NOT_FOUND = new Response('Not Found', { status: 404 })` constant returned from many requests (the first serve consumes it), teeing bodies for audit trails without using clone().

Related errors


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

Appendix: source

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

      // 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 abd22074e4)