Next.jsのBFFから画像を送信したら届きそうで届かなかった話

![Screenshot 2024-02-13 at 17.59.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2373655/a15bd053-4fb8-1914-8c5d-bd3b5fd9fdeb.png)

# この記事を読んだ方がいい人

Javascriptに不慣れな人。

HTMLで当たり前のように遅れていた画像がBFFを通したら送れなくて困っている人

# この記事を読まない方がいい人

コーディングとネットワーク領域の両方に自信があるひと

# やりたかったこと

Next.jsで作ったフロントアプリケーションにて画像登録のフォームを作成した。

バックエンドAPIはGoで作ってある

# はまったポイント

BFFで画像をハンドリングする処理がうまくいかない。

## そもそもフォームデータって

フォームって一言で言っても画面UIとしての入力フォーム的なこととも言えるし、もう少し物理層チックなことを言ってそうな気もしますが、今回はHTTP通信においてのテキストとしてのフォームのことです。

フォームをブラウザから送信すると中身はこんな感じの区切り文字と値で表されるテキストになっています。

```

------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="profileImage"; filename="Screenshot 2024-00-00 at 00.55.43.png"
Content-Type: image/png


------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="email"

mail.address@example.com
------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="password"

12345abcde
------WebKitFormBoundaryH1hEBwyt0daM3FqD
Content-Disposition: form-data; name="birthOfDate"
・
・
```

## busboyが使えない

busboyというのはどんな少年なのかと思った方もいるかもですが、Javascriptでmultipartのリクエストを解釈するためのライブラリです。

本気の情報をお求めの方は[公式](https://github.com/mscdex/busboy#examples)へどうぞ。

最近awaitとかasyncとかPromiseし始めたJS素人童貞の自分からすると、このコールバック祭りなコードはハードルが高かったです。

受け取ったフォームデータの処理を定義しておいて、req.pipeに渡す処理。

```busboy.ts
function processFiles(req: NextApiRequest): Promise<any> {
  return new Promise((resolve, reject) => {
    const bb = new Busboy({ headers: req.headers });
    const result = { files: [], fields: {} };

    bb.on('file', (fieldname, file, filename, encoding, mimetype) => {
      console.log(`File [${fieldname}]: filename: ${filename}, encoding: ${encoding}, mimetype: ${mimetype}`);
      const buffers = [];

      file.on('data', (data) => {
        buffers.push(data);
      });

      file.on('end', () => {
        result.files.push({
          fieldname,
          filename,
          encoding,
          mimetype,
          data: Buffer.concat(buffers), // ファイルデータを結合
        });
      });
    });

    bb.on('field', (fieldname, val) => {
      result.fields[fieldname] = val;
    });

    bb.on('finish', () => {
      resolve(result);
    });

    bb.on('error', (error) => {
      reject(error);
    });

    req.pipe(bb);
  });
  ```

このライブラリはUnexpected EOF的なエラーが解消できず使うのを断念しました。

フォームを受け取る前に次の処理が始まっていそうにも見えたのですが覚えたてのasync awaitをこねくり回してもダメだったので一旦捨てました。

つまりフロントエンド→BFFで止まった状態です。

## formidableでBFFを突破

ライブラリを変えてformidableで実装し直し。

```formidable.ts
  return await new Promise((resolve, reject) => {
    // formidableを使用してファイルとフィールドをパース
    const form = new IncomingForm()
    form.parse(req, async (err, fields, files) => {
      if (err) return reject(err)

      const file = files.profileImage[0]
      const fileContent = await fs.readFile(file.filepath)
      const formData = new FormData()

      // 画像の場合はファイル名とMIMEタイプを指定しないとバックエンドで正しく読み込めない
      formData.append('userProfileImage', fileContent)

      formData.append('email', fields.email[0])
      formData.append('password', fields.password[0])
```

これでGoのAPIまで到達したことは確認できました。

でもデバッグすると画像の実態は届いていそうなのですが、画像を取り扱おうとすると画像がない状態に。

```backendApi.go
file, err := c.FormFile("profileImage") // これがエラーになる

```
リクエスト情報をデバッグするとバイナリ文字がブァーっと出力されるので実態はきていそうなのだが。

ちなみにPostmanからは正常に送れ登録できるのでクライアント側に原因がありそう。

こんな感じでファイルもフォーム送信できる。優秀。
![Screenshot 2024-02-13 at 18.35.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2373655/5a0f4e3f-3a5b-7ca9-0595-5098ea85d57c.png)


# 解決


```formidableModified.ts
  return await new Promise((resolve, reject) => {
    // formidableを使用してファイルとフィールドをパース
    const form = new IncomingForm()
    form.parse(req, async (err, fields, files) => {
      if (err) return reject(err)

          const file = files.profileImage[0]
      const fileContent = await fs.readFile(file.filepath)
      const formData = new FormData()

      // 画像の場合はファイル名とMIMEタイプを指定しないとバックエンドで正しく読み込めない


      // appendの第３引数！！！！
      formData.append('userProfileImage', fileContent, {
        filename: 'userProfileImage.jpg',　 // これとか
        // contentType: type.mime, // 指定する場合は動的に取得する　// これはなくてもよかた。
        knownLength: fileContent.length　// これとか
      })

      formData.append('email', fields.email[0])
      formData.append('password', fields.password[0])
```

formにappendする際、付加情報みたいなものを第３引数に取れるようなのですが、これを追加することで正しくハンドリングできました。

contentTypeは画像データから動的に`image/jpeg`だの`image/png`だのやるのがだるかったので消してみたら動きました。

ちなみに頼りになるパイセンに相談した結果この答えまで辿り着いたのですが、ここが怪しいって微塵も想像してなかったので実力不足を突きつけられたお題でした。。