Next.jsからgcsに画像を登録したり呼び出したりしたい！！！！


# まずはGoogleCloudPlattformに登録するにょ〜
まあ、この辺参照しながら頑張ってみてください。

https://cloud.google.com/free?utm_source=google&utm_medium=cpc&utm_campaign=japac-JP-all-ja-dr-BKWS-all-all-trial-EXA-dr-1605216&utm_content=text-ad-none-none-DEV_c-CRE_521780177400-ADGP_%5BDNU%5DHybrid%20%7C%20BKWS%20-%20EXA%20%7C%20Txt%20~%20GCP%20~%20General_google-KWID_43700063983700146-aud-1596662389094%3Akwd-1195304733301&userloc_1009296-network_g&utm_term=KW_gcp%20%E7%99%BB%E9%8C%B2&gclid=CjwKCAjw586hBhBrEiwAQYEnHYALkkep401h3YULjJa9dWkDvyApSSj87mT9wYaOs9soJE5bRevY0RoCD80QAvD_BwE&gclsrc=aw.ds&hl=ja

# サービスアカウントを作るにょ〜
登録できたら、プロジェクトを作成します。
作成が終わったら、サービスアカウントを作成していきます。

この画面で認証情報をクリックします。
![](https://storage.googleapis.com/zenn-user-upload/cec3bd4a99b7-20230410.png)


画面上部の認証情報の作成→サービスアカウントの作成をクリックします。
![](https://storage.googleapis.com/zenn-user-upload/ba2669992186-20230410.png)
この辺は任意でつけられます。

![](https://storage.googleapis.com/zenn-user-upload/a371f17b3478-20230410.png)

そしたらこのサービスアカウントに対してGCSにアクセス可能なロールを与えてあげます。
まあ、難しいことは考えずにストレージ管理者でいいでしょう！！！！知らんけど！！！！

あとは適当にすっ飛ばしてサービスアカウント作成完了です🙌

# 認証情報を管理するにょ〜
作成できたサービスアカウントをクリックして画面上部のキーを選択します。
![](https://storage.googleapis.com/zenn-user-upload/7c24ddacbece-20230410.png)
JSONを選択します。
鍵がダウンロードされるので、それをリポジトリの一番上におきます。（正確には一番上っていうかDockerfileやら.envがあるあたり）

:::note alert
注意してね！！！！
その鍵はとても大切なものなので絶tっっっっっっっっっっっっっっっっっっ対に人に教えてはいけません。
もちろんgitにPushするのもNGです。
gitignoreに入れましょう。
:::

# ここまでできた自分を褒めよう！！！！讃えよう！！！！
ここまできたら富士山で言うところの8号目を突破したと言っても過言じゃないです（過言）
自分に大きな拍手！！！！！🎉

# バケットを作るにょ〜
![](https://storage.googleapis.com/zenn-user-upload/4351147f1bee-20230410.png)

上記画像のGCSをクリック
![](https://storage.googleapis.com/zenn-user-upload/0fb0a5cf7ccd-20230410.png)
こんな画面が来るので、バケットを作成

![](https://storage.googleapis.com/zenn-user-upload/fbfb59add5e2-20230410.png)

:::note
グローバルで一意な誰とも被らない素敵な名前をつけてあげましょう
:::
![](https://storage.googleapis.com/zenn-user-upload/f7766810d911-20230410.png)
保存場所はまあ、どこでもいいんだけどなんとなくデータと近くにいたいから東京で！
ロケーションタイプはお財布と相談しながら決めるにょ〜
（個人開発程度なら**Resion**でいいかな）

![](https://storage.googleapis.com/zenn-user-upload/15d122bcb18e-20230410.png)
これもautoclassでいいかな。あの天下のGoogleさんだしよしなにやってくれるだろ。知らんけど！

![](https://storage.googleapis.com/zenn-user-upload/f3d0ec5ae4b6-20230410.png)
ここら辺は、まあよくわからん！なので適当にスルー

![](https://storage.googleapis.com/zenn-user-upload/e508b8518d43-20230410.png)
ここまできたら作成ボタンをクリック！！！！！

バケットの完成🎉

# コードを書いていくにょ〜
```ts:lib/image.ts
import {v4 as uuidv4} from 'uuid'

export const uploadImg = async (file:File) => {
  const fileName = uuidv4()
  const res = await fetch(`/api/uploadImage?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('upload failed')
    return ''
  }
  return fileName
}
```
jsとかtsに慣れてる人は説明しなくてもわかるわ！！！って感じだと思うけど、①のURLの中にいる?fileってやつはquery parameterとか言ったりするにょ〜

よくわからんぞ🤔って人はこの辺参考にしてみてね

https://be-marke.jp/articles/knowhow-queryparameter#:~:text=%E3%82%AF%E3%82%A8%E3%83%AA%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%A8%E3%81%AF%E3%80%81%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC,%E3%81%97%E3%81%A6%E3%81%8D%E3%81%9F%E3%81%8B%E5%88%A4%E5%88%A5%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82

①のAPIはここで用意する
```ts:pages/api/uploadImage.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 = process.env.BUCKET_NAME ?? ''
  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)
}

```

envは.env.localというファイルを用意してそこに書こう！
```.env:.env.local（sample）
PROJECT_ID="hogehoge"
GOOGLE_APPLICATION_CREDENTIALS="hugahuga"
BUCKET_NAME="hshs"
```


ここで注意することは
**@google-cloud/storage**
はNode.jsのライブラリなのでフロントからは叩けないと言うこと。
自分はこれで試行錯誤しましたが、、、無念、、、

# やっと叩くぞ〜！<upload編>
ここからはフロントエンドから叩く方法をご紹介
```tsx:pages/index.tsx
export const HomePage: NextPage = () => {
　　const [file, setFile] = useState<File | null>(null)
　　// 以下でfileの変更を検知してデータを取得
　　const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
    const selectedFile = event.target.files && event.target.files[0]
    if (!selectedFile) return
    setFile(selectedFile)
}
---
　　const handleSubmit = async(file: File) => {
　　　　const imageIDinGcs = await uploadImg(file)
　　　　if (!imageIDinGcs) return alert('画像のアップロードに失敗しました。')
  // ここから先はまあ、データベースに登録するなり好きにやってください
　　}
}
```
おめでとう！！これでuploadが完了したはず！
consoleを見るなり、バケットを見にいくなりしてください

# やっと叩くぞ〜！<get編>
ほんほん、なるほど。と。
URLを登録するのではなく、名前（idと言った方がしっくり来るかも？）を登録するのかと。
これじゃあ、Imageとして読めないじゃないかと。
思った

::: note warn
そこのあなた！！！！！！！
:::
鋭いですねえええ

はい。その通りです。なので、gcsで検索をかける処理を書かなければなりません。
そりゃPostがあればGetもあって然るべきですよね。

```ts:/pages/api/getImage.ts
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 = process.env.BUCKET_NAME ?? ''
  const fileName = req.query.file ?? ''

  if (fileName === '') {
    res.status(400).json('file name is empty')
    return
  }

  const bucket = storage.bucket(bucketName)
  const file = bucket.file(fileName)

  // 有効期間を指定してSigned URLを生成する
  const options = {
    version: 'v4' as const,
    action: "read" as const,
    expires: Date.now() + 5 * 60 * 1000, // 5分間有効
  }

  const [url] = await file.getSignedUrl(options)
  return res.status(200).json({url})
}

```
とまあこんな感じ。
handlerの型が気になる人は適当にhttpからRequestとかResponseとか引っ張ってくればいいかと

フロントから叩く時はこんな感じ
```tsx:/pages/users/[id].tsx
const [image, setImage] = React.useState<string | null>(null)
  useEffect(() => {
    if (user) {
      fetchData(String(user.image))
    }
  }, [user])
  const fetchData = async (fileName: string) => {
    const res = await fetch(`/api/getProfImage?file=${fileName}`)
    const JSONRes = await res.json()
    setImage(JSONRes.url)
  }
```

こんな感じにするといい感じに表示されるにょ🎉
useEffectを使用する理由は、userが取得できていない段階で叩いてしまうとエラーになるので、取得してから発火するため

# 最後に
ここまでお疲れ様でした〜〜〜！！
クラウド系覚えること多い（特にロールがめんどい）けど、頑張って一つずつ使えるようになってクラウドマスター目指していきましょ〜〜〜！！！

# 参考文献
https://qiita.com/toki_dev/items/09c4665525e44beae4e5

https://sterfield.co.jp/programmer/%E7%BD%B2%E5%90%8D%E4%BB%98%E3%81%8Durl%E3%81%A7cloud-storage-%E3%81%AB%E7%94%BB%E5%83%8F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E7%9B%B4%E6%8E%A5%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC/

# 著者

https://twitter.com/ryuji_vlog