Twitterクローンを作ります #1 プロジェクト作成

[Twitterクローンを作っていく企画](https://qiita.com/apollo-devel/items/4b6ee6149afc600f0092)の第1回です。
今回から実際に作業を進めていきます。

※ 今回のコードは[こちら](https://github.com/apollo-devel/toitta/tree/793b44f1ed3d9246b3ceeeac68377780a11e6b7e)で確認できます。

今回はクライアントとAPIサーバのプロジェクトをそれぞれ作成し、通信ができるところまで進めます。

はじめにnodeのバージョンを確認しておきます。

```
$ npm --version
6.14.12

$ node --version
v14.16.1
```

# クライアント側のプロジェクト作成

クライアント側のプロジェクトを作成します。
`/path/to/project/` はプロジェクトのルートディレクトリのつもりです。

```
$ npm install -g @vue/cli
$ cd /path/to/project/
$ vue create client
```

プリセットの選択を求められたので `Manually select features` を選択します。
Babel, Router, Vuex, Linter / Formatter を選択します。
Vue.js のバージョンは 3.x を選択します。
Use history mode for router? は Y を入力しておきます。
linter / formatter の設定は ESLint with error prevention only を選択しておきました。
aditional lint features は Lint on save を選択しました。
設定は In dedicated config files を選択しました。

```
Vue CLI v5.0.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
```


とりあえず起動してみます。

```
$ cd client
$ npm run serve
```

http://localhost:8080 にアクセスすると無事表示されました。

![スクリーンショット 2022-02-24 11.24.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/d62bc965-dabb-262d-869b-af5f80e54f51.png)


# APIサーバ側のプロジェクトを作成

まずはディレクトリと、Pythonの仮想環境を作ります。

```
$ cd /path/to/project/
$ mkdir api
$ cd api
$ python3 -m venv api-env
```

仮想環境を起動します。

```
$ source ./api-env/bin/activate
```

下記の内容で、 requirements.txt を作成します。後で constraints.txt も作成する予定なので、ここではバージョンを指定しません。

```requirements.txt
Flask
```

それではライブラリをインストールします。

```
$ pip install -r requirements.txt
```

constraints.txt を作っておきましょう。

```
$ pip freeze > constraints.txt
```

僕の環境では以下の様になりました。

```constraints.txt
click==8.0.4
Flask==2.0.3
itsdangerous==2.1.0
Jinja2==3.0.3
MarkupSafe==2.1.0
Werkzeug==2.0.3
```

以後、Pythonのライブラリを追加する際は、requirements.txt にライブラリを追記 → `pip install -r requirements.txt -c constraints.txt` → `pip freeze > constraints.txt` という流れで requirements.txt と constraints.txt を更新していきます。

ここで .gitignore も作っておきましょう。プロジェクトのルートディレクトリに下記の内容で作成します。

```.gitignore
api-env/
__pycache__/
```

クライアント側については、`vue create client`によって `client/.gitignore` が生成されているので現段階ではapi側の仮想環境を無視するだけで良さそうです。

# APIサーバとクライアントの通信

apiディレクトリの直下に下記の内容で main.py を作成します。

```python:main.py
from flask import Flask, jsonify, session

app = Flask(__name__)
app.secret_key = 'DUMMY'

@app.route("/api/hello")
def hello_world():
    if 'count' not in session:
        session['count'] = 0
    session['count'] += 1
    return jsonify({
        'count': session['count'],
        'message': 'hello'
    })
```

下記で起動できます。仮想環境が起動していないとエラーになるので注意してください。

```
$ cd /path/to/project/api
$ FLASK_APP=main flask run
```

この状態で http://localhost:5000/api/hello にアクセスすると `{"count":1,"message":"hello"}` のような文字列が表示されることが確認できると思います。

ここからは、クライアント側からAPIが呼び出せるか？ と その際にセッションを利用できるか？ を確認していきます。

まずはAPI呼び出し用のライブラリをインストールしておきます。

```
$ cd /path/to/project/client
$ npm install axios
```

`client/src/components/HelloWorld.vue`を下記のように書き換えてAPI呼び出しを試してみます。

```js:client/src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>メッセージ : {{ message }}</p>
    <p>カウント : {{ count }}</p>
    <button @click=onClick>push</button>
  </div>
</template>

<script>
import axios from 'axios';
import { ref } from 'vue';

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup () {
    const message = ref('');
    const count = ref(0);
    const onClick = () => {
      axios.get('http://localhost:5000/api/hello')
        .then(res => {
          message.value = res.data.message;
          count.value = res.data.count;
        });
    };
    return {
      message,
      count,
      onClick
    };
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>
```

push と書かれたボタンをクリックするとAPIを呼び出してその内容が画面に表示されるはずですが、実際に押してみても反応がありません。ブラウザの開発者ツール等で確認するとわかりますが、CORSでエラーになっています。
これを解消するには、vue.config.js を下記のように修正します。

```js:vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    proxy: {
      "^/api/*": {
        target: "http://localhost:5000"
      }
    }
  }
})
```

あと、先程の `client/src/components/HelloWorld.vue` の onClick を下記のように修正しておいてください。

```js
    const onClick = () => {
      axios.get('/api/hello')
        .then(res => {
          message.value = res.data.message;
          count.value = res.data.count;
        });
    };
```

この状態でクライアントを起動すると、ちゃんとAPIを呼び出せることとセッションに保存した値がインクリメントされていくことが確認できると思います。

![スクリーンショット 2022-02-24 11.30.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/74d47f43-84ba-b1a2-c589-664182184225.png)



今回はここまでです。
次回はログイン周りをやっていこうと思います。