Can axios serialize FormData consistently across browser and Node.js without manual header management?
0 reputation · 04 Oct 2021, 08:09 UTC
0 reputation · 04 Oct 2021, 08:09 UTC
Achieve identical FormData request serialization in both browser and Node.js runtimes so that a single codebase can run in a repeatable development environment without environment-specific branching.
Axios automatically detects FormData in browsers and emits a correct multipart/form-data Content-Type header with boundary. In Node.js, axios does not set this header; the form-data package must be installed separately and its getHeaders() output attached manually. No axios configuration flag bridges this gap, and the behavior may shift between major versions. Mock adapters often fail to reproduce the exact header differences, making local verification unreliable.
No. Axios does not serialize FormData consistently across browser and Node.js without manual header management. In a browser, the underlying XHR or fetch transport detects native FormData and emits multipart/form-data with a generated boundary. In Node.js, the default axios HTTP adapter does not inspect FormData and does not add that header. You must supply it yourself, typically with the form-data package and its getHeaders() method. Axios 1.x does not expose a documented config flag that closes this gap.
A request interceptor in a single shared module is more maintainable than a custom adapter. It keeps axios transforms and response handling intact while normalizing headers before dispatch. Do not set content-type manually in the browser; let the transport generate the boundary.
import axios from 'axios';
const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
const instance = axios.create();
instance.interceptors.request.use((config) => {
const data = config.data;
const headers = config.headers || {};
if (data && typeof data.getHeaders === 'function' && isNode && !headers['content-type']) {
Object.assign(headers, data.getHeaders());
config.headers = headers;
}
return config;
});
export default instance;
Pass a form-data instance as data in Node. In the browser, pass native FormData and omit content-type so the transport sets the boundary.
Does your Node target use the form-data package or Node 18+ global FormData? This determines whether getHeaders() is available and how boundaries are generated. Confirm the runtime before shipping the interceptor, and re-check after any axios major-version upgrade.
Review needed: verify this against your installed axios and Node versions before publication.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 04 Oct 2021, 12:57 UTC
In axios 1.x, browser FormData relies on the underlying XHR or fetch implementation to append multipart/form-data with a generated boundary, while Node does not derive that header from FormData automatically. A request interceptor is usually the least invasive place to attach form-data headers only on Node, leaving browser payloads untouched. Because multipart boundaries are nondeterministic, do not assert byte-identical bodies across runtimes; instead, capture the request through a custom adapter and compare parsed fields, field names, and file names. Pin axios and the Node form-data package, and verify under Node 18+ where global FormData behavior may still differ from the browser.