From 77434518522912c45ef72971c64c5c3d189508cd Mon Sep 17 00:00:00 2001 From: Alex Kahn Date: Thu, 30 Apr 2026 21:09:50 -0400 Subject: [PATCH] docs(elysia): caution callout for parse: 'none' refactor footgun MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current example uses `(ev) => handlers(ev.request)`, which works because Elysia's static analyzer (sucrose) doesn't recognize the property-access shape as body access. If a user refactors to the more idiomatic destructured form `({ request }) => handlers(request)`, the analyzer flips `inference.body = true`, Elysia compiles in a `request.json()` call, and UT's handler receives a consumed stream — surfacing as a silent empty-body 400. This change keeps the original example untouched and adds: - A `` callout in the docs page explaining the gotcha and showing the `{ parse: "none" }` workaround for users who do refactor. - An inline comment in the runnable example noting why the handler shape is intentional. `parse: 'none'` is the upstream-recommended fix per elysiajs/elysia#1252 and the same option Elysia uses internally for `.mount()`. --- .../(docs)/backend-adapters/fetch/page.mdx | 29 +++++++++++++++++++ .../backend-adapters/server/src/elysia.ts | 8 +++++ 2 files changed, 37 insertions(+) diff --git a/docs/src/app/(docs)/backend-adapters/fetch/page.mdx b/docs/src/app/(docs)/backend-adapters/fetch/page.mdx index 8e5d8625ac..050dd0d7e9 100644 --- a/docs/src/app/(docs)/backend-adapters/fetch/page.mdx +++ b/docs/src/app/(docs)/backend-adapters/fetch/page.mdx @@ -120,6 +120,35 @@ new Elysia() }); ``` + + **Heads up if you refactor the handler shape.** The example above works + because Elysia's static analyzer leaves `ev.request` alone. If you + rewrite either route to the more idiomatic destructured form — + `({ request }) => handlers(request)` — the analyzer infers the route + reads `body` and Elysia pre-parses the request via `request.json()`, + consuming the stream before UploadThing's handler can read it. The + client sees a silent empty-body 400. + + The fix is to opt out of body parsing for the route by passing + `{ parse: "none" }` as the third argument: + + ```ts + .get("/api/uploadthing", ({ request }) => handlers(request), { + parse: "none", + }) + .post("/api/uploadthing", ({ request }) => handlers(request), { + parse: "none", + }) + ``` + + This is the same option Elysia uses internally for its own + [`.mount()` method](https://elysiajs.com/patterns/mount.html) and is + documented under + [explicit parsers](https://elysiajs.com/essential/life-cycle.html#explicit-parser). + See [elysiajs/elysia#1252](https://github.com/elysiajs/elysia/issues/1252) + for background. + + ### Hono ```ts diff --git a/examples/backend-adapters/server/src/elysia.ts b/examples/backend-adapters/server/src/elysia.ts index 9df54bb1df..f87161e01f 100644 --- a/examples/backend-adapters/server/src/elysia.ts +++ b/examples/backend-adapters/server/src/elysia.ts @@ -9,6 +9,14 @@ const handler = createRouteHandler({ router: uploadRouter, }); +// The `(ev) => handler(ev.request)` shape is intentional — it keeps +// Elysia's static analyzer from inferring the route reads `body` and +// pre-parsing the request, which would consume the stream before +// UploadThing's handler reads it. If you rewrite to the idiomatic +// `({ request }) => handler(request)`, add `{ parse: "none" }` as the +// route's third arg to opt out of body parsing. See the Elysia callout +// in docs/src/app/(docs)/backend-adapters/fetch/page.mdx and +// elysiajs/elysia#1252. new Elysia() .get("/api", () => "Hello from Elysia!") .get("/api/uploadthing", (ev) => handler(ev.request))