Migrating node-fetch/form-data to Node.js native APIs
Shin'ya Ueoka
Posted on July 27, 2024
The release of Node v16.15.0 and v17.5.0 brought in the Fetch API and FormData. Before that, the node-fetch and form-data packages were used to use browser-like APIs in Node.js. To migrate node-fetch/form-data packages to Node.js native APIs, some changes can be made by removing import statements of 3rd-party packages, but there are important considerations to keep in mind when using Node.js features such as file system. This article outlines about migrating from node-fetch/form-data packages to Node.js native APIs.
Overview of migration
import fs from 'node:fs';
-import FormData from 'form-data';
-import fetch from 'node-fetch';
-const file = fs.createReadStream('secret.txt');
+const file = await fs.openAsBlob('secret.txt', { type: 'text/plain' });
const form = new FormData();
-form.append('file', file);
+form.append('file', file, 'secret.txt');
const response = await fetch('https://example.com/upload', {
method: 'POST',
body: form,
});
Details
Incompatibility of types
The form-data package and Node.js FormData are not compatible and cannot be passed directly with the Fetch API. If you pass form-data to the native Fetch API, the request body will be the string [object FormData]
.
import FormData from 'form-data';
const form = new FormData();
await fetch('https://example.com/upload', {
method: 'POST',
body: form,
});
// [object FormData]
The form-data package accepts fs.ReadStream
in Node.js. The native API accepts a browser-compatible Blob. To create a Blob from a file, we can sue fs.openAsBlob()
,added in Node.js v19.8.0. If you pass fs.ReadStream
to the native API's FormData, the value will be [object Object]
.
import fs from 'node:fs';
const file = fs.createReadStream('secret.txt');
const form = new FormData();
form.append('file', file);
// Content-Disposition: form-data; name="file"
//
// [object Object]
Incompatibility of internal behavior
The form-data package obtains the file name from fs.ReadStream
and uses it as the filename
field (internal implementation). It also determines the file type from the file name and sets the Content-Type header (internal implementation).
Blob does not have a file name, it needs to be specified manually. The native API's FormData defaults to application/octet-stream
if the Content-Type is not specified. We can specify the Content-Type explicitly from the argument of fs.openAsBlob()
.
const file = await fs.openAsBlob('secret.txt');
const form = new FormData();
form.append('file', file);
// Content-Disposition: form-data; name="file"; filename="blob"
// Content-Type: application/octet-stream
const file = await fs.openAsBlob('secret.txt', { type: 'text/plain' });
const form = new FormData();
form.append('file', file, 'secret.txt');
// Content-Disposition: form-data; name="file"; filename="secret.txt"
// Content-Type: text/plain
Conclusion
Fetch API and FormData have become Stability: 2 (Stable) from Node.js v21.0.0. fs.openAsBlob()
is still Stability: 1 (Experimental) in the latest version of Node.js (v22.5.1 as of at July 27, 2024). We need to take care when running in old runtimes or using experimental APIs which has potential feature changes.
Posted on July 27, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.