Twitterクローンを作ります #2 ユーザー登録機能 (1)

[Twitterクローンを作っていく企画](https://qiita.com/apollo-devel/items/4b6ee6149afc600f0092)の第2回です。

前回はクライアント側とAPI側のプロジェクトを作り、通信を確認しました。
今回はユーザー登録機能を作っていきます。

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

# ユーザー登録画面

ユーザー登録画面を作成していきます。 `client/src/views/Register.vue` を追加していきましょう。


```vue:client/src/views/RegisterView.vue
<template>
    <div>Register Page</div>
</template>

<script>
export default{
    setup() {
        
    },
}
</script>
```

http://localhost:8080/register でアクセスできるようにルーターの設定をしていきます。

`client/src/router/index.js` を下記のように修正します。

```js:client/src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  },
  {
    path: '/register',
    name: 'register',
    component: () => import('../views/RegisterView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

```

これで http://localhsot:8080/register にアクセスすることができるようになります。

![スクリーンショット 2022-02-24 12.11.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/9f5b03f9-81ba-b237-3275-bae51bc4ec79.png)

それでは画面を作っていきましょう。UIはUIKitを利用していこうと思っているのでインストールします。

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

`client/main.js` を修正してUIKitを読み込みます。

```js:client/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import UIkit from 'uikit'
import Icons from 'uikit/dist/js/uikit-icons'
import 'uikit/dist/css/uikit.min.css'
UIkit.use(Icons)

createApp(App).use(store).use(router).mount('#app')
```

これでUIKitが利用できるので、ユーザー登録画面を作っていきます。
`client/src/views/RegisterView.vue` を下記のように修正します。

```vue:client/src/views/RegisterView.vue
<template>
    <div class="uk-container">
        <h1>ユーザー登録</h1>

        <form class="uk-form-horizontal uk-margin-large" @submit.prevent>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__display-name">表示名</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input" 
                        id="register__display-name"
                        type="text"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__username">ユーザーID</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__username"
                        type="text"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__email">メールアドレス</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__email"
                        type="email"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__password">パスワード</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__password"
                        type="password"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__password-confirm">パスワード(再確認)</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__password-confirm"
                        type="password"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <input class="uk-button uk-button-primary" type="submit"  value="登録" @click="onClick">
            </div>
        </form>
    </div>
</template>

<script>
import UIkit from 'uikit';

export default {
    setup() {
        const onClick = () => {
            UIkit.notification("hello", {status: 'danger'});
        };
        return {
            onClick
        };
    },
}
</script>
```

これでなんとなく画面はできました。最上部にメニューが出てしまっていますがこれはあとでなおしましょう。

![スクリーンショット 2022-02-24 14.59.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/3347186e-a4b6-8725-c684-998b29badbbe.png)

ここからはユーザー登録用のAPIを用意して、それを呼び出せるようにしていきましょう。
API側のプロジェクトのディレクトリ名をapiにしてしまいましたが、APIを定義するファイルをapisというディレクトリにしたいので `api/apis/hoge.py` みたいになってしまうのが微妙な気がしてきました。serverに変更しましょう。

```
$ deactivate
$ cd /path/to/project/api
$ rm -rf ./api-env/
$ cd ..
$ mv api server
$ cd server
$ python3 -m venv server-env
$ source ./server-env/bin/activate
$ pip install -r requirements.txt -c constraints.txt
```

.gitignoreも修正しておきます。

```.gitignore
server-env/
__pycache__/
```

それではAPIを追加していきます。

まずはmain.pyを修正します。

```python:server/main.py
from flask import Flask
import logging

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

app.logger.setLevel(logging.INFO)
```

次にユーザー登録用のAPIを追加します。

```python:server/apis/users.py
from flask import jsonify, request
from server.main import app

@app.route('/api/users', methods=['POST'])
def register():
    body = request.json
    app.logger.info(body)
    return jsonify(body)
```

仮実装として、リクエストのbodyをログ出力しつつそのまま返却するようにしました。

画面側は入力された値をAPIに送るように修正します。

```vue:client/src/views/RegisterView.vue
<template>
    <div class="uk-container">
        <h1>ユーザー登録</h1>

        <form class="uk-form-horizontal uk-margin-large" @submit.prevent>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__display-name">表示名</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input" 
                        id="register__display-name"
                        type="text"
                        v-model="displayName"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__username">ユーザーID</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__username"
                        type="text"
                        v-model="username"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__email">メールアドレス</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__email"
                        type="email"
                        v-model="email"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__password">パスワード</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__password"
                        type="password"
                        v-model="password"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <label class="uk-form-label" for="register__password-confirm">パスワード(再確認)</label>
                <div class="uk-form-controls">
                    <input 
                        class="uk-input"
                        id="register__password-confirm"
                        type="password"
                        v-model="passwordConfirm"
                        required>
                </div>
            </div>
            <div class="uk-margin">
                <input class="uk-button uk-button-primary" type="submit"  value="登録" @click="onClick">
            </div>
        </form>
    </div>
</template>

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

export default {
    setup() {
        const displayName = ref("");
        const username = ref("");
        const password = ref("");
        const passwordConfirm = ref("");
        const email = ref("");
        const onClick = () => {
            var body = {
                display_name: displayName.value,
                username: username.value,
                password: password.value,
                email: email.value
            };
            axios.post('/api/users', body).then(resp => {
                UIkit.notification("success");
                console.log(resp);
            });
        };
        return {
            displayName,
            username,
            password,
            passwordConfirm,
            email,
            onClick
        };
    },
}
</script>
```

今回はここまでにしましょう。
次回はDBに登録する部分とバリデーションをやっていきたいと思います。