【Next.js14】Next.js API RouteからAWS S3バケットへアップロードする方法

2023/11/18に公開

はじめまして、Shinです。
http://shincode.info/2021/12/31/udemy-discount-coupon/

今回はNext.js API RouteからAWS S3バケットへアップロードする方法を紹介します。

クライアント側のform送信ロジック

Next.jsのformタグは以下のようになっています。バリデーションチェックもしたいのでreact-hook-formライブラリを利用しています。

app/ap
"use client";

import React, { useRef, useState } from "react";
import { useForm } from "react-hook-form";

const CreateBook = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const inputFileRef = useRef<HTMLInputElement>(null);
  const [blob, setBlob] = useState<any>(null);

  const onSubmit = async (data: any) => {
    const { title, content, price } = data;

    if (!inputFileRef.current?.files) {
      throw new Error("No file selected");
    }

    const file = inputFileRef.current.files[0];
    const imageFileName = file.name;

    const formData = new FormData();
    formData.append("file", file); // ここでファイルオブジェクトを追加

    //ここで画像アップロードAPIを叩く(後半で記載しています)
    const imageUploadResponse = await fetch(
      `${process.env.NEXT_PUBLIC_API_URL}/thumbnail/upload?filename=${imageFileName}`,
      {
        method: "POST",
        body: formData, //ここにセット
      }
    );
    
    ...省略
  };
  
  ...省略
  return (
    <form onSubmit={handleSubmit(onSubmit)} className="mt-10 container mx-auto">
      <div className="mt-4">
        <label htmlFor="image">サムネイル</label>
	//下記にname="file"を設定
        <input ref={inputFileRef} type="file" id="image" name="file" required />
        {errors.image && (
          <p className="text-red-500 font-medium">Price is required.</p>
        )}
        {blob && (
          <div>
            Blob url: <a href={blob.url}>{blob.url}</a>
          </div>
        )}
      </div>

      <button
        type="submit"
        className="bg-blue-400 px-6 py-3 font-medium rounded-md text-white shadow-sm mt-4"
      >
        Create Book
      </button>
    </form>
  );
};

export default CreateBook;

useRef()を使って、input属性を取得。そしてconst file = inputFileRef.current.files[0];でファイルオブジェクトを取得しています。

それをformData.append("file", file);で設定し、fetchのbodyへ付けてAPI側で受け取ります。

次に、Next.js API RouteでAPI設定をします。

APIの設定

Next.js13以降で導入された/appディレクトリでAPI Route設定をします。

app/api/thumbnail/upload/route.ts
import { NextResponse } from "next/server";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

//Upload image to AWS S3
export async function POST(request: Request) {
  const { ACCESS_KEY_ID, SECRET_ACCESS_KEY, REGION, S3_BUCKET_NAME } =
    process.env;

  const s3Client = new S3Client({
    region: REGION,
    credentials: {
      accessKeyId: ACCESS_KEY_ID || "",
      secretAccessKey: SECRET_ACCESS_KEY || "",
    },
  });

  // URLからファイル名を取得
  const { searchParams } = new URL(request.url);
  const fileName = searchParams.get("filename");

  const formData = await request.formData();
  const file: any = formData.get("file");

  // File オブジェクトから Buffer に変換
  const buffer = Buffer.from(await file?.arrayBuffer());

  // アップロードパラメータの設定
  const uploadParams: any = {
    Bucket: S3_BUCKET_NAME,
    Key: fileName, //保存時の画像名
    Body: buffer, //input fileから取得
    ContentType: "image/png", // 適切なContentTypeを設定
    ACL: "public-read", // 公開読み取りアクセスを設定
  };

  try {
    //画像のアップロード
    const command = new PutObjectCommand(uploadParams);
    const uploadResult = await s3Client.send(command);
    console.log("Upload success:", uploadResult);
    const imageUrl = `https://${S3_BUCKET_NAME}.s3.${REGION}.amazonaws.com/${fileName}`;
    return NextResponse.json({ imageUrl });
  } catch (err) {
    console.error(err);
    return NextResponse.json(err);
  }
}

重要な記述が以下でしょう。

  const formData = await request.formData();
  const file: any = formData.get("file");

  // File オブジェクトから Buffer に変換
  const buffer = Buffer.from(await file?.arrayBuffer());

S3に送る際はbuffer形式でないとダメみたいです。そうでないとアップロードできたとしても画像が表示されません。

また、公開読み取りアクセス設定の箇所のALCなのですが、これはS3バケットの方で有効に✅を入れておきましょう。じゃないと弾かれてエラーになります。下画像のようにしてください。

アクセス許可タブからオブジェクト所有者で確認できます。

実際に画像投稿してみる

やってみます。

上のCreate Bookボタンを押すと

このように画像アップロードできます。S3はこうなっています。

ということで、今回は以上になります。

Udemyクーポン配布中です。

気になる方は下記をチェックしてみてください。
http://shincode.info/2021/12/31/udemy-discount-coupon/

また、Youtubeも運営しているのでぜひ!
https://www.youtube.com/channel/UCNTxclE0N4qsUuirssL_D8w

Discussion