
Phoenix Api Channels
- 235 installs
- 63 repo stars
- Updated July 18, 2026
- bobmatnyc/claude-mpm-skills
Implement Phoenix Channels for WebSocket topics, presence, broadcasts, and authenticated real-time APIs alongside REST in Elixir/Phoenix services.
About
Covers Phoenix Channels for Elixir backends: socket pipelines, channel modules, topic naming, authorization on join, broadcasting from contexts, presence, and test strategies so Claude builds dependable real-time APIs.
- Socket authentication and channel joins
- Topic design and PubSub broadcasting
- Presence tracking patterns
- Backpressure and reconnect handling
- Testing channels and isolating domain events
Phoenix Api Channels by the numbers
- 235 all-time installs (skills.sh)
- Ranked #1,678 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill phoenix-api-channelsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 235 |
|---|---|
| repo stars | ★ 63 |
| Last updated | July 18, 2026 |
| Repository | bobmatnyc/claude-mpm-skills ↗ |
What it does
Implement Phoenix Channels for WebSocket topics, presence, broadcasts, and authenticated real-time APIs alongside REST in Elixir/Phoenix services.
Files
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
mix phx.new my_api --no-html --no-live
cd my_api
mix deps.get
mix ecto.create
mix phx.serverKey files:
lib/my_api_web/endpoint.ex— plugs, sockets, instrumentationlib/my_api_web/router.ex— pipelines, scopes, versioning, socketslib/my_api_web/controllers/*— REST/JSON controllerslib/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.
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
endTips
- 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.
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
endFallbackController centralizes error translation ({:error, :not_found} → 404 JSON).
Plugs
RequireAuthverifies bearer/session tokens, setscurrent_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.
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
endGuidelines
- Keep schema modules free of controller knowledge.
- Validate at the changeset; use
Ecto.Multifor multi-step operations. - Prefer pagination helpers (
Scrivener,Flop) for large lists.
---
Channels, PubSub, and Presence
Channel module example:
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
endPubSub from contexts
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
endBest practices
- Authorize in
UserSocket.connect/3before 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, assigncurrent_user. - Signed params:
Phoenix.Token.sign/verifyfor short-lived join params. - Rate limiting: Use plugs + ETS/Cachex or reverse proxy (NGINX/Cloudflare).
- CORS: Configure in
Endpointwithcors_plug.
---
Testing
Use generated helpers:
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
endChannel tests:
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
endDataCase: isolates DB per test; use fixtures/factories for setup.
---
Telemetry, Observability, and Ops
:telemetryevents from endpoint, controller, channel, and Ecto queries; export viaOpentelemetryPhoenixandOpentelemetryEcto.- Use
Plug.Telemetryfor request metrics; add logging metadata (request_id, user_id). - Releases:
MIX_ENV=prod mix release; configure runtime inconfig/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.
{
"name": "phoenix-api-channels",
"version": "1.0.0",
"category": "toolchain",
"toolchain": "elixir",
"framework": "phoenix-api-channels",
"tags": [
"elixir",
"phoenix",
"channels",
"presence",
"pubsub",
"json",
"api",
"auth",
"telemetry"
],
"entry_point_tokens": 170,
"full_tokens": 2001,
"author": "Claude MPM Team",
"license": "MIT",
"requires": [],
"updated": "2025-12-05",
"source_path": "toolchains/elixir/frameworks/phoenix-api-channels/SKILL.md",
"repository": "https://github.com/bobmatnyc/claude-mpm-skills"
}