mcpField
P
phoenix-api-channels

phoenix-api-channels

github.com/bobmatnyc開発78343 回インストール15 週間前に更新
リポジトリ

phoenix-api-channels について

phoenix-api-channelsは、PhoenixとElixirでREST/JSON APIやリアルタイム機能を構築するためのスキルです。コントローラーとContextの分担、Channels、PubSub、Presence、認証、テスト、運用を扱います。

主な機能

  1. 01REST/JSONコントローラーの設計
  2. 02Contextによるデータ管理
  3. 03PubSubとPresenceを使うChannels
  4. 04Plugベースの認証
  5. 05APIとChannelのテスト
  6. 06Telemetryとリリースの手引き

活用シーン

  1. 01バージョン管理されたJSON APIの設計
  2. 02ルームのリアルタイム更新の配信
  3. 03接続中のユーザーやデバイスの追跡
  4. 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 が並行処理とフォールトトレランスを担うため、明確な境界とリアルタイム体験に注力しましょう。

コメント