この連載記事では、フロントエンドに Vue.js + Vue Router + Vuex とサーバーサイドに Laravel を使用したシングルページ Web アプリケーションの開発方法を紹介します。実際に写真共有アプリを開発する手順を通して SPA 開発のエッセンスを学ぶことができます。
今回のチュートリアルで扱うツールなどのバージョンは以下の通りです。
Node | npm | Vue.js | Vue Router | Vuex | PHP | Laravel |
---|---|---|---|---|---|---|
10.15 | 6.4 | 2.6 | 3.1 | 3.1 | 7.4 | 6.9 |
この章では、アプリケーション全体の設計を考えます。
まず SPA の特徴について見ていったあとに、データベースと URL を設計しましょう。
SPA のアーキテクチャ
通信パターンの特徴
シングルページアプリケーション(Single Page Application)とシングルページではない「普通の」Web アプリケーション(シングルページと対比してマルチページアプリケーションと呼ぶことにします)はブラウザ - サーバ間の通信パターンに大きな違いがあります。
まず、マルチページアプリケーションの通信は以下のようなパターンになります。
ブラウザの画面要求(GET)またはデータ送信(POST)に対してサーバは HTML を返却し、ブラウザはその HTML を描画して画面を構築します。マルチページアプリケーションの通信パターンはこのワンセットの繰り返しです。
これに対してシングルページアプリケーションの通信パターンは以下の通りです。
ブラウザは最初に画面を要求し、サーバは HTML を返却します。ブラウザが画面を構築した後は、Ajax でデータの要求やデータの送信が行われ、サーバからは JSON データが返却されます。ブラウザは受け取った JSON データを使用し JavaScript によって画面の一部を書き換えます。画面全体をサーバから受け取るのは最初だけで、そのあとは Ajax によるリクエスト〜 JSON データのレスポンスがワンセットで繰り返される通信パターンになります。
SPA での「画面遷移」
前述の通り、SPA ではサーバから返却された HTML で画面全体を構築し直すのではなく、JSON データを元に画面の一部を書き換えます。そのため、本来の意味での「画面遷移」は発生しません。だからこそ「シングルページ」と呼ばれるわけですね。ただアプリケーションの使い勝手の観点から、SPA でも画面が移り変わったような見た目が必要な場合もあります。
SPA で画面遷移を表現する場合、主要なコンテンツを持つ領域(例えばヘッダーとフッター以外の部分)を書き換えることで画面遷移が遷移したように見せるテクニックを用います。URL に対応する HTML 部品をあらかじめ決めておいて、URL の変化に応じて対応する部品でコンテンツ部分を切り替える手法が一般的です。
Vue Router や React Router などのルーターライブラリはそれぞれのフレームワークの内部で上記の実装パターンを実現するために開発されています。このチュートリアルでは Vue Router の使い方を紹介します。
機能一覧
データベースと URL を設計するために、写真共有アプリの機能を洗い出します。
- 写真の一覧を表示する
- 写真を投稿する(会員のみ)
- 写真にいいねを付ける(会員のみ)
- 写真からいいねを外す(会員のみ)
- 写真にコメントを追加する(会員のみ)
- 写真に付けられたいいねの数を表示する
- 写真に付けられたコメントを表示する
- 会員登録する
- ログインする
- ログアウトする(会員のみ)
ざっとこんなところでしょう。
ではこの機能を元にデータベースと URL の設計に移ります。
データベース設計
どのようなテーブルが必要か
まず認証機能があるので、ユーザーテーブルが必要でしょう。すでに Laravel でアプリを作ったことがある方はご存知かと思いますが、Laravel はデフォルトでユーザーテーブルの定義(マイグレーションファイル)が用意されているので、今回はそれらをそのまま使用します。
次に写真共有アプリなので写真を表すテーブルも必要そうです。会員登録したユーザーが写真を投稿できるので、ユーザーが複数の写真を所有する関係が作られます。言い換えると、ユーザーテーブルと写真テーブルは 1:N のリレーションで紐づきます。
さらにいいねを表現するテーブルとコメントを表現するテーブルが必要です。
いいねテーブルについては、写真テーブルにいいね数を表すカラムを持たせるパターンも考えられなくもないです。ただ複数のユーザーが同時に同じ写真にいいねした(または解除した)とき、つまり同じ写真行に同時に更新がかかった場合の制御が複雑であることを考慮すると別テーブルに切り出す方が賢い選択だと思います。別テーブルにしておけば、今回は作りませんが、自分がいいねしたアイテムの一覧を見せたいといった仕様にも対応できるでしょう。
コメントテーブルですが、こちらは写真に対してコメントが複数ついてさらにコメント自体が入力内容を持つ必要があるので、別テーブルにするしか実装の選択肢はないでしょう。
いいねテーブルとコメントテーブルは、どのユーザーが、どの写真にいいねまたはコメントしたかという情報を持つ必要があります。すなわち....
- ユーザーは複数の写真にいいねを付けられる → ユーザーといいねは 1:N で紐づく
- 一枚の写真には複数のいいねが付く → 写真といいねは 1:N で紐づく
- ユーザーはコメントを複数追加できる → ユーザーとコメントは 1:N で紐づく
- 一枚の写真には複数のコメントが付く → 写真とコメントは 1:N で紐づく
テーブル定義
では一つずつテーブルのカラムを定義していきましょう。
今回はデータベースに PostgreSQL を使用するので、自動採番列は SERIAL 型になります。
photos テーブル
写真テーブルはユーザーテーブルへの外部キーのほか、ファイル名を表す filename
カラムを持ちます。ファイル自体は AWS S3 に格納します。
もう一つのポイントは、id
を文字列型にした点です。パクった参考にした Unsplash を真似してランダムな文字列を主キーにします。Laravel で主キーを自動採番以外の型を使用する方法は後の章で紹介します。
列名 | 型 | PRIMARY | UNIQUE | NOT NULL | FOREIGN |
---|---|---|---|---|---|
id | VARCHAR(255) | ||||
user_id | INTEGER | users(id) | |||
filename | VARCHAR(255) | ||||
created_at | TIMESTAMP | ||||
updated_at | TIMESTAMP |
comments テーブル
コメントテーブルはユーザーと写真テーブルへの外部キーに加えてコメント内容を表す content
カラムを持ちます。
列名 | 型 | PRIMARY | UNIQUE | NOT NULL | FOREIGN |
---|---|---|---|---|---|
id | SERIAL | ||||
photo_id | VARCHAR(255) | photos(id) | |||
user_id | INTEGER | users(id) | |||
content | TEXT | ||||
created_at | TIMESTAMP | ||||
updated_at | TIMESTAMP |
likes テーブル
いいねテーブルは実質ユーザーと写真テーブルへの外部キーのみを持ちます。
列名 | 型 | PRIMARY | UNIQUE | NOT NULL | FOREIGN |
---|---|---|---|---|---|
id | SERIAL | ||||
photo_id | VARCHAR(255) | photos(id) | |||
user_id | INTEGER | users(id) | |||
created_at | TIMESTAMP | ||||
updated_at | TIMESTAMP |
users テーブル
ユーザーテーブルは前述の通り Laravel のデフォルトをそのまま使用します。
email_verified_at
など今回は使わないテーブルもありますが、そのままにしておきます。
列名 | 型 | PRIMARY | UNIQUE | NOT NULL | FOREIGN |
---|---|---|---|---|---|
id | SERIAL | ||||
name | VARCHAR(255) | ||||
VARCHAR(255) | |||||
password | VARCHAR(255) | ||||
remember_token | VARCHAR(100) | ||||
email_verified_at | TIMESTAMP | ||||
created_at | TIMESTAMP | ||||
updated_at | TIMESTAMP |
ER図
以下がここまでの内容をまとめた ER 図です。
URL 設計
Web API の URL 設計
SPA における HTTP 通信パターンの図をもう一度見てみましょう。
上の図にあるように Ajax でのデータ要求 / 送信を受け取って JSON データを返却するサーバサイドのプログラムを Web API(または単に API)と呼びます。SPA の場合はこの Web API についても URL を設計する必要があります。
URL 設計における Web API とマルチページアプリケーションの大きな違いは、HTTP メソッドにあります。マルチページアプリケーションの場合は HTTP メソッドとして GET と POST しか用いられませんが、Web API では GET、POST の他に PUT、PATCH、DELETE も使用できます。
マルチページアプリケーションはサーバへのリクエスト発行をアンカーリンク(<a>
)とフォーム送信(<form>
)に頼っています。アンカーリンクは GET リクエストを発行しますし、フォーム要素の method
属性は GET と POST しかサポートしていません。
これに対し Web API は Ajax によってリクエストを発行するので上記の制約はありません。HTTP の仕様として定義されているメソッドはすべて使用できるというわけです。具体的には以下の5種類の HTTP メソッドを使うことができます(参考)。
メソッド | 意味 |
---|---|
GET | リソースの取得 |
POST | リソースの新規作成 |
PATCH | リソースの一部変更 |
PUT | リソースの置き換え |
DELETE | リソースの削除 |
「リソース」というのは少し抽象的な表現ですが、「ユーザー」「写真」「商品」「ツイート」などリクエストにより操作したい対象のデータを指します。
使用できる HTTP メソッドの違いは URL 設計にも影響します。Web API の場合は使える HTTP メソッドをフル活用した設計の考え方をするのが一般的です。
具体例で考えてみましょう。あるアイテムの新規作成、編集、削除の URL を設計するとします。マルチページアプリケーション流に考えると以下のようになるでしょう。
POST /items/create # 新規作成
POST /items/update # 編集
POST /items/delete # 削除
データの処理を依頼するためのメソッドが POST しかないので、ざっくり画面を要求するだけの時は GET、それ以外のデータ処理を要求するときは POST として、データ処理の要求内容は URL のパスで表現するという考え方です。
このケースを Web API 流に考えると以下のようになります。
POST /items # 新規作成
PATCH /items # 編集
DELETE /items # 削除
「items を DELETE する」のように、URL が目的語で HTTP メソッドが動詞という形式になっています。データ処理の要求内容を HTTP メソッドで表現できるからこそ可能な設計ですね。
URL 一覧
API
上記の説明を踏まえて、写真共有アプリで必要な Web API の URL は以下の通りです。
マークがついている URL は認証済みでないとアクセスできません。
URL | メソッド | 認証 | 内容 |
---|---|---|---|
/api/photos | GET | 写真 一覧取得 | |
/api/photos | POST | 写真 投稿 | |
/api/photos/{写真ID} | GET | 写真 詳細取得 | |
/api/photos/{写真ID}/like | PUT | 写真 いいね追加 | |
/api/photos/{写真ID}/like | DELETE | 写真 いいね解除 | |
/api/photos/{写真ID}/comments | POST | 写真 コメント追加 | |
/api/register | POST | 会員登録 | |
/api/login | POST | ログイン | |
/api/logout | POST | ログアウト | |
/api/user | GET | 認証ユーザー取得 |
- 分かりやすいように Web API の頭にはすべて
/api
を付けています。 - いいね数やコメント内容は写真一覧または詳細データに含みます。
- いいねの付け外しを
PUT
とDELETE
で表現しました。外すのがDELETE
なのはいいとして付けるのがなぜPUT
かというと、まず同じ人が同じ写真にいいねを何個も付けることは現実的な仕様として考えにくいので新規作成を意味するPOST
は不自然だと思いました。またいいねは ON か OFF の状態しかなく、一部だけ変更することはないのでPATCH
も不自然です。仮にいいねを付けるリクエストを同じ写真に繰り返し行っても、置き換えを表すPUT
であれば意味的に一個しかつかなそうな感じがするのでPUT
を採用しました。
POST
がいいか PUT
がいいかというような議論はあくまで URL 設計を単体で見たときに分かりやすいか、美しいかを言っているにすぎません。HTTP メソッドの選択などの URL 設計とプログラムの実装内容は関係がないことに注意してください。結局 URL とそれに対応するプログラムはルート定義で恣意的に決めるのですから。論理的に不自然な URL を選択したからといってプログラムが正しく動作するかは別問題です。そこのところを混同しないようにしたうえで、もちろんエンジニアたるもの分かりやすく論理的に美しく自然な URL を設計するよう知恵を絞りましょう。
API 以外
API 以外にサーバサイドで用意する必要がある URL は以下の通りです。
URL | メソッド | 認証 | 内容 |
---|---|---|---|
/ | GET | 最初に HTML を返却する | |
/photos/{写真ID}/download | GET | 写真ダウンロード |
フロントエンド
さて、最後に画面側の URL も載せておきます。
こちらはフロントエンド(Vue Router)で実現します。
URL | 内容 |
---|---|
/ | 写真一覧ページ |
/photos/{写真ID} | 写真詳細ページ |
/login | ログイン・会員登録ページ |
第2章はこれでおしまいです。
次の章からコードを書いていきます。
まずは SPA プロジェクトの作成から Vue Router の導入まで進みます。
関連記事
連載記事(全16回)
Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう
- (1) イントロダクション
- (2) アプリケーションの設計
- (3) SPA開発環境とVue Router
- (4) 認証API
- (5) 認証ページ
- (6) 認証機能とVuex
- (7) 認証機能とVuex Part.2
- (8) エラーハンドリング
- (9) 写真投稿API
- (10) 写真投稿フォーム
- (11) 写真一覧取得API
- (12) 写真一覧ページ
- (13) 写真詳細ページ
- (14) コメント投稿機能
- (15) いいね機能
- (16) エラーハンドリング Part.2