Next.jsのAPIルートでCSVファイルをHTTPリクエストする方法

## はじめに

APIルートでCSVファイルを送る必要があり、実装していたところ、APIルートでCSVが文字列で受け取ったり実装に苦労したため、備忘録として残します。

## 環境

csvファイルをパースするために、`multiparty`を使用します。

```
"next": "13.2.4",
"multiparty": "^4.2.3",
```

https://github.com/pillarjs/multiparty

## コード

HTTPリクエストに`axios`を使用していますが、何でも良いです。

```typescript:filePost.ts
import axios from 'axios'

export const filePost = async (params: FormData) => {
  await axios.post('/api/post', params, {
    headers: {
      ContentType: 'multipart/form-data',
    },
  });
};
```

```typescript:src/pages/api/post.ts
import axios from 'axios';

import { createFormDataFromMultipartyParsedFiles } from '@/utils/createFormDataFromMultipartyParsedFiles';
import { parseNextApiRequest } from '@/utils/parseNextApiRequest';

import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  if (req.method === 'POST') {
    (async () => {
      try {
        // リクエストで受けとったCSVファイルをパースする
        const formParsedData = await parseNextApiRequest(req);

        if ('error' in formParsedData) {
          return;
        }

        // パースしたデータから、formDataを生成
        const formData = createFormDataFromMultipartyParsedFiles(
          formParsedData.files,
        );

        // ファイルを送信
        await axios.post(<URL>, formData);

        return res.status(200).json(response.data);
      } catch (error: any) {
        return res.status(500).json(error.response.data);
      }
    })();
  }
}

// APIルートで受け取ったリクエストをパースしないようにする
export const config = {
  api: {
    bodyParser: false,
  },
};
```

```typescript:src/utils/createFormDataFromMultipartyParsedFiles.ts
import fs from 'fs';

import type multiparty from 'multiparty';

type MultipartyParsedValues = {
  fields: Record<string, string[] | undefined>;
  files: Record<string, multiparty.File[] | undefined>;
};

export const createFormDataFromMultipartyParsedFiles = (
  files: MultipartyParsedValues['files'],
) => {
  const formData = new FormData();

  Object.values(files).forEach((files) => {
    if (!files) {
      return;
    }

    const file = files[0];

    // ファイルを読み込んで、ファイルオブジェクトを生成
    const fileContent = fs.readFileSync(file.path);
    const blob = new Blob([fileContent], {
      type: file.headers['content-type'],
    });
    const processedFile = new File([blob], file.originalFilename);

    formData.append(file.fieldName, processedFile);
  });

  return formData;
};
```

```typescript:src/utils/parseNextApiRequest.ts
import multiparty from 'multiparty';

import type { NextApiRequest } from 'next';

type MultipartyParsedError = {
  error: Error | null;
};

type MultipartyParsedValues = {
  fields: Record<string, string[] | undefined>;
  files: Record<string, multiparty.File[] | undefined>;
};

export const parseNextApiRequest = async (req: NextApiRequest) => {
  const form = new multiparty.Form();

  const formParsedData = await new Promise<
    MultipartyParsedError | MultipartyParsedValues
  >((resolve, reject) => {
    form.parse(req, (error, fields, files) => {
      if (error) {
        reject({ error });
      }

      resolve({ fields, files });
    });
  });

  return formParsedData;
};
```

## まとめ

APIルートで受け取ったリクエストを`formData`に戻すのに苦労しました。外部モジュールの必要や処理が増えるため、中間APIが必要でなければ直接リクエストするのが良いかなと思います。

## 参考記事

https://qiita.com/kunihiros/items/e11daacfd62d85d83c06



## 最後に

### GoQSystemでは一緒に働いてくれる仲間を募集中です！

ご興味がある方は以下リンクよりご確認ください。

https://goqsystem.recruit.site?qiita=2cd6643d3bd2bbf4dc9e