node-fetch/form-dataからNode.jsのネイティブAPIへの移行

node-fetch/form-dataからNode.jsのネイティブAPIへの移行

Node v16.15.0 および v17.5.0 から、Fetch APIFormData が利用できるようになりました。 それ以前はブラウザに近いAPIを利用するために node-fetchform-data パッケージを利用している人もいるかと思います。 3rd-partyパッケージのimport文を消すだけで移行できる場合もありますが、ファイルアクセスなどNode.jsの機能を利用している場合は、気をつけるポイントがあります。 この記事ではnode-fetch/form-dataパッケージからNode.jsネイティブAPIへの移行方法を紹介します。

忙しい人向けdiff

 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,
 });

解説

型の非互換性

form-dataパッケージとNode.jsのFormDataは互換性がなく、Fetch APIにそのまま渡せません。 もしネイティブのFetch APIにform-dataを渡すと、[object FormData] という文字列がリクエストボディとなってしまいます。

import FormData from 'form-data';

const form = new FormData();
await fetch('https://example.com/upload', {
  method: 'POST',
  body: form,
});

// [object FormData]

またform-dataパッケージは、Node.jsの fs.ReadStream を受け取りました。 ネイティブAPIはブラウザ互換のBlobを受け取ります。 Node.js v19.8.0から追加された fs.openAsBlob() を使うと、ファイルからBlobを作れます。 もしネイティブAPIのFormDataに fs.ReadStream を渡すと、[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]

内部挙動の非互換性

form-dataパッケージは fs.ReadStream からファイル名を取得して filename フィールドに渡します(内部実装)。 またファイル名からファイルタイプを取得してContent-Typeヘッダーを指定します(内部実装)。

Blobはファイル名を持たないため、FormData で指定する必要があります。 ネイティブ APIの FormData は、Content-Typeを指定しない場合 application/octet-stream がデフォルトとなります。 Content-Typeを明示的に指定するには、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

まとめ

Fetch API/FormDataはNode.js v21.0.0からStability: 2 (Stable)になりました。 fs.openAsBlob() は最新版のNode.js v22でもStability: 1(Experimental)です(2024年7月27日時点の最新版はv22.5.1)。 古い環境での実行や、将来のAPI変更には注意しましょう。


Profile picture

Shin'ya Ueoka

B2B向けSaaSを提供する会社の、元Webエンジニア。今はエンジニアリング組織のマネジメントをしている。