LoginSignup
11
9

More than 1 year has passed since last update.

Next.jsからGoogle Cloud Storageに画像をアップロード

Last updated at Posted at 2021-04-26

はじめに

Next.jsを使って、Google Cloud Storageに画像をアップロードしていきます。

今回コードを書くファイルは以下の2つです。

  • 「src/pages/index.tsx」
  • 「src/pages/api/upload.ts」

Next.jsにはAPIルートというものがあり、
pages/api内にあるファイルは '/api/*'にマッピングされ、APIエンドポイントとして使えます。
今回の場合は、'/api/upload'で扱う事ができます。

以下のような写真を入力し、アップロードするだけのシンプルなものを作っていきます。
スクリーンショット 2021-04-25 22.06.55.png

githubにコードも置いています。少しだけ他のも混ざってます。すみません。
https://github.com/tokio-k/Gcs-Upload-Nextjs

GCP側の準備

GCPを開きます。
https://console.cloud.google.com/?hl=ja

検索の箇所に「認証情報」と入力し
1番上に来ている「認証と情報 APIとサービス」をクリックします。

スクリーンショット 2021-04-26 10.57.12.png

認証情報のページに来たら、上部の「認証情報を作成」をクリックします。

認証情報を作成→サービスアカウントと進みます。

サービスアカウント名は任意の名前を入れてください。
ロールを選択で、Cloud Storageに権限を与えます。
完了をクリックし、サービスアカウントを作成します。

サービスアカウントが作成できたら、アカウントをクリックします。(メールアドレスの箇所)
サービスアカウントのページに来たら、「キー」のタブを選択し鍵を追加します。
新しい鍵を作成でjsonファイルを作成します。

このjsonファイルに様々な認証の情報が載っています。(他の人に見せてはいけません)
これで、GCP側の準備は完了です。

.env.localに追加していく

スクリーンショット 2021-04-26 11.11.42.png

{} hello-world-project-311214...が先程作成したjsonファイルです。
(gitignoreでgitにあげないようにしてください。)

ㅤ.gitignore
hello-world-project-311214-b2498….json

(追加するだけです。)

.env.localにjsonファイルのパスとプロジェクトIDを設定します。

ㅤ.env.local
PROJECT_ID=hello-world-proje....
GOOGLE_APPLICATION_CREDENTIALS="hell....85.json"

ライブラリのインストール

ターミナルで以下を実行して、ライブラリをインストールします。

yarn add @google-cloud/storage

コードの編集

index.tsx

(react-hook-formやuseStateなども使ってますがなくてもできます。)

index.tsx
import { useCallback, useState } from "react";
import { useForm } from "react-hook-form";

const IndexPage = () => {
  const { handleSubmit } = useForm({defaultValues: {name:"", iconUrl:""}});
  const [file, setFile] = useState<File>();
  const handleChangeFile = (e: any) => {
    setFile(e.target.files[0]);
  };
  const uploadImg = useCallback(async (file:File) => {
    const fileName = "imgfile2"
    const res = await fetch(`/api/upload?file=${fileName}`);
    const { url, fields } = await res.json();
    const body = new FormData();
    Object.entries({ ...fields, file }).forEach(([key, value]) => {
      body.append(key, value as string | Blob );
    });
    const upload = await fetch(url, {method:"POST", body});

    if (upload.ok) {
      console.log('Uploaded successfully!');
    } else {
      console.error('Upload failed.');
    }
  },[])

  const handleClick =  handleSubmit ( async () => {
    if(file) {
      uploadImg(file)
    }
  })
  return (
    <>
    <div>
      写真
      <input
        type="file"
        accept="image/*"
        onChange={handleChangeFile}
        id="icon"
      />
      <input type="submit" onClick={handleClick} />
    </div>
    </>
  )
}

export default IndexPage;

inputの写真が変更された時に、handoleChangeFileが呼ばれて、
setFileでファイルをfileにセットしてる。

送信(input type submit)がクリックされた時に、setFileが呼ばれて、
fileを引数にuploadImgが呼ばれている
uploadImgが今回のアップロードの処理です。

uploadImgのみを抜粋

index.tsx
const uploadImg = useCallback(async (file:File) => {
    const fileName = "imgfile2" 
    const res = await fetch(`/api/upload?file=${fileName}`);
    const { url, fields } = await res.json();
    const body = new FormData();
    Object.entries({ ...fields, file }).forEach(([key, value]) => {
      body.append(key, value as string | Blob );
    });
    const upload = await fetch(url, {method:"POST", body});

    if (upload.ok) {
      console.log('Uploaded successfully!');
    } else {
      console.error('Upload failed.');
    }
  },[])


fileNameはアップロードする画像の名前になります。
fileから名前を取得したり、ランダムな文字列を取得してつけると良いと思いますが、今回は固定にしています。


ここで、pages/api/upload.tsの処理が実行できます。
file=${fileName}はクエリパラメータです。情報を渡しています。
アップロードするためのurlや認証に関する情報などをレスポンスとして受け取っています。


実際に、画像をアップロードしている箇所です。
②で取得したurlや、その他bodyなどを使って画像をアップロードしています。

upload.ts

upload.ts
import { Storage } from "@google-cloud/storage";

export default async function handler(req: any, res: any) {
  const storage = new Storage({
    projectId: process.env.PROJECT_ID,
    keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
  });
  const bucketName = "sample-upload-img";
  const bucket = storage.bucket(bucketName);
  const file = bucket.file(req.query.file);
  const options = {
    expires: Date.now() + 1 * 60 * 1000,
    fields: { "x-goog-meta-test": "data" },
  };
  const [response] = await file.generateSignedPostPolicyV4(options);
  res.status(200).json(response);
}

req.query.fileにはクエリパラメータの値が入ってきます。
bucket.file("IconImage/" +req.query.file)などにすると、フォルダの中に画像を入れることも可能です。

bucketNameにはバケットの名前を入れます。
作成していなければ、Google Cloud Storageのサイト上部の「+バケットを作成」から、作成します。

これで、画像のアップロードの処理を書く事ができました。
最後に、CORSの設定をします。

CORSの設定

スクリーンショット 2021-04-26 11.34.12.png

右上の赤丸の箇所をクリックして、画面下部の黒い画面を開きます。
ここからはこの画面で進めていきます。

プロジェクトに含まれるバケットを一覧表示します。

$ gsutil list
gs://sample-upload-img/

corsの設定を書くファイルを作成します。

$ touch cors_setting.json

corsの設定を記述していきます。

$ vim cors_setting.json

以下の様に書き換えます。

ㅤ.json
[
  {
    "origin": [
      "http://localhost:3000"
    ],
    "responseHeader": [
      "Content-Type"
    ],
    "method": [
      "POST"
    ],
    "maxAgeSeconds": 60
  }
]

スクリーンショット 2021-04-26 11.43.38.png

最後に、corsの設定ファイルの内容をバケットに設定します。

$ gsutil cors set cors_setting.json gs://sample-upload-img/

(確認もできます。)

gsutil cors get gs://sample-upload-img/

これで、corsの設定が完了しました。
これで画像がアップロードできる様になりました!

わからないところ、間違っているところがありましたらコメントお待ちしております。

参考文献

https://github.com/leerob/nextjs-gcp-storage
https://daiiz.hatenablog.com/entry/2017/03/05/001700

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9