前回の記事
大急ぎでAPI+SPA構成のアプリを立ち上げる(Spring Boot&Heroku編)
あらすじ
こんなの(下図)を明日までに準備することになり、バックエンドとDBの構築までを終わらせました。
今回はフロントエンドを何とかします。
Nuxt.jsについて
Nuxt.jsはフロントエンドアプリを構築するためのフレームワークです。
Vue.jsの知識は必要になりますが、最初からガッツリ出来上がったものが手に入るので、急いでいる時にはもってこいです。
環境
OS
- macOS Catalina
ツール・ソフトウェア
- Node.js v10.15.3
- npm v6.14.8
- WebStorm 2020.2.4
(※バックエンド・Heroku関連は前回記事に記載)
雛形を作る
Get Startedに従い、雛形を作ります。
このコマンドを実行するとカレントディレクトリにソースが生成されるため、あらかじめ開発用のディレクトリに移動しておきます。
npx create-nuxt-app my-rapid-app-front
生成中にいろいろ聞かれますが、落ち着いて答えていきましょう。
■Project name, Project description, Author name
何でもOKです。適当に入れます。
■package manager
yarn か npm の2択です。
長い目で見ればyarnがオススメですが、未インストールならnpmでもOKかと。
■UI framework, custom server framework, Nuxt.js modules, linting tools, test framework
たくさんあって迷っちゃいますね。全部Noneです。
Lintツールくらいは入れておいても損はないのですが、今回の記事では取り上げません。
■rendering mode
サーバサイドレンダリング(SSR)かSPAの2択です。今回はSPA。
できました。
早速プロジェクトを開いてみましょう。
何から何までご用意されています。最高ですね。
動作確認
「nuxt dev」コマンドを実行して、ローカルで開発用サーバを起動します。
このときホットリロードが有効になるため、コードを修正すると即時反映されます。
npm run dev
起動したら、ブラウザでhttp://localhost:3000
を開きます。
適当に入れたメッセージが表示されてて恥ずかしいので、さっさと直していきましょう。
APIサーバと接続する(バックエンド側)
早速APIを叩いていきたいところですが、先にバックエンド側にCORSの設定を入れます。
どこから飛んできたリクエストを許容するか、あらかじめ決めておく必要があるわけです。
DemoApplication.java
に設定を追記したら、Herokuにpush。
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"
);
}
};
}
}
git add .
git commit -am "cors setting"
git push heroku master
これで準備完了です。
APIサーバと接続する(フロントエンド側)
ローカルからの接続先はhttp://localhost:8080
、Herokuからはhttps://***.herokuapp.com
にしたいので、環境変数で制御できるように設定を入れます。
nuxt.config.js
を開き、末尾に以下を追加してください。
env: {
baseUrl: process.env.BASE_URL || 'http://localhost:8080'
}
これにより、変数process.env.baseUrl
に対し、環境変数BASE_URL
が設定されていればその値が、されていなければhttp://localhost:8080
が格納されるようになります。
続いてpages/index.vue
を開き、<template>
と<script>
をごそっと編集。
<style>
はとりあえずそのまま。
<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です。
殺風景ですが、とりあえずヨシ。
デプロイする
静的ビルドしてNetlify等に置くだけでもOKなのですが、今回はフロントエンドもHeroku上で動かします。
公式のガイドを参考に進めていきましょう。
まず、Heroku用の設定ファイルを作ります。
ファイル名は「Procfile」。ここに起動時のコマンドを設定します。
echo 'web: nuxt start' > Procfile
プロジェクト直下にProcfileができたことを確認したら、Herokuアプリケーションを作成。
heroku create my-rapid-demo-front
Herokuに設定を入れます。上2つがサーバの動作に必要な設定、
下が「process.env.baseUrl」に与えるための環境変数です。
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
に追記して、コミット対象外になるようにしておきます。
echo "package-lock.json" >> .gitignore
echo "yarn.lock" >> .gitignore
コードをpush。
git add .
git commit -am "initial commit"
git push heroku master
pushが完了したら、https://【アプリ名】.herokuapp.com/
を開いてみましょう。
起動まで少し時間がかかるかもしれません。
無事に成功しました。
しばらく待っても何も出てこない場合、git push時の出力、Herokuのログ、デベロッパーツールのコンソールを確認し、エラーが起きていないか確認してください。
Done!
これで最低限の構築は完了です。
あとは必要に応じてAPIを増やしたり、テンプレートやUIフレームワークを見繕ってフロントエンドの見栄えを調整したりしていきましょう。
下のスクリーンショットはVuetifyで体裁を整えた画面です。
簡易認証を入れる(おまけ)
さて、開発用とはいえAPIと画面が公開されているのは気になる…
ということで、最低限の認証機構を入れてみます。
- フロントエンドとバックエンドで共通のAPIキーを持たせてチェック
- 画面を開くときにユーザ名・パスワードの入力を求める
なお、これを導入したとてAPIキーが流出すればアクセスし放題なので、言うまでもなく気休めです。くれぐれも本番運用にはお使いになりませんよう…
1. APIキーのチェック
まずはバックエンド側から。
application.properties
にAPI_KEY=dummy
という行を追加します。
この値は環境変数で上書きされるため、環境変数があればその値、なければ「dummy」が適用されます。
続いて、パッケージinterceptor
を新しく切り、中に以下のクラスを作成します。
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
をまるっと書き換えます。
@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
に変数を追加します。
env: {
baseUrl: process.env.BASE_URL || 'http://localhost:8080',
apiKey: process.env.API_KEY || 'dummy'
}
そして、リクエスト送信時にヘッダを追加します。
const response = await fetch(`${process.env.baseUrl}/api/employees`, {
headers: {
Authorization: `Bearer ${process.env.apiKey}`
}
})
これで実装は完了です。
試しに"dummy"を別の文字列に変えてみると、401エラーが返ってくるかと思います。
Heroku上にはもう少し長めの文字列を付与してあげます。
なお、本格的に認証を行うのであればただの文字列ではなく、JWTトークンなどを検討するのが良いかと思います。
heroku config:set API_KEY=【キー文字列】 -a 【フロントエンドアプリ名】
heroku config:set API_KEY=【キー文字列】 -a 【バックエンドアプリ名】
2. 画面に認証ダイアログ
こちらはNuxt.js用の神モジュールを使わせて頂きます。
npm install nuxt-basic-auth-module
modules: [
'nuxt-basic-auth-module'
],
basic: {
name: process.env.BASIC_AUTH_USER || 'user',
pass: process.env.BASIC_AUTH_PASSWORD || 'password',
enabled: true
},
デプロイ後に画面を開き直すと、ダイアログが出てきました。
これですべて完了です。
おわりに
ここまで読んでいただきありがとうございました。
リポジトリのリンクを置いておきますので、お急ぎの際はご自由にお使いください。
GitHub
my-rapid-demo
my-rapid-demo-front