Phoenix + Reactでチャットアプリを作ろう(Part1)

Phoenixを使ったGroup・MessageのREST API作成

Ichizo Umehara
14 min readMar 9, 2020

アプリ概要は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権限有り)が開発環境と一致していることを確認してから、

/config/dev.exs

DB作成。

cd apimix ecto.create

ecto.createでプロジェクト名のデータベース(chatter_devchatter_test)を作ってくれる。

アプリの枠組みができたので、サーバを起動しよう。

mix phx.server
  • サーバをhttp://localhost:4000に起動してくれる。(ちなみに、サーバを落としたい場合は control C 2回で止められる。)
ブラウザでhttp://localhost:4000をロードすると、このようなエラーベージが

これで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.exgroup_controller.exrouter.exchat.exgroup.ex2020xxxxxxxxxx_create_groups.exs 。順番に見ていこう。

group_view.exにはレスポンスのJSONが定義されている。現時点では変更不必要。

group_controller.exには5つのメソッドが自動生成される。今回使うindex(一覧表示)、create(新規作成)、show(詳細表示)を残して、それ以外は削除しよう。

/lib/chatter_web/controllers/group_controller.ex

router.exでは、リソースをapiのスコープに追加し、不必要なメソッドを除外ブロックexcept:に追加する。

/lib/chatter_web/router.ex

chat.exがContext(サービス層)。ここもまた必要なメソッドだけ残してあとは削除。

/lib/chatter/chat.ex

group.exはデータベース接続層。プロパティのフィルタリングやバリデーションなどを行う。現時点では変更不必要。

/lib/chatter/chat/group.ex

2020xxxxxxxxxx_create_groups.exs(xxxは作成時のtimestamp)はデータベースのマイグレーションファイル。mix ecto.migrateでテーブルの作成や修正がDBに適用される。現時点では変更不必要。

/priv/repo/migrations/2020xxxxxxxxxx_create_groups.exs

Group関連のファイルが整ったので、データベースにgroupsのテーブルを追加しよう。

mix ecto.migrate

ここでまたサーバを起動して/api/groupsに行くと、JSONが見れるはず。

mix phx.server
http://localhost:4000/api/groups

現時点ではまだデータが空なので、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

ブラウザでまた確認すると、

http://localhost:4000/api/groups

2件見えるはず。

idを使って詳細画面も確認してみよう。パスは/api/groups/:id

http://localhost:4000/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”のテストを追加。レスポンスにメッセージの配列が含まれていることを確認しよう。

/test/chatter_web/controllers/group_controller_test.exs

Contextのテストのget_group!/1のテストにもメッセージの配列が含まれていることを確認。

/test/chatter/chat_test.exs

ここで、テストを走らせると、(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を追加する。

/priv/repo/migrations/2020xxxxxxxxxx_create_messages.exs

次に、各Schemaファイルにhas_manybelongs_toの関係を追記しよう。(Messageに関しては、changesetに:group_idを追加することも忘れなく。)

/lib/chatter/chat/group.ex
/lib/chatter/chat/message.ex

ContextからGroupを取得する時には、Repo.preload(:messages)を使えば紐づいたMessagesも取得できる。

また、list_messages/0update_message/1delete_message/1は必要ないので、実装ファイルとテストファイル両方から削除。

/lib/chatter/chat.ex

最後に、viewsディレクトリのshow.jsonを編集して、そこから新しく作成したmessage.jsonを参照する。

/lib/chatter_web/views/group_view.ex
/lib/chatter_web/views/message_view.ex

これでテストを走らせれば、全テストがパスするはず。

mix test

ブラウザでも確認しよう。

http://localhost:4000/api/groups/:id

メッセージがないので、既存Groupと紐づけた形で作成する必要がある。

ただ、Curlでは作れない。なぜなら、(後にWebsocketのChannelを使う予定なので)現時点ではRestエンドポイントがないからだ。

ここで便利なのが、elixirのiexだ。コンソール内でelixirの実装環境を立ち上げて、Phoenixのアプリと接続してくれる。

アプリが起動している状態で、以下を入力すると、

iex -S mix

アプリのメソッドを直接呼び出せるようになる。アプリ内と同様、aliasを定義するか、module名を全入力(Chatter.Chat.get_groupなど)する必要ある。

Chat.create_message/1を使って新しい Messageを作成しよう。

iex -S mix

これでブラウザに戻ると、作成したメッセージが紐づいているはず。

http://localhost:4000/api/groups/:id

これでAPIのRESTの部分が完成した!

ここまでのコードはP1_2_GENERATE_MESSAGE_RESOURCEブランチ参照

最後に、CORSの設定を追加しよう。

CORSの有効化

現時点では、http://localhost:4000を使ったブラウザやCurlのリクエストは成功するが、別origin(http://localhost:4000以外)で立ち上げたWebアプリからのリクエストはCORS違反で失敗する。

ウェブアプリからアクセスできるよう、想定するウェブアプリのoriginを事前にCORS設定に追加しよう。

依存ライブラリにcorsicaを追加して、

./mix.exs

インストールしよう。

mix deps.get

ウェブアプリのポートはcreate-react-appのデフォルトを想定してhttp://localhost:3000に設定しよう。

endpoint.exのChatterWeb.Routerの設定の上に追加しよう。

/lib/chatter_web/endpoint.ex

これで、http://localhost:3000からのリクエストを受け付けるようになった。

現時点では確認の使用がないので、次のPart 2に進んでhttp://localhost:3000で起動するウェブアプリを作ろう!

ここまでのコードはP1_3_ADD_CORSブランチ参照

--

--