Phoenix + Reactでチャットアプリを作ろう(Part1)
Phoenixを使ったGroup・MessageのREST API作成
アプリ概要はPhoenix + Reactでチャットアプリを作ろう(Intro)参照。
ソースはGithub参照。
以下のプロジェクト・フォルダ構成を前提とする。
mkdir chatter
cd chatter
本章(Part 1)ではPhoenixを使ってGroup一覧取得、Group作成、Group詳細とその紐づいたMessageの取得をできるJSON REST APIを完成させる。
以下の順番で作業を進める。
- プロジェクトファイル作成と初期設定:データは無いけど、サーバを起動できる
- Groupリソースの作成:Group作成、Group詳細取得、Group一覧取得ができる
- Messageリソースの作成:Group詳細取得時にそのGroupに紐づいたMessageがついてくる
- CORSの有効化:外部アプリからのリクエストを受け付けられるように設定
Part 2ではReact Hooksを使ったGroupの作成・参照画面を作成し、本章で作成したJSONデータと連結する。
プロジェクトファイル作成と初期設定
Phoenixにはファイルを自動生成してくれるいくつかのMix Tasksがあり、それをうまく使えばそれだけで初期設定がほとんどできてしまうのだ。
プロジェクト作成時はmix phx.new
を使えば一発で枠組みを作成できちゃいます。
mix phx.new api --app chatter --module Chatter --binary-id --no-html --no-webpack
オプションは、順番に
- 最初の引数はディレクトリ名。APIサーバとウェブアプリを別ディレクトリにするので、
api
にした。 --app
: アプリ名はchatter--module
: モジュール名は同じくChatter(先頭大文字)--binary-id
: 主キーにUUID型を使用--no-html
: Rest APIなので、HTMLは不必要--no-webpack
: 同じくJSもないので、JS用ビルド・起動ツールは不必要
タスクを走らせると、以下の通り必要なファイルを生成してくれる。
設定は/config
、アプリロジックは/lib
、テストは/test
に整理されている。
ファイル作成が完了するとFetch and install dependencies?
と聞かれるのでY
でインストールしよう。
インストールできたら、以下の通り、次のステップを教えてくれる。
以下のDB設定(デフォルトDBはPostgresでpostgresユーザ・postgresパスワード、 createdb権限有り)が開発環境と一致していることを確認してから、
DB作成。
cd apimix ecto.create
ecto.create
でプロジェクト名のデータベース(chatter_dev
とchatter_test
)を作ってくれる。
アプリの枠組みができたので、サーバを起動しよう。
mix phx.server
- サーバを
http://localhost:4000
に起動してくれる。(ちなみに、サーバを落としたい場合はcontrol C
2回で止められる。)
これでPhoenixアプリの起動に成功した!(404だけど。)
次はAPIのエンドポイントを追加しよう。
Groupのリソース作成
Introで説明した通り、Groupはチャットルームに該当する、複数のMessageを持つリソース。これがないと始まらないので、まずはGroupのリソースを作成しよう。
mix phx.gen.json
を使うとController、View(JSONの定義)、Context(サービス層)、データのSchema、データベースのmigrationなどに必要なファイルを自動生成してくれる。
mix phx.gen.json Chat Group groups name:string
- context名が
Chat
- schema名が
Group
- データベースのtable名が
groups
- Groupにnameという文字列フィールドを定義
重要なファイルは、group_view.ex
、group_controller.ex
、router.ex
、chat.ex
、group.ex
、2020xxxxxxxxxx_create_groups.exs
。順番に見ていこう。
group_view.ex
にはレスポンスのJSONが定義されている。現時点では変更不必要。
group_controller.ex
には5つのメソッドが自動生成される。今回使うindex
(一覧表示)、create
(新規作成)、show
(詳細表示)を残して、それ以外は削除しよう。
router.ex
では、リソースをapiのスコープに追加し、不必要なメソッドを除外ブロックexcept:
に追加する。
chat.ex
がContext(サービス層)。ここもまた必要なメソッドだけ残してあとは削除。
group.ex
はデータベース接続層。プロパティのフィルタリングやバリデーションなどを行う。現時点では変更不必要。
2020xxxxxxxxxx_create_groups.exs
(xxxは作成時のtimestamp)はデータベースのマイグレーションファイル。mix ecto.migrate
でテーブルの作成や修正がDBに適用される。現時点では変更不必要。
Group関連のファイルが整ったので、データベースにgroups
のテーブルを追加しよう。
mix ecto.migrate
ここでまたサーバを起動して/api/groups
に行くと、JSONが見れるはず。
mix phx.server
現時点ではまだデータが空なので、curlでいくつかのグループを作成しよう。
curl -H "Content-Type: application/json" -X POST -d '{"group": {"name": "first group"}}' http://localhost:4000/api/groups
もう1つ。。
curl -H "Content-Type: application/json" -X POST -d '{"group": {"name": "second group"}}' http://localhost:4000/api/groups
ブラウザでまた確認すると、
2件見えるはず。
idを使って詳細画面も確認してみよう。パスは/api/groups/:id
テストも確認しよう。
さっき、mix phx.gen
を実行した時に実装コードに加えてテストも生成してくれていたのだ。走らせてみよう。
mix test
実装から消したメソッドの分もテストがあるので、7件失敗する。
先程削除したメソッドに該当するテストを消すと、実装コードへの(メソッドごとの削除以外の)変更はしていないので、残った9件は全てパスするはず。
これでGroupの作成、一件取得、一覧取得ができるようになった!
ここまでのコードはP1_1_GENERATE_GROUP_RESOURCEブランチ参照
次に、Messageのリソースを追加しよう。
Messageのリソース作成
既存のGroupController#show/2でGroup詳細を取得した時に、紐づいたMessageが含まれるように修正しよう。
まずは既存のコントローラのテストを修正しよう。現時点ではshowのメソッドは”create group”のテストでしか使われていないので、新しく”show group”のテストを追加。レスポンスにメッセージの配列が含まれていることを確認しよう。
Contextのテストのget_group!/1のテストにもメッセージの配列が含まれていることを確認。
ここで、テストを走らせると、(UndefinedFunctionError)
になるはずなので、実装をしよう。
Groupと同じcontext (Chat)を使って、Messageのリソースを追加しよう。
mix phx.gen.context Chat Message messages content:string
Groupに複数のMessageを持てるようにしたいので、messagesテーブルにgroup_idコラムを追加して、Group has_many Messages、とMessage belongs_to Groupの依存関係にしたい。
そうするために、まずはmessagesテーブルのマイグレーションにgroup_idを追加する。
次に、各Schemaファイルにhas_many
、belongs_to
の関係を追記しよう。(Messageに関しては、changesetに:group_id
を追加することも忘れなく。)
ContextからGroupを取得する時には、Repo.preload(:messages)
を使えば紐づいたMessagesも取得できる。
また、list_messages/0
、update_message/1
、delete_message/1
は必要ないので、実装ファイルとテストファイル両方から削除。
最後に、viewsディレクトリのshow.jsonを編集して、そこから新しく作成したmessage.jsonを参照する。
これでテストを走らせれば、全テストがパスするはず。
ブラウザでも確認しよう。
メッセージがないので、既存Groupと紐づけた形で作成する必要がある。
ただ、Curlでは作れない。なぜなら、(後にWebsocketのChannelを使う予定なので)現時点ではRestエンドポイントがないからだ。
ここで便利なのが、elixirのiexだ。コンソール内でelixirの実装環境を立ち上げて、Phoenixのアプリと接続してくれる。
アプリが起動している状態で、以下を入力すると、
iex -S mix
アプリのメソッドを直接呼び出せるようになる。アプリ内と同様、aliasを定義するか、module名を全入力(Chatter.Chat.get_group
など)する必要ある。
Chat.create_message/1
を使って新しい Messageを作成しよう。
これでブラウザに戻ると、作成したメッセージが紐づいているはず。
これでAPIのRESTの部分が完成した!
ここまでのコードはP1_2_GENERATE_MESSAGE_RESOURCEブランチ参照
最後に、CORSの設定を追加しよう。
CORSの有効化
現時点では、http://localhost:4000を使ったブラウザやCurlのリクエストは成功するが、別origin(http://localhost:4000以外)で立ち上げたWebアプリからのリクエストはCORS違反で失敗する。
ウェブアプリからアクセスできるよう、想定するウェブアプリのoriginを事前にCORS設定に追加しよう。
依存ライブラリにcorsicaを追加して、
インストールしよう。
mix deps.get
ウェブアプリのポートはcreate-react-appのデフォルトを想定してhttp://localhost:3000に設定しよう。
endpoint.exのChatterWeb.Routerの設定の上に追加しよう。
これで、http://localhost:3000からのリクエストを受け付けるようになった。
現時点では確認の使用がないので、次のPart 2に進んでhttp://localhost:3000で起動するウェブアプリを作ろう!
ここまでのコードはP1_3_ADD_CORSブランチ参照