2019.01.12

Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう (16) エラーハンドリング Part.2


この連載記事では、フロントエンドに Vue.js + Vue Router + Vuex とサーバーサイドに Laravel を使用したシングルページ Web アプリケーションの開発方法を紹介します。実際に写真共有アプリを開発する手順を通して SPA 開発のエッセンスを学んでいただけるように書いていきます。

今回のチュートリアルで扱うツールなどのバージョンは以下の通りです。

Node npm Vue.js Vue Router Vuex PHP Laravel
10.7.0 6.4.1 2.5.21 3.0.2 3.0.1 7.2.10 5.7.19

この章では最後の仕上げとして、以下2点のエラーハンドリングを追加します。

  • 認証エラー
  • 404 エラー

認証エラー

認証エラーは、サイトの閲覧中にセッションが切れるなどして認証していない状態で写真投稿やいいねなど認証の必要な API にアクセスしたときに起こり得ます。

エラーハンドリングの仕組みとしては、システムエラーと同じ方法を採ります。つまり、

  1. API 通信が完了したあとに失敗であればステータスコードを error ストアに格納する。
  2. ルートコンポーネントが error ストアに格納されるコードを監視し、コード番号によって必要な処理を行う。

1 および 2 の監視処理はシステムエラーで実装済みなので、基本的には 2 の後半で認証エラーの分岐を増やせば OK です。

トークンリフレッシュ API

認証エラーの場合はログインページへ移動して再度ログインをさせようと考えたのですが、それだけでは課題がありました。ログインページへ移動したあと CSRF トークンがうまくリフレッシュされず、再ログインできなかったのです。

そこで、トークンリフレッシュ用の API を作ってしまいます。

api.php
// トークンリフレッシュ
Route::get('/reflesh-token', function (Illuminate\Http\Request $request) {
    $request->session()->regenerateToken();

    return response()->json();
});

レスポンスコード定義

util.js にレスポンスコードを定義します。

util.js
export const UNAUTHORIZED = 419

認証切れの場合のレスポンスコードは 419 でした。

ちなみに 419 は正式には定義されていないコードで、Laravel が独自で使用しています。

ルートコンポーネント

App.vue を編集します。

まずレスポンスコード定義 UNAUTHORIZED をインポートします。

App.vue
import { UNAUTHORIZED, INTERNAL_SERVER_ERROR } from './util'

そして watch 内の errorCode のハンドラを以下のように編集してください。

App.vue
errorCode: {
  async handler (val) {
    if (val === INTERNAL_SERVER_ERROR) {
      this.$router.push('/500')
    } else if (val === UNAUTHORIZED) {
      // トークンをリフレッシュ
      await axios.get('/api/refresh-token')
      // ストアのuserをクリア
      this.$store.commit('auth/setUser', null)
      // ログイン画面へ
      this.$router.push('/login')
    }
  },
  immediate: true
},

ログインページに移動する前にストアの user をクリアしておかないとログインページにアクセスできないので注意しましょう。

これで認証エラー処理は完了です。

404 エラー

404 エラーとして対処すべきケースには2種類あります。

  • API から 404 NOT FOUND が返却される。
  • ブラウザで存在しないパスにアクセスされる。

レスポンスコード定義

まずは util.js にレスポンスコードを定義します。

util.js
export const NOT_FOUND = 404

エラーページ作成

404 エラーページを作成しましょう。
resources/js/pages/errors/NotFound.vue を追加してください。

NotFound.vue
<template>
  <p>お探しのページは見つかりませんでした。</p>
</template>

続いてルート定義に <NotFound> を追加しましょう。

route.js
import NotFound from './pages/errors/NotFound.vue'

/* 中略 */

const routes = [
  /* 中略 */
  {
    path: '*',
    component: NotFound
  }
]

path に「任意」を意味する *(アスタリスク)を指定しているのがポイントです。
これにより、定義されたルート以外のパスでのアクセスは <NotFound> が表示されます。

ルートコンポーネント

API から 404 が返された場合に備えてルートコンポーネント <App> も編集します。

まずレスポンスコード定義 NOT_FOUND をインポートします。

App.vue
import { NOT_FOUND, UNAUTHORIZED, INTERNAL_SERVER_ERROR } from './util'

そして watch 内の errorCode のハンドラを以下のように編集してください。

App.vue
errorCode: {
  async handler (val) {
    if (val === INTERNAL_SERVER_ERROR) {
      this.$router.push('/500')
    } else if (val === UNAUTHORIZED) {
      await axios.get('/api/refresh-token')
      this.$store.commit('auth/setUser', null)
      this.$router.push('/login')
    } else if (val === NOT_FOUND) {
      this.$router.push('/not-found')
    }
  },
  immediate: true
},

NOT_FOUND の場合の分岐を追加しました。

👾 👾 👾

この章はこれでおしまいです。
本章までのソースコードはリポジトリの chapter-16 ブランチに置いてあります。

そしてこれでこのチュートリアルもおしまいです。
フロントエンドに Vue + Vue Router + Vuex とサーバーサイドに Laravel を組み合わせて小さなアプリケーションを作ることができました 😆 👏 🎉

いかがだったでしょうか?
ここで習得したパターンを仕事や趣味でさらに応用して、より大きなアプリケーションを作れるようになる、そしてエンジニアとしてもステップアップするきっかけになれば幸いです。

最後に

アプリをネットに公開する

作ったアプリケーションをネットに公開してみたい場合は、Heroku が手軽なのでオススメです。

公開方法は以下の記事を参考にしてください。
入門Laravelチュートリアル (11) ToDoアプリをHerokuにデプロイする

また、公開する場合は以下のコマンドでフロントエンドをビルドしましょう。

$ npm run production

watch と異なりファイルの圧縮が行われるので、公開環境にはこちらのビルドを用います。

追加機能案

チュートリアルでは取り上げられませんでしたが、今回作った写真共有アプリをブラッシュアップするならこんな機能がいいかも、というアイディアを載せておきます。

  • 投稿者ごとの写真一覧ページ
  • いいねした写真の一覧ページ
  • 気に入った投稿者をフォローする
  • 写真へのタグ付け
  • 写真の削除
  • 画像のロードが遅いので、投稿されたときにサイズが小さいサムネイル版を自動生成する
  • 画像のロードが遅いので、Cloud Front などの CDN を使うといいかも

ここに挙げた機能や自分で思いついた機能などを追加してみるのも良い学習になると思います。

関連記事

連載記事(全16回)

その他


<!-- Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com --><!-- License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->