denoland/deno · error · TypeError

Body already consumed

Error message

Body already consumed

What it means

Thrown by InnerBody.consume() in ext/fetch/22_body.js when the body is unusable — the stream is locked or disturbed, or the static body was already consumed. This backs every one-shot body reader: request.json(), request.text(), request.arrayBuffer(), response.formData(), etc. A body can only be consumed once.

Solutions

  1. Read the body once, then reuse the parsed value: `const data = await req.json().catch(() => null)` and pass `data` downstream.
  2. When multiple consumers are needed, clone first: `const [a, b] = [req.clone(), req]` (clone before any read).
  3. In middleware, buffer the body (await once) and reconstruct a new Request with the buffered bytes for downstream handlers.

Example fix

// before
console.log(await req.text());
const json = await req.json(); // Body already consumed

// after
const raw = await req.text();
const json = JSON.parse(raw);
Defensive patterns

Strategy: validation

Validate before calling

function bodyReadable(b) {
  const s = b.body;
  return s !== null && !(s.locked);
}
if (!bodyReadable(req)) throw new Error("body already used; buffer and pass the parsed value instead");

Type guard

function hasUnreadBody(r: Request | Response): boolean {
  return r.body === null || !(r.body.locked); // disturbed streams are not directly detectable; track reads yourself
}

Try / catch

let data; try { data = await req.json(); } catch (e) { if (e instanceof TypeError && e.message === "Body already consumed") { /* re-fetch or use cached value */ } else throw e; }

Prevention

When it happens

Trigger: Calling two one-shot readers on the same body: `(await req.text())` followed by `req.json()`; reading `res.body` with a reader and then calling `res.text()`; cloning after consumption; middleware that logs the body and then the handler reads it again.

Common situations: Logging middleware that calls request.json() before the route handler; error handlers that try to re-read the body after a failed parse; double-calling formData() in upload handlers; using the same Request object for fetch() after reading its body.

Related errors


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

Appendix: source

Thrown at ext/fetch/22_body.js:189

   */
  consumed() {
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      return isReadableStreamDisturbed(this.streamOrStatic);
    }
    return this.streamOrStatic.consumed;
  }

  /**
   * https://fetch.spec.whatwg.org/#concept-body-consume-body
   * @returns {Promise<Uint8Array>}
   */
  consume() {
    if (this.unusable()) throw new TypeError("Body already consumed");
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      readableStreamThrowIfErrored(this.stream);
      return PromisePrototypeCatch(
        readableStreamCollectIntoUint8Array(this.stream),
        (e) => {
          if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, e)) {
            // TODO(kt3k): We probably like to pass e as `cause` if BadResource supports it.
            throw new e.constructor(
              "Cannot read body as underlying resource unavailable",
            );
          }
          throw e;
        },

View on GitHub (pinned to 9ad36f7a2c)