1

Below is the error I console from my client side.

{
  "data": null,
  "error": {
    "message": "ReadableStream has already been used"
  },
  "headers": {
    "access-control-allow-credentials": "true",
    "access-control-allow-headers": "host, content-type, user-agent, connection, accept, accept-language, content-length, accept-encoding, authorization",
    "access-control-allow-methods": "POST",
    "access-control-expose-headers": "host, content-type, user-agent, connection, accept, accept-language, content-length, accept-encoding, authorization",
    "content-length": "36",
    "content-type": "text/plain;charset=utf-8",
    "date": "Tue, 07 Oct 2025 08:28:18 GMT",
    "vary": "Origin"
  },
  "response": {
    "bodyUsed": true,
    "ok": false,
    "status": 500,
    "statusText": "",
    "type": "default",
    "url": "http://000.000.00.110:4000/category"
  },
  "status": 500
}

The issue happened after I upgrade my dependency:

  "name": "api",
  "version": "1.0.50",
  "main": "src/index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "bun run --watch src/index.ts",
    "check-types": "tsc --noEmit",
    "upgrade": "bunx npm-check-updates -u",
    "lint": "eslint ."
  },
  "dependencies": {
    "@elysiajs/cors": "^1.4.0",
    "@elysiajs/openapi": "^1.4.11",
    "@elysiajs/swagger": "^1.3.1",
    "@sinclair/typebox": "^0.34.41",
    "@workspace/database": "workspace:*",
    "elysia": "1.4.9",
    "elysia-clerk": "^0.12.2",
    "eslint-config-prettier": "^10.1.8"
  },
  "devDependencies": {
    "@eslint/js": "^9.37.0",
    "@eslint/json": "^0.13.2",
    "bun-types": "latest",
    "eslint": "^9.37.0",
    "globals": "^16.4.0",
    "prisma": "^6.16.3",
    "typescript": "^5.9.3",
    "typescript-eslint": "^8.45.0"
  },
  "module": "src/index.js"
}

Basically, I started getting the

“ReadableStream has already been used”

error after upgrading all my dependencies. I noticed that if I revert Elysia from version 1.4.9 to 1.2.5, everything works fine. However, due to some reasons, I need to keep my dependencies updated.

After some testing, I found that removing .use(clerkPlugin()) fixes the issue — the API works again. But since I need authentication, removing clerkPlugin() isn’t a viable option.

  name: "Category",
  prefix: "/category",
})
  .use(clerkPlugin())
  .post(
    "",
    async ({ body }) => {
      console.log("im second", body);
    },
    {
      body: t.Object({
        type: t.Enum({ EXPENSES: "EXPENSES", INCOME: "INCOME" }),
        title: t.String(),
        icon: t.Object({
          name: t.String(),
          set: t.String(),
        }),
        parentId: t.Optional(t.String()),
      }),
    }
  );```

1 Answer 1

0
import { Elysia } from 'elysia';
import { cors } from '@elysiajs/cors';
import { fromTypes, openapi } from '@elysiajs/openapi';
import { CategoryController } from './modules/category/controller';

const app = new Elysia()
  .use(
    cors({
      origin: ["http://localhost:8081", "http://localhost:4000"],
      credentials: true,
    })
  )
  .use(
    openapi({
      path: "/swagger",
      references: fromTypes(),
      documentation: {
        tags: [{ name: "Category", description: "Category Endpoints" }],
      },
    })
  )
  .derive((ctx: any) => {
    const req = ctx.request as Request | undefined;

    // Guard: if no request or already cloned, do nothing
    if (!req || (req as any).__elysia_cloned) return {};

    try {
      const cloned = req.clone();
      // mark to avoid cloning more than once
      (cloned as any).__elysia_cloned = true;
      return { request: cloned };
    } catch (err) {
      // clone can throw in some runtimes — fail-safe: return nothing so original request used
      console.warn(
        "[CloneRequest] failed to clone request:",
        (err as Error).message
      );
      return {};
    }
  })
  .use(CategoryController)
  .listen(4000);

export type ApiType = typeof app;

console.log(
  `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);

I have a temporary workaround which I don't really like it, but however this is the only way I can temporarily resolve and proceed with my work.

The workaround involves cloning the request object right before the clerkPlugin() reads it. Normally, clerkPlugin() needs to access the request body to validate authentication tokens. However, in Elysia 1.2.x, reading the body in a plugin consumes the stream, which means that when the route handler later tries to access the body, the stream has already been used — resulting in the ReadableStream has already been used error.

By using a .derive() function, we create a cloned copy of the request for the plugin to read. This way, the plugin reads from the clone, leaving the original request body intact for the route handlers.

HOWEVER HOWEVER HOWEVER, the cons are: it duplicates memory for the request body, can break streaming for large uploads, is runtime-dependent, and is hacky/not future-proof.

So I am still looking for a better solution!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.