phoenix-api-channels について
phoenix-api-channelsは、PhoenixとElixirでREST/JSON APIやリアルタイム機能を構築するためのスキルです。コントローラーとContextの分担、Channels、PubSub、Presence、認証、テスト、運用を扱います。
主な機能
- 01REST/JSONコントローラーの設計
- 02Contextによるデータ管理
- 03PubSubとPresenceを使うChannels
- 04Plugベースの認証
- 05APIとChannelのテスト
- 06Telemetryとリリースの手引き
活用シーン
- 01バージョン管理されたJSON APIの設計
- 02ルームのリアルタイム更新の配信
- 03接続中のユーザーやデバイスの追跡
- 04認証付きChannelの操作のテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill phoenix-api-channelsインストール後、Claude Code で/phoenix-api-channelsを入力して読み込まれたか確認します。
同梱ファイルmetadata.json
詳細
Phoenix API、Channels、Presence(Elixir/BEAM)
Phoenix は、ボイラープレートを最小限に抑えた REST/JSON API と WebSocket Channels に優れており、BEAM のフォールトトレランス、軽量プロセス、監視された PubSub/Presence を活用します。
中核となる柱
- plug、パイプライン、バージョニングを備えた JSON API 用コントローラー。
- コンテキストがデータ(Ecto スキーマとクエリー)を管理し、コントローラーやチャネルに限定的な API を公開します。
- Channels と PubSub によるリアルタイム更新のファンアウト、Presence によるユーザー/デバイスの追跡。
- plug による認証(ブラウザーにはセッション/Cookie、API にはトークン/Bearer)と、署名付きパラメーター。
プロジェクトのセットアップ
mix phx.new my_api --no-html --no-live
cd my_api
mix deps.get
mix ecto.create
mix phx.server
主要なファイル:
lib/my_api_web/endpoint.ex— plug、ソケット、インストルメンテーションlib/my_api_web/router.ex— パイプライン、スコープ、バージョニング、ソケットlib/my_api_web/controllers/*— REST/JSON コントローラーlib/my_api/*— コンテキストと Ecto スキーマ(データロジックの管理)lib/my_api_web/channels/*— Channel モジュール
ルーティングとパイプライン
ブラウザー用と API 用のパイプラインを分け、スコープで API をバージョニングします。
defmodule MyApiWeb.Router do
use MyApiWeb, :router
pipeline :api do
plug :accepts, ["json"]
plug :fetch_session
plug :protect_from_forgery
plug MyApiWeb.Plugs.RequireAuth
end
scope "/api", MyApiWeb do
pipe_through :api
scope "/v1", V1, as: :v1 do
resources "/users", UserController, except: [:new, :edit]
post "/sessions", SessionController, :create
end
end
socket "/socket", MyApiWeb.UserSocket,
websocket: [connect_info: [:peer_data, :x_headers]],
longpoll: false
end
ヒント
- パイプラインは短く保ち、認証やガードは plug に移します。
- Channels 用に
socket "/socket"を公開し、必要に応じてトランスポートを制限します。
コントローラーと Plug
コントローラーは薄く保ち、ロジックはコンテキストが管理します。
defmodule MyApiWeb.V1.UserController do
use MyApiWeb, :controller
alias MyApi.Accounts
action_fallback MyApiWeb.FallbackController
def index(conn, _params) do
users = Accounts.list_users()
render(conn, :index, users: users)
end
def create(conn, params) do
with {:ok, user} <- Accounts.register_user(params) do
conn
|> put_status(:created)
|> put_resp_header("location", ~p\"/api/v1/users/#{user.id}\")
|> render(:show, user: user)
end
end
end
FallbackController はエラー変換({:error, :not_found} → 404 JSON)を一元化します。
Plug
RequireAuthは bearer/セッショントークンを検証し、current_userを設定します。:scrub_paramsのような変換はコントローラーではなく、パイプラインで使用します。- plug はリクエストごとに実行されるため、重い処理は避けます。
コンテキストとデータ(Ecto)
コンテキストは、コントローラーやチャネルが必要とするものだけを公開します。
defmodule MyApi.Accounts do
import Ecto.Query, warn: false
alias MyApi.{Repo, Accounts.User}
def list_users, do: Repo.all(User)
def get_user!(id), do: Repo.get!(User, id)
def register_user(attrs) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
end
ガイドライン
- スキーマモジュールにコントローラーの知識を持たせないようにします。
- changeset で検証し、複数ステップの操作には
Ecto.Multiを使用します。 - 大規模なリストには、ページネーションヘルパー(
Scrivener、Flop)を優先します。
Channels、PubSub、Presence
Channel モジュールの例:
defmodule MyApiWeb.RoomChannel do
use Phoenix.Channel
alias Phoenix.Presence
def join("room:" <> room_id, _payload, socket) do
send(self(), :after_join)
{:ok, assign(socket, :room_id, room_id)}
end
def handle_info(:after_join, socket) do
Presence.track(socket, socket.assigns.user_id, %{online_at: System.system_time(:second)})
push(socket, "presence_state", Presence.list(socket))
{:noreply, socket}
end
def handle_in("message:new", %{"body" => body}, socket) do
broadcast!(socket, "message:new", %{user_id: socket.assigns.user_id, body: body})
{:noreply, socket}
end
end
コンテキストからの PubSub
def create_order(attrs) do
with {:ok, order} <- %Order{} |> Order.changeset(attrs) |> Repo.insert() do
Phoenix.PubSub.broadcast(MyApi.PubSub, "orders", {:order_created, order})
{:ok, order}
end
end
ベストプラクティス
- トピックに参加する前に、
UserSocket.connect/3で認可します。 - ペイロードサイズを制限し、受信イベントを検証します。
- テナント分離にはトピックのパーティショニングを使用します(
"tenant:" <> tenant_id <> ":room:" <> room_id)。
認証パターン
- API トークン:
authorization: Bearer <token>を受け付けます。plug で検証し、current_userを割り当てます。 - 署名付きパラメーター:有効期間の短い参加用パラメーターには
Phoenix.Token.sign/verifyを使用します。 - レート制限:plug と ETS/Cachex、またはリバースプロキシ(NGINX/Cloudflare)を使用します。
- CORS:
cors_plugを使用してEndpointで設定します。
テスト
生成されたヘルパーを使用します:
defmodule MyApiWeb.UserControllerTest do
use MyApiWeb.ConnCase, async: true
test "lists users", %{conn: conn} do
conn = get(conn, ~p\"/api/v1/users\")
assert json_response(conn, 200)["data"] == []
end
end
Channel のテスト:
defmodule MyApiWeb.RoomChannelTest do
use MyApiWeb.ChannelCase, async: true
test "broadcasts messages" do
{:ok, _, socket} = connect(MyApiWeb.UserSocket, %{"token" => "abc"})
{:ok, _, socket} = subscribe_and_join(socket, "room:123", %{})
ref = push(socket, "message:new", %{"body" => "hi"})
assert_reply ref, :ok
assert_broadcast "message:new", %{body: "hi"}
end
end
DataCase:テストごとに DB を分離します。セットアップにはフィクスチャ/ファクトリーを使用します。
Telemetry、可観測性、運用
- endpoint、controller、channel、Ecto クエリーからの
:telemetryイベントを、OpentelemetryPhoenixとOpentelemetryEctoでエクスポートします。 - リクエストメトリクスには
Plug.Telemetryを使用し、ログのメタデータ(request_id、user_id)を追加します。 - リリース:
MIX_ENV=prod mix release。config/runtime.exsでランタイムを設定します。 - クラスタリング:複数ノードでの Presence には
libclusterと分散 PubSub を使用します。 - アセット不要の API:API のみのアプリでは、未使用のウォッチャー(esbuild/tailwind)を無効にします。
よくある落とし穴
- コントローラーがコンテキストに委譲せず、直接クエリーを実行している。
UserSocket.connect/3で認可せず、トピックが露出している。action_fallbackがなく、エラー形式に一貫性がない。- イベントのペイロード制限を忘れ、大きなメッセージがチャネルを圧迫する。
- 使用していないのに longpoll を有効にしたままにしている。攻撃対象領域を減らすために無効にします。
コンテキストがデータを管理し、コントローラーを薄く保ち、Channels で厳格な認可と telemetry を伴う PubSub/Presence を使用すると、Phoenix API と Channels の強みが発揮されます。BEAM が並行処理とフォールトトレランスを担うため、明確な境界とリアルタイム体験に注力しましょう。