nitrojs / nitrojs/nitro

$fetch not working for internal urls with formData

Open
#2,883 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug v2
Dominant language
TypeScript
Stars
11.2k
Forks
899
Avg merge
2d 24m
Merged PRs (30d)
40

Description

Environment

nitropack@latest
node 16-22

Reproduction

https://stackblitz.com/edit/unjs-nitro-starter-xipb6m?file=server%2Froutes%2Findex.ts

Describe the bug

It seems that $fetch does not work with FormData on internal URLs. Here is an example on Stackblitz.

If you uncomment lines 12 to 15 and refresh the browser, you can clearly see the error. It seems that ofetch does not correctly serialize the data, and the application/x-www-form-urlencoded header is also not set properly. A workaround or potential fix is already included in the Stackblitz example.

Since the behavior of internal URLs is related to a Nitro feature and, as far as I know, is not yet part of ofetch, I am creating this bug report here because it likely needs to be fixed directly in Nitro.

Additional context

Potential fix:

// Works for both internal and external urls
const resFix = await $fetch('/test', {
  method: 'POST',
  body: formData,
  onRequest({ options, request }) {
    if (options.body instanceof FormData && request.startsWith('/')) {
      const params = new URLSearchParams();
      for (const [key, value] of options.body.entries()) {
        params.append(key, value);
      }

      options.headers = options.headers || new Headers();
      // Header is also missing when calling internal url!!!
      options.headers.set('Content-Type', 'application/x-www-form-urlencoded');
      options.body = params.toString();
    }
  },
});
Logs
[nitro] [request error] [unhandled] Request.formData: Could not parse content as FormData.
  at o.errors.exception (https://unjsnitrostarterxipb6m-dyvd.w-credentialless-staticblitz.com/builtins.ddb8d84d.js:93:35885)  
  at _Request.formData (https://unjsnitrostarterxipb6m-dyvd.w-credentialless-staticblitz.com/builtins.ddb8d84d.js:93:90813)  
  at async Module.readFormData (./node_modules/h3/dist/index.mjs:600:10)  
  at async Object.eval [as handler] (./.nitro/dev/index.mjs:970:20)  
  at async Object.eval [as handler] (./node_modules/h3/dist/index.mjs:2103:19)  
  at async toNodeHandle (./node_modules/h3/dist/index.mjs:2395:7)  
  at async ufetch (./node_modules/unenv/runtime/fetch/index.mjs:28:17)  
  at async $fetchRaw2 (./node_modules/ofetch/dist/shared/ofetch.03887fc3.mjs:275:26)  
  at async $fetch2 (./node_modules/ofetch/dist/shared/ofetch.03887fc3.mjs:333:15)  
  at async Object.eval [as handler] (./.nitro/dev/index.mjs:928:3)
[nitro] [request error] [unhandled] [POST] "/test": 500 
  at async $fetch2 (./node_modules/ofetch/dist/shared/ofetch.03887fc3.mjs:333:15)  
  at async Object.eval (./.nitro/dev/index.mjs:928:3)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure by uncommenting lines 12–15 in server/routes/index.ts from the linked StackBlitz example. Trace the internal URL path through h3 readFormData, unenv fetch, and ofetch, then verify that a FormData POST is serialized with the expected content type and can be parsed successfully instead of returning 500.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.