formData形式で画像をアップロード

その他

画像をAPIからアップロードするプログラムを実装したい、けど全然わからないや。

saku
saku

大丈夫!わかりやすく解説するね!

画像をAPI経由で送信し、サーバーやS3に保存する処理の実装は実務でもよくあると思います。

方法としては下記の2種類があります。

  • formDataで送る形式
  • base64エンコードして送る形式

今回はformData形式で、実装方法を説明します!

base64エンコードする形式はこちら👇

サンプルアプリのgithub

GitHub - shinsaku-sasaki/ImageUpload: 画像アップロード処理のサンプル
画像アップロード処理のサンプル. Contribute to shinsaku-sasaki/ImageUpload development by creating an account on GitHub.

こちらのformDataディレクトリにコードが入っています。

READMEに起動手順が書いてあるので、クローンしたら起動してみてください。

サンプルアプリの構成

下記の構成で説明します。

  • フロントエンド React
  • バックエンド Express

Expressで画像を受け取るところはmulterというライブラリを使っていますので、興味のある方は見てみてください。

GitHub - expressjs/multer: Node.js middleware for handling `multipart/form-data`.
Node.js middleware for handling `multipart/form-data`. - GitHub - expressjs/multer: Node.js middleware for handling `multipart/form-data`.

アプリの説明

画面はこんな感じです。

画像を選択してみるとプレビューが出ます↓

送信ボタンを押すと、アップロードされサーバー側のディレクトリに保存されます。

起動中のExpress側のターミナルに、アップロードしたファイルの情報が表示されます。

フロント側のアップロード処理

ImageUploadFormコンポーネントに処理が書いてあります。

import React, { useState, ChangeEvent } from "react";
import "./ImageUploadForm.css";

const ImageUploadForm: React.FC = () => {
  const [selectedImage, setSelectedImage] = useState<File | null>(null);
  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
  const [submissionMessage, setSubmissionMessage] = useState<string | null>(
    null
  );

  const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) {
      setSelectedImage(file);
      setPreviewUrl(URL.createObjectURL(file));
    }
  };

  const handleSubmit = () => {
    if (selectedImage) {
      const formData = new FormData();
      formData.append("image", selectedImage);

      // サーバーへの送信ロジック
      fetch("http://localhost:3000/upload", {
        method: "POST",
        body: formData,
      })
        .then((response) => response.json())
        .then((data) => {
          console.log(data);
          setSubmissionMessage("アップロードに成功しました!");
        })
        .catch((error) => {
          console.error(error);
          setSubmissionMessage(
            "アップロードに失敗しました。エラーが発生しました。"
          );
        })
        .finally(() => {
          // オブジェクトURLを解放
          if (previewUrl) {
            URL.revokeObjectURL(previewUrl);
          }
        });
    }
  };

  return (
    <div className="image-upload-form">
      <input
        type="file"
        id="image-input"
        accept="image/*"
        onChange={handleImageChange}
      />
      {previewUrl && (
        <div className="preview-container">
          <img src={previewUrl} alt="Preview" className="preview-image" />
        </div>
      )}
      <button onClick={handleSubmit}>送信</button>
      {submissionMessage && <p>{submissionMessage}</p>}
    </div>
  );
};

export default ImageUploadForm;

handleImageChangeで、アップロードする画像を選択した時の処理、

handleSubmith関数で画像の送信処理をしています。

画像選択時

input type=”file”のonChangeに指定している関数を見てみます。

  const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) {
      setSelectedImage(file);
      setPreviewUrl(URL.createObjectURL(file));
    }
  };

引数で渡ってきたイベントハンドラの、e.target.files

に、配列形式で選択した画像の情報が渡ってきます。

試しに、e.target.filesをコンソールで見てみます。

  const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.files);
    const file = e.target.files?.[0];
    if (file) {
      setSelectedImage(file);
      setPreviewUrl(URL.createObjectURL(file));
    }
  };

このように、ブラウザにアップロードしたファイルの情報が配列形式で取得できます。

あとは

setSelectedImage(file);

でステートに保存しています。

(保存したステートをそのままサーバーに送信する感じです、後述)

setPreviewUrl(URL.createObjectURL(file));

で、プレビュー用のURLをステートに保存しています。

console.log(previewUrl);

で、保存したURLを見ると、

ランダムな英数字のURLが取得されました。

これは一時的にブラウザ上に作成されるURLで、imgタグのsrc属性に入れると画像を表示することができます。

 <img src={previewUrl} alt="Preview" className="preview-image" />

このようにして、ステートに保持しているURLをsrc属性に入れて表示しています。

画像送信時

送信ボタンを押した時の処理はこちらです↓

  const handleSubmit = () => {
    if (selectedImage) {
      const formData = new FormData();
      formData.append("image", selectedImage);

      // サーバーへの送信ロジック
      fetch("http://localhost:3000/upload", {
        method: "POST",
        body: formData,
      })
        .then((response) => response.json())
        .then((data) => {
          console.log(data);
          setSubmissionMessage("アップロードに成功しました!");
        })
        .catch((error) => {
          console.error(error);
          setSubmissionMessage(
            "アップロードに失敗しました。エラーが発生しました。"
          );
        })
        .finally(() => {
          // オブジェクトURLを解放
          if (previewUrl) {
            URL.revokeObjectURL(previewUrl);
          }
        });
    }
  };

FormDataオブジェクトは、キーとバリューの形式で送信する値を持つことができます。

例えば、

{
 name: "saku",
 email: "saku@saku.com"
}

のようなイメージです。

appendメソッドで、FormDataオブジェクトに値を追加できます。

今回はファイルオブジェクトを追加しますが、単に名前やメールなどのテキスト情報だけでも問題ありません。

formData.append("image", selectedImage);

で、imageというキーにselectedImageという変数を追加していますね。

selectedImageはステートで保持しているファイルオブジェクトです↓

次に、fetchでサーバーに送信します。

   fetch("http://localhost:3000/upload", {
     method: "POST",
     body: formData,
   })

bodyにそのまま追加して終わりです。

あとはよくあるfetchの処理と同じですね。

finallyの箇所ですが、

finally(() => {
 // オブジェクトURLを解放
 if (previewUrl) {
 URL.revokeObjectURL(previewUrl);
 }
});

URL.revokeObjectURLで、一時的に作成したURLを削除します。

URL.createObjectURLで作成したURLはブラウザのメモリ上に保存されています。

使い終わったら解放してあげます。

フロントエンドからformData形式で画像を送信する説明は以上です!

base64エンコードよりも簡単にできるのが特徴です!

実装する時はできればこちらの方法がいいですね!😄