Keywords: Fetch API | multipart/form-data | FormData
Abstract: This article explores common errors when sending multipart/form-data requests with the Fetch API, focusing on the handling of Content-Type and Content-Length headers. By analyzing a typical CURL-to-Fetch conversion case, it explains why manually setting these headers leads to 401 unauthorized errors and provides best-practice solutions. The core insight is that when using FormData objects as the request body, browsers or Node.js environments automatically manage multipart/form-data boundaries and content length, and developers should avoid manual intervention. The article also discusses how to properly use the form-data module in Node.js to retrieve header information and methods to verify request formats through network inspection tools.
Problem Background and Common Mistakes
In modern web development, sending multipart/form-data requests using the Fetch API is a common requirement, especially for file uploads. However, many developers encounter authorization failures or format errors when migrating from traditional tools like CURL to Fetch. A typical scenario is: a developer successfully sends a request using a CURL command but receives a 401 unauthorized status code with Fetch, with an error message indicating a mismatch between the userId in the token and the userId in the FormData.
Analysis of Erroneous Code
Here is a common incorrect implementation that attempts to mimic CURL command behavior:
const formData = new FormData();
formData.append('file', file);
formData.append('userId', userId);
return fetch(`<url>`, {
method: 'POST',
headers: {
'Content-Length': file.length
'Authorization: Bearer <authorization token>',
'Content-Type': 'multipart/form-data'
},
body: formData
})
This code has several key issues:
- Manual Setting of Content-Type Header: When using a FormData object as the request body, browsers automatically set Content-Type to multipart/form-data with the correct boundary string. Manual setting overrides this, leading to lost or malformed boundary information.
- Incorrect Calculation of Content-Length: file.length may be inaccurate because the FormData object contains multiple fields and files, and its total length needs dynamic calculation. Manually setting an incorrect Content-Length causes server parsing errors.
- Header Format Errors: The Authorization header uses a colon instead of an equal sign in the code, which is a syntax error, but the core issue lies in multipart format handling.
Correct Solution
Based on best practices, the correct approach is to let the browser or environment automatically handle multipart/form-data headers. Here is the corrected code:
const formData = new FormData();
formData.append('file', file);
formData.append('userId', userId);
return fetch(`<url>`, {
method: 'POST',
headers: {
'Authorization': 'Bearer <authorization token>'
},
body: formData
});
Key improvements:
- Remove Content-Type and Content-Length headers, allowing the Fetch API to set them automatically.
- Keep only the necessary Authorization header, ensuring correct format.
In browser environments, this usually works because browsers automatically generate the correct multipart boundary and content length for FormData requests. This can be verified using network inspection tools, such as viewing request headers in Chrome DevTools, which might show auto-generated content like:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 12345
Special Handling in Node.js Environments
When using isomorphic-fetch in Node.js, the situation differs slightly. Browser environments automatically inject headers, but Node.js may require manual handling. In this case, the getHeaders() method from the form-data module can be used to retrieve correct header information. Referring to supplementary answers, here is a Node.js example:
const FormData = require('form-data');
const formData = new FormData();
formData.append('image', pngBuffer, {
filename: 'image.png',
contentType: 'application/octet-stream'
});
const headers = Object.assign({
'Accept': 'application/json',
'Authorization': authToken
}, formData.getHeaders());
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: formData
});
formData.getHeaders() returns a header object containing Content-Type and Content-Length, ensuring correct request format in Node.js environments.
Debugging and Verification Methods
If issues arise, the following steps can be taken for debugging:
- Use network inspection tools (e.g., browser developer tools or Wireshark) to capture requests and compare raw header and body formats between CURL and Fetch.
- Check received request data in server-side logs to confirm if FormData fields are parsed correctly.
- Avoid manual calculation of Content-Length, relying on automatic handling or methods from the form-data module.
Conclusion
When sending multipart/form-data requests, the core principle is to trust the environment to handle format details automatically. In browsers, remove Content-Type and Content-Length headers; in Node.js, use the form-data module for assistance. This prevents common 401 errors and ensures request formats match server expectations. With the correct approach, developers can efficiently implement file uploads and form submissions.