ReactとFlaskがGCPへ

# GCP使ってみたいドリブンでYelp API使ったテストサイトを作る

GCPでAPIテストサーバー作りたかった
言語: React, Flask
<img width="454" alt="Screen Shot 2020-03-22 at 1.12.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/6b6e8446-bedd-c4db-99c0-826691aa523e.png">


これにどこからかのapi付けて描画すればいいかなっと思ったけど味気ない (**Learn Udon**のリンク先は、うどんについて語る[wiki](https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%A9%E3%82%93))ので
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/14dcad62-b08c-8bdf-ea57-da9afb617221.png)Yelp API使ってこんな感じにした




# Yelp api
日本の食べ物系サイトはみんな知ってるし、海外のをちょっと見てるのもよいかなとyelp apiを使った

api keyの取得は簡単。[yelp developers](https://www.yelp.com/developers)に入ってgoogleかfacebookのアカウントで登録すればすぐ発行してくれる

# Flask

ディレクトリ構成

```bash
app
├── config.py
└── run.py
```

```python
# run.py

from flask import Flask, request
from flask_cors import CORS
import requests

import config

app = Flask(__name__)
CORS(app)

URL = "https://api.yelp.com/v3/businesses/search"
headers = {'Authorization': f"Bearer {config.API_KEY}"}


@app.route('/')
def ramen_yelp():
    payload = {
        "term": request.args.get("term", "ramen"),
        "location": request.args.get("location", "ny")
    }
    response = requests.request("GET", URL, headers=headers, params=payload)
    return response.json()


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=config.PORT, debug=config.DEBUG_MODE)

```
root('/')の場合はデフォルトでニューヨークのラーメンランキングにしている

configにはflask, gunicornの設定
大事なことはenv変数に入れておく

```python
# config.py

from os import environ
import multiprocessing

PORT = int(environ.get("PORT", 8080))
DEBUG_MODE = int(environ.get("DEBUG_MODE", 1))
API_KEY = environ.get("API_KEY")

bind = ":" + str(PORT)
workers = multiprocessing.cpu_count() * 2 + 1
threads = multiprocessing.cpu_count() * 2
```


ブラウザ(firefox or chrome)から**0.0.0.0:8080**に入ると
<img width="790" alt="json_screen_shot.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/87fd52ca-9a4f-d2d0-52a8-17df4531cce5.png">

結果をjsonで吐き出すようになった

# GCR (Google Container Registry)に登録
flaskをdocker化してGCRにup

Dockerfileはこんな感じでサクッと

```dockerfile
# pull official base image
FROM python:3.8.1-alpine

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install dependencies
RUN pip install --upgrade pip
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

# set work directory
WORKDIR /usr/src/app

# copy project
COPY ./app .

ENV PORT 80
ENV API_KEY YOUR_API_KEY

CMD ["gunicorn", "run:app", "--config=config.py"]
```

```python
# requirements.txt
Flask==1.1.1
gunicorn==20.0.4
requests==2.23.0
Flask-Cors==3.0.8
```

**本番はAPI_KEYを[dotenv](https://pypi.org/project/python-dotenv/)使ってconfig.pyにまとめる*

* gcloudをローカルにインストール([doc](https://cloud.google.com/sdk/docs/quickstart-macos))
* GCRの課金を有効にする

次に下記のようにコマンド打てば簡単に登録できる

```bash
#!/bin/sh
docker build -t flask-test-gcr 
docker tag flask-test-gcr [HOSTNAME]/[PROJECT-ID]/flask-test-gcr
docker push [HOSTNAME]/[PROJECT-ID]/flask-test-gcr
gcloud container images list-tags [HOSTNAME]/[PROJECT-ID]/flask-test-gcr
```
こんな感じ
<img width="843" alt="Screen Shot 2020-03-22 at 14.08.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/baffbdda-4bdd-54dd-1086-ee81b42cff6b.png">


# GCE(Google Compute Engine)
インスタンスをgcpコンソール画面から作成

マシンスペックはテストなので最弱の最弱にしよう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/35e1717a-ab5e-c199-5017-f61eb148bd35.png)

docker使ってコンテナからすぐデプロイしたい人は下記のチェックとコンテナイメージを忘れずに
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/a99f5b58-2409-8bdd-56d6-a4b9679bf54b.png)

GCE VMインスタンス画面の外部IPを *http:[IP]* にすれば同じ結果が出るようになった！
SSL証明書がない場合は外部IPを *https* じゃなくて *http* にしないとダメだよ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/eaf02187-e2fd-3a75-d27f-ef63b8b8c289.png)

このIPアドレスをReactから呼び出すのでメモしておく


# FrontはReact
create-react-appというステキなボイラープレートで簡単に

```bash
# app-nameは好きな名前で
npx create-react-app app-name
```

App.jsはほとんどテンプレで、画像やリンク、Yelp.jsのComponentを呼んでいる違いぐらい

```react
// App.js

import React from 'react';
import logo from './logo.svg';
import './App.css';

import Yelp from './Yelp' //←これ

function App() {
  return (
    <div className="main">
      <Yelp />
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="Udon logo" />
          <a
            className="App-link"
            href="https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%A9%E3%82%93"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn Udon
        </a>
        </header>
      </div>
    </div>
  );
}

export default App;
```


```react
// Yelp.js

import React, { useEffect, useState } from 'react'
import './Yelp.css'

const Yelp = () => {
  const [error, setError] = useState(null)
  const [yelpData, setYelpData] = useState({})

  useEffect(() => {
    const API_URL = 'http://さっきメモした外部IP/'
    fetch(API_URL)
      .then(res => res.json())
      .then(
        result => {
          setYelpData(result)
        })
      .catch(e => {
        setError(e)
      })
  }, [])

  if (error) {
    return (
      <div className="Yelp">error</div>
    )
  } else if (yelpData.businesses) {
    const images = yelpData.businesses.map(item => {
      return (
        < a key={item.id} href={item.url} target="_blank" rel="noopener noreferrer" >
          < img
            height="150" width="150" crop="fill" radius="20"
            src={item.image_url} alt="ramen"
          />
        </a >
      )
    })
    return (
      <div className="Yelp">
        {images}
      </div>
    )
  } else {
    return (
      <div className="Yelp"></div>
    )
  }
}

export default Yelp
```

あとはローカルで起動すればラーメン画像が出てくる

```bash
# 起動
yarn start
```

好きな画像をクリックするとYelpのリンク先にとんで詳細が見れる
![ラーメン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202660/5c09fba0-50d2-7be6-9c06-5130ae201e92.gif)


* ニューヨークのラーメン屋さんはメニューが豊富。ラーメン一筋でやるのは文化的に合わないのかな
* うどんのSVGがグルグル回ってるのがうっとうしい、良いかは賛否ある

### 参考
[ContainerRegistry(GCR)に登録されたDockerイメージをGCE上で動かす](https://qiita.com/nobi_tum/items/d8595c040f1ec2281307)