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

#はじめに
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/670edcd1-9c94-e857-8d72-7770d1a2f5d0.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/9db1e2cd-1a5b-1e35-59fb-375cc8d349a4.png)

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

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

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

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

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

# .env.localに追加していく

![スクリーンショット 2021-04-26 11.11.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/62f2e0e9-c1c8-b59e-1ddb-d4a162c6bdbb.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/ba0455a5-29fe-0c6d-ee8e-d0f71e217968.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506867/25d789ef-e447-8485-a00c-46a027de7419.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