bobmatnyc/claude-mpm-skills

phoenix-api-channels

Phoenix controllers, JSON APIs, Channels, and Presence on the BEAM

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill phoenix-api-channels

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

Phoenix APIs, Channels, and Presence (Elixir/BEAM)

Phoenix excels at REST/JSON APIs and WebSocket Channels with minimal boilerplate, leveraging the BEAM for fault tolerance, lightweight processes, and supervised PubSub/Presence.

Core pillars

  • Controllers for JSON APIs with plugs, pipelines, and versioning.
  • Contexts own data (Ecto schemas + queries) and expose a narrow API to controllers/channels.
  • Channels + PubSub for fan-out real-time updates; Presence for tracking users/devices.
  • Auth via plugs (session/cookie for browser, token/Bearer for APIs), with signed params.

Project Setup

bash
mix phx.new my_api --no-html --no-live
cd my_api
mix deps.get
mix ecto.create
mix phx.server

Key files:

  • lib/my_api_web/endpoint.ex — plugs, sockets, instrumentation
  • lib/my_api_web/router.ex — pipelines, scopes, versioning, sockets
  • lib/my_api_web/controllers/* — REST/JSON controllers
  • lib/my_api/* — contexts + Ecto schemas (ownership of data logic)
  • lib/my_api_web/channels/* — Channel modules

Routing and Pipelines

Separate browser vs API pipelines; version APIs with scopes.

elixir
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

Tips

  • Keep pipelines short; push auth/guards into plugs.
  • Expose socket "/socket" for Channels; restrict transports as needed.

Controllers and Plugs

Controllers stay thin; contexts own the logic.

elixir
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 centralizes error translation ({:error, :not_found} → 404 JSON).

Plugs

  • RequireAuth verifies bearer/session tokens, sets current_user.
  • Use plug :scrub_params-style transforms in pipelines, not controllers.
  • Avoid heavy work in plugs; they run per-request.

Contexts and Data (Ecto)

Contexts expose only what controllers/channels need.

elixir
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

Guidelines

  • Keep schema modules free of controller knowledge.
  • Validate at the changeset; use Ecto.Multi for multi-step operations.
  • Prefer pagination helpers (Scrivener, Flop) for large lists.

Channels, PubSub, and Presence

Channel module example:

elixir
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 from contexts

elixir
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

Best practices

  • Authorize in UserSocket.connect/3 before joining topics.
  • Limit payload sizes; validate incoming events.
  • Use topic partitioning for tenancy ("tenant:" <> tenant_id <> ":room:" <> room_id).

Authentication Patterns

  • API tokens: Accept authorization: Bearer <token>; verify in plug, assign current_user.
  • Signed params: Phoenix.Token.sign/verify for short-lived join params.
  • Rate limiting: Use plugs + ETS/Cachex or reverse proxy (NGINX/Cloudflare).
  • CORS: Configure in Endpoint with cors_plug.

Testing

Use generated helpers:

elixir
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 tests:

elixir
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: isolates DB per test; use fixtures/factories for setup.


Telemetry, Observability, and Ops

  • :telemetry events from endpoint, controller, channel, and Ecto queries; export via OpentelemetryPhoenix and OpentelemetryEcto.
  • Use Plug.Telemetry for request metrics; add logging metadata (requestid, userid).
  • Releases: MIX_ENV=prod mix release; configure runtime in config/runtime.exs.
  • Clustering: libcluster + distributed PubSub for multi-node Presence.
  • Assetless APIs: disable unused watchers (esbuild/tailwind) for API-only apps.

Common Pitfalls

  • Controllers doing queries directly instead of delegating to contexts.
  • Not authorizing in UserSocket.connect/3, leading to topic exposure.
  • Missing action_fallback → inconsistent error shapes.
  • Forgetting to limit event payloads; large messages can overwhelm channels.
  • Leaving longpoll enabled when unused; disable to reduce surface area.

Phoenix API + Channels shine when contexts own data, controllers stay thin, and Channels use PubSub/Presence with strict authorization and telemetry. The BEAM handles concurrency and fault tolerance; focus on clear boundaries and real-time experiences.

관련 작업

같은 저장소의 Skills

더 많은 Skills

모든 Skills