大急ぎでAPI＋SPA構成のアプリを立ち上げる（Nuxt.js＆簡易認証編）

# 前回の記事

[大急ぎでAPI＋SPA構成のアプリを立ち上げる（Spring Boot＆Heroku編）](https://qiita.com/meadow_sage/items/0245bc744be401b4a5d1)

# あらすじ

こんなの（下図）を明日までに準備することになり、バックエンドとDBの構築までを終わらせました。
今回はフロントエンドを何とかします。

![Picture2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/7104511f-4088-ef7b-aad5-f555f8108d27.png)

# Nuxt.jsについて

[Nuxt.js](https://nuxtjs.org/)はフロントエンドアプリを構築するためのフレームワークです。
Vue.jsの知識は必要になりますが、最初からガッツリ出来上がったものが手に入るので、急いでいる時にはもってこいです。

# 環境

**OS**

- macOS Catalina

**ツール・ソフトウェア**

- Node.js v10.15.3
- npm v6.14.8
- WebStorm 2020.2.4

（※バックエンド・Heroku関連は前回記事に記載）

# 雛形を作る

[Get Started](https://nuxtjs.org/docs/2.x/get-started/installation)に従い、雛形を作ります。
このコマンドを実行するとカレントディレクトリにソースが生成されるため、あらかじめ開発用のディレクトリに移動しておきます。

```bash
npx create-nuxt-app my-rapid-app-front
```

生成中にいろいろ聞かれますが、落ち着いて答えていきましょう。

**■Project name, Project description, Author name**

何でもOKです。適当に入れます。

**■package manager**

yarn か npm の２択です。
長い目で見ればyarnがオススメですが、未インストールならnpmでもOKかと。

**■UI framework, custom server framework, Nuxt.js modules, linting tools, test framework**

たくさんあって迷っちゃいますね。**全部Noneです。**
Lintツールくらいは入れておいても損はないのですが、今回の記事では取り上げません。

**■rendering mode**

サーバサイドレンダリング（SSR）かSPAの２択です。今回はSPA。

![Screen Shot 2020-12-11 at 20.45.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/0a3fa75f-5451-2d9a-d1f4-42295e2b5e3d.png)

できました。
早速プロジェクトを開いてみましょう。

![Screen Shot 2020-12-11 at 20.50.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/848641ba-9794-87c8-adcb-164220258418.png)

何から何までご用意されています。最高ですね。

# 動作確認

「nuxt dev」コマンドを実行して、ローカルで開発用サーバを起動します。
このときホットリロードが有効になるため、コードを修正すると即時反映されます。

```bash
npm run dev
```

起動したら、ブラウザで`http://localhost:3000`を開きます。

![Screen Shot 2020-12-12 at 16.38.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/5744217e-cfd8-7230-5f6e-48c190aeac55.png)

適当に入れたメッセージが表示されてて恥ずかしいので、さっさと直していきましょう。

# APIサーバと接続する（バックエンド側）

早速APIを叩いていきたいところですが、先にバックエンド側に**CORS**の設定を入れます。
どこから飛んできたリクエストを許容するか、あらかじめ決めておく必要があるわけです。

▼設定しないとこんなエラーが出ます
![Screen Shot 2020-12-11 at 22.47.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/08c846ef-3c66-9939-ce21-3677d0b8866b.png)

`DemoApplication.java`に設定を追記したら、Herokuにpush。

```java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    // 以下を追記
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                // localhostとフロントエンドのドメイン（予定）に対し、"/api/"で始まるURIへのリクエストを許可
                // 末尾に/を入れると機能しないので注意
                registry.addMapping("/api/**")
                        .allowedOrigins(
                                "http://localhost:3000",
                                "https://my-rapid-demo-front.herokuapp.com"
                        );
            }
        };
    }
}
```

```bash
git add .
git commit -am "cors setting"
git push heroku master
```

これで準備完了です。

# APIサーバと接続する（フロントエンド側）

ローカルからの接続先は`http://localhost:8080`、Herokuからは`https://***.herokuapp.com`にしたいので、環境変数で制御できるように設定を入れます。
`nuxt.config.js`を開き、末尾に以下を追加してください。

```javascript
env: {
    baseUrl: process.env.BASE_URL || 'http://localhost:8080'
}
```

これにより、変数`process.env.baseUrl`に対し、環境変数`BASE_URL`が設定されていればその値が、されていなければ`http://localhost:8080`が格納されるようになります。

続いて`pages/index.vue`を開き、`<template>`と`<script>`をごそっと編集。
`<style>`はとりあえずそのまま。

```vue
<template>
  <div class="container">
    <ul>
      <li v-for="employee in employees">
        <span>{{ employee.name }} [{{ employee.department.name }}]</span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      employees: []
    }
  },
  async created() {
    const response = await fetch(`${process.env.baseUrl}/api/employees`)
    if (response.ok) {
      this.employees = await response.json()
    } else {
      console.error(response.statusText)
    }
  }
}
</script>
```

これがトップページになります。中身をざっくり解説すると、

- ページのインスタンスができたタイミング（created）で、Employee一覧取得APIを実行
- 変数（date）の`employees`にレスポンスボディを格納
- `v-for`を使い、`employees`の要素数だけ`<li>`タグを生成

という流れです。

画面を再度開き、以下のようになっていれば接続はOKです。

![Screen Shot 2020-12-12 at 14.52.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/5c7fdfd2-d25e-58c5-8b31-7e0af5275af3.png)

殺風景ですが、とりあえずヨシ。

# デプロイする

静的ビルドしてNetlify等に置くだけでもOKなのですが、今回はフロントエンドもHeroku上で動かします。
[公式のガイド](https://ja.nuxtjs.org/faq/heroku-deployment/)を参考に進めていきましょう。

まず、Heroku用の設定ファイルを作ります。
ファイル名は「**Procfile**」。ここに起動時のコマンドを設定します。

```
echo 'web: nuxt start' > Procfile
```

プロジェクト直下にProcfileができたことを確認したら、Herokuアプリケーションを作成。

```bash
heroku create my-rapid-demo-front
```

Herokuに設定を入れます。上２つがサーバの動作に必要な設定、
下が「process.env.baseUrl」に与えるための環境変数です。

```bash
heroku config:set HOST=0.0.0.0 -a 【アプリ名】
heroku config:set NODE_ENV=production -a 【アプリ名】
heroku config:set BASE_URL=https://【バックエンドのアプリ名】.herokuapp.com -a 【アプリ名】
```

ここで注意なのですが、npm／yarn使用時に生成される`package-lock.json`／`yarn.lock`をコミットすると、ビルドに失敗してしまいます。
`.gitignore`に追記して、コミット対象外になるようにしておきます。 

```bash
echo "package-lock.json" >> .gitignore
echo "yarn.lock" >> .gitignore
```

コードをpush。

```bash
git add .
git commit -am "initial commit"
git push heroku master
```

pushが完了したら、`https://【アプリ名】.herokuapp.com/`を開いてみましょう。
起動まで少し時間がかかるかもしれません。

![Screen Shot 2020-12-12 at 14.56.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/b4e0ed98-4728-2947-12b2-e1d4774c0e56.png)

無事に成功しました。
しばらく待っても何も出てこない場合、git push時の出力、Herokuのログ、デベロッパーツールのコンソールを確認し、エラーが起きていないか確認してください。

# Done!

これで最低限の構築は完了です。
あとは必要に応じてAPIを増やしたり、テンプレートやUIフレームワークを見繕ってフロントエンドの見栄えを調整したりしていきましょう。
下のスクリーンショットは[Vuetify](https://vuetifyjs.com/ja/)で体裁を整えた画面です。

![Screen Shot 2020-12-12 at 16.34.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/c457ff4f-09b4-5a5a-3cdb-9c639d188ec8.png)

# 簡易認証を入れる（おまけ）

さて、開発用とはいえAPIと画面が公開されているのは気になる…
ということで、最低限の認証機構を入れてみます。

1. フロントエンドとバックエンドで共通のAPIキーを持たせてチェック
1. 画面を開くときにユーザ名・パスワードの入力を求める

なお、これを導入したとてAPIキーが流出すればアクセスし放題なので、言うまでもなく気休めです。くれぐれも本番運用にはお使いになりませんよう…

## 1. APIキーのチェック

まずはバックエンド側から。
`application.properties`に`API_KEY=dummy`という行を追加します。
この値は環境変数で上書きされるため、環境変数があればその値、なければ「dummy」が適用されます。

続いて、パッケージ`interceptor`を新しく切り、中に以下のクラスを作成します。

```java
package com.example.demo.interceptor;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class ApiInterceptor implements HandlerInterceptor {

    @Value("${API_KEY}")
    private String API_KEY;

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler) throws IOException {

        // preflightの場合はスキップ
        if (request.getMethod().equals(HttpMethod.OPTIONS.name())) return true;

        String authorizationHeader = request.getHeader("Authorization");

        if (authorizationHeader == null || !authorizationHeader.equals("Bearer " + API_KEY)) {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
            return false;
        }

        return true;
    }
}
```

このpreHandleというメソッドは、リクエストがControllerに渡される前に実行されます。
Headerに設定された文字列を読み取り、キーが一致しなかったら401エラーを返して後続処理が行われないようにしています。

続いて、`DemoApplication.java`を開き、先ほど作った`corsConfigurer`をまるっと書き換えます。

```java
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                        .allowedOrigins(
                                "http://localhost:3000",
                                "https://my-rapid-demo-front.herokuapp.com"
                        );
            }

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(apiInterceptor)
                        .addPathPatterns("/api/**");
            }
        };
    }
```

`addInterceptors`メソッドを実装することで、先ほどのInterceptorが`/api/`以下のリクエストに適用されるようになります。

続いてフロントエンド側。
先ほどのbaseUrlと同様に、`nuxt.config.js`に変数を追加します。

```javascript
env: {
    baseUrl: process.env.BASE_URL || 'http://localhost:8080',
    apiKey: process.env.API_KEY || 'dummy'
}
```

そして、リクエスト送信時にヘッダを追加します。

```javascript
    const response = await fetch(`${process.env.baseUrl}/api/employees`, {
      headers: {
        Authorization: `Bearer ${process.env.apiKey}`
      }
    })
```

これで実装は完了です。
試しに"dummy"を別の文字列に変えてみると、401エラーが返ってくるかと思います。

Heroku上にはもう少し長めの文字列を付与してあげます。
なお、本格的に認証を行うのであればただの文字列ではなく、JWTトークンなどを検討するのが良いかと思います。

```bash
heroku config:set API_KEY=【キー文字列】 -a 【フロントエンドアプリ名】
heroku config:set API_KEY=【キー文字列】 -a 【バックエンドアプリ名】
```

## 2. 画面に認証ダイアログ

こちらはNuxt.js用の神モジュールを使わせて頂きます。

[nuxt-basic-auth-module](https://github.com/potato4d/nuxt-basic-auth-module)

```bash
npm install nuxt-basic-auth-module
```

```javascript
  modules: [
    'nuxt-basic-auth-module'
  ],
  basic: {
    name: process.env.BASIC_AUTH_USER || 'user',
    pass: process.env.BASIC_AUTH_PASSWORD || 'password',
    enabled: true
  },
```

![Screen Shot 2020-12-12 at 21.03.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255659/850fe6ed-b60f-47aa-9580-c28621a9f89a.png)

デプロイ後に画面を開き直すと、ダイアログが出てきました。
これですべて完了です。

# おわりに

ここまで読んでいただきありがとうございました。
リポジトリのリンクを置いておきますので、お急ぎの際はご自由にお使いください。

# GitHub

[my-rapid-demo](https://github.com/meadowsage/my-rapid-demo)
[my-rapid-demo-front](https://github.com/meadowsage/my-rapid-demo-front)

# 参考ページ

- [spring.io / Enabling Cross Origin Requests for a RESTful Web Service](https://spring.io/guides/gs/rest-service-cors/)
- [docs.spring.io / Web on Servlet Stack](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html)
- [Heroku Dev Center / CLI からのアプリの作成](https://devcenter.heroku.com/ja/articles/creating-apps)
- [Heroku Dev Center / Git を使用したデプロイ](https://devcenter.heroku.com/ja/articles/git)
- [Heroku Help / Why is my Node.js build failing because of conflicting lock files?](https://help.heroku.com/0KU2EM53/why-is-my-node-js-build-failing-because-of-conflicting-lock-files)
- [Vuetify / Nuxt install](https://vuetifyjs.com/en/getting-started/installation/#nuxt-install)
- [Vuetify / Wireframes](https://vuetifyjs.com/en/getting-started/wireframes/)