これさえあればWEBアプリが簡単に作れる！Nuxt・Express・Vuetifyを使ったログイン画面の実装テンプレ

# 目的
nuxt.jsを使うことでWEBアプリが簡単に作れるようになりました。
加えてVuetifyやExpressを使うことで、高度なWEBアプリが開発できます。
今回はそれらを使ったログイン画面の実装テンプレートを紹介します。

# 実装に向けて
https://github.com/nuxt-community
基本このGithubを参考にしました。
Expressやauth-moduleのテンプレートはありますが、
まるっと統合したものが欲しかったので、これらを応用して作成しました。

### 使用したフレームワーク・ライブラリ
 - nuxt
 - Express（API）
 - vuetify（デザイン）
 - axios（API通信）

### ドキュメント
 - [Nuxt](https://ja.nuxtjs.org/guide/)
 - [auth-module](https://auth.nuxtjs.org/)
 - [Vuetify](https://vuetifyjs.com/ja/getting-started/quick-start)
 - [Express](https://expressjs.com/ja/)

### GitHubレポジトリ
https://github.com/tonio0720/nuxt_login_template

## ソース解説
### フォルダ構成
```{bash}
./api                  # Express
./api/index.js
./api/routes           # Express Router
./api/routes/auth.js   # 認証用API
./assets
./assets/css
./assets/css/main.css  # 全体のCSS
./components
./layouts              # レイアウト
./layouts/default.vue  # デフォルトのレイアウト（Vuetify)
./layouts/error.vue
./layouts/blank.vue    # ログイン画面用のレイアウト（ログイン画面ではヘッダーとかは見えてほしくないので）
./nuxt.config.js       # nuxtの設定
./package.json
./pages                # この中に各ページを作る
./pages/index.vue      # ホーム画面
./pages/login.vue      # ログイン画面
./plugins
./static
./static/favicon.ico
./store
./store/index.js
```
### nuxt.config.js

```{javascript:nuxt.config.js} 
module.exports = {
    // <head>タグの中身
    head: {
        title: 'nuxt-login-template',
        meta: [
            { charset: 'utf-8' },
            { name: 'viewport', content: 'width=device-width, initial-scale=1' },
            { hid: 'description', name: 'description', content: 'nuxt login template' }
        ],
        link: [
            { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
            {
                rel: 'stylesheet',
                type: 'text/css',
                href: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons'
            }
        ]
    },
    plugins: [],
    css: [
        // 全体のCSS
        '~/assets/css/main.css'
    ],
    build: {
        vendor: ['@nuxtjs/axios'],
        extractCSS: true,
        extend(config) {
            if (process.server && process.browser) {
                config.module.rules.push({
                    enforce: 'pre',
                    test: /\.(js|vue)$/,
                    loader: 'eslint-loader',
                    exclude: /(node_modules)/
                });
            }

        }
    },
    serverMiddleware: [
        // API
        '~/api/index.js'
    ],
    // ライブラリの読込
    modules: [
        '@nuxtjs/axios',
        '@nuxtjs/auth',
        '@nuxtjs/vuetify'
    ],
    axios: {
        proxy: true
    },
    proxy: {
        '/api': `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}`
    },
    // ここでauth-moduleを有効にする
    auth: {
        redirect: {
            login: '/login',
            logout: '/',
            callback: '/login',
            home: '/'
        },
        strategies: {
            local: {
                endpoints: {
                    login: {
                        url: '/api/auth/login',
                        method: 'post',
                        propertyName: 'token.accessToken'
                    }
                }
            }
        }
    },
    // Vuetifyを有効にする
    vuetify: {}
};
```
### api
Expressを使って実装。
認証はexpress-jwtを使用。

#### index.js
```{javascript:index.js}
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const jwt = require('express-jwt');

const app = express();
const secret = 'dummy';  // jwt secret

app.use(cookieParser());
app.use(bodyParser.json());

// ログインしてなくてもここにはアクセスできる
app.use(
    jwt({ secret })
        .unless({ path: '/api/auth/login' })
);

app.use('/auth', require('./routes/auth'));

// other routes

app.use((err, req, res, next) => {
    console.error(err);
    res.status(401).send(err + '');
});

module.exports = {
    path: '/api',
    handler: app
};

```

#### routes/auth.js

```{javascript:auth.js}
const { Router } = require('express');
const jsonwebtoken = require('jsonwebtoken');

const router = Router();
const secret = 'dummy';

async function checkAuth({ username, password }) {

    // ここでDBに接続するなり、IDPASSの確認

    if (username === 'xxx' && password === 'xxx') {
        return true;
    }

    return false;

}


router.post('/login', async (req, res, next) => {

    const { username, password } = req.body;

    if (!await checkAuth({ username, password })) {
        res.status(401).send('Invalid username or password');
        return;
    }

    const accessToken = jsonwebtoken.sign({
        username
    }, secret);

    res.json({ token: { accessToken } });

});

router.get('/user', (req, res, next) => {
    res.json({ user: req.user });
});

router.post('/logout', (req, res, next) => {
    res.json({ status: 'OK' });
});

module.exports = router;

```


### pages
#### index.vue
```{vue:index.vue}
<template>
    <v-container fluid>
        index
    </v-container>
</template>

<script>

export default {
    // middlewareにauthを追加することで、
    // ログインしていないとログイン画面にリダイレクトされる
    middleware: ['auth'],
    head() {
        return {
            title: 'ホーム'
        };
    }
};
</script>

<style scoped>
</style>
```

#### login.vue
```{vue:login.vue}
<template>
    <v-container>
        <v-layout justify-center>

            <v-flex xs12 sm4>

                <v-toolbar color="primary" dark>
                    <v-toolbar-title>ログイン</v-toolbar-title>
                </v-toolbar>

                <v-card>
                    <v-container fluid>
                        <v-form ref="form" v-model="valid" lazy-validation>

                            <v-layout row wrap>
                                <v-flex xs12>
                                    <v-alert v-if="error" :value="true" type="error" outline>
                                        {{ error }}
                                    </v-alert>
                                </v-flex>
                            </v-layout>

                            <v-layout row wrap>
                                <v-text-field type="text" v-model="username" label="ログイン名" required></v-text-field>
                            </v-layout>

                            <v-layout row wrap>
                                <v-text-field type="password" v-model="password" label="パスワード" required></v-text-field>
                            </v-layout>

                            <v-layout row wrap justify-end>
                                <v-btn color="success" :disabled="!valid" @click="login">ログイン</v-btn>
                            </v-layout>

                        </v-form>
                    </v-container>
                </v-card>

            </v-flex>

        </v-layout>
    </v-container>
</template>

<style scoped>
</style>

<script>

export default {
    layout: 'blank', // layouts/blank.vueを使用
    middleware: ['auth'],
    head() {
        return {
            title: 'ログイン'
        }
    },
    data() {
        return {
            valid: true,
            username: '',
            password: '',
            error: null
        };
    },
    methods: {

        async login() {

            this.error = null;

            if (this.$refs.form.validate()) {

                return this.$auth
                    .loginWith('local', {
                        data: {
                            username: this.username,
                            password: this.password
                        }
                    })
                    .catch(e => {
                        this.error = 'ログインに失敗しました。IDかパスワードが間違っている可能性があります。';
                    });

            }
        }
    }
}
</script>
```


## 使い方
ソースのダウンロード
<code>$ git clone git://github.com/tonio0720/nuxt_login_template.git .</code>

依存のインストール
<code>$ npm i</code>

開発環境に実装
<code>$ npm run dev</code>

※username:xxx、password:xxxでログインできます。（api/routes/auth.js）
![login.PNG](https://qiita-image-store.s3.amazonaws.com/0/272803/a9880c36-7fa1-bdec-62e1-ddfc5ce6a025.png)

# 最後に

いかがでしたでしょうか。
不明点等あれば、コメントにお願いいたします！