phoenix-api-channels by bobmatnyc/claude-mpm-skills
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill phoenix-api-channelsPhoenix 擅长构建 REST/JSON API 和 WebSocket 通道,且样板代码极少,它利用 BEAM 实现容错、轻量级进程和受监管的 PubSub/在线状态功能。
核心支柱
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 — 插件、套接字、仪表化lib/my_api_web/router.ex — 管道、作用域、版本管理、套接字lib/my_api_web/controllers/* — REST/JSON 控制器lib/my_api/* — 上下文 + Ecto 模式(数据逻辑的所有权)lib/my_api_web/channels/* — 通道模块广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
区分浏览器与 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
提示
socket "/socket" 用于通道;根据需要限制传输方式。控制器保持精简;上下文拥有业务逻辑。
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)。
插件
RequireAuth 验证 Bearer/会话令牌,设置 current_user。plug :scrub_params 风格的转换,而不是在控制器中。上下文仅暴露控制器/通道所需的内容。
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
指导原则
Ecto.Multi。Scrivener、Flop)。通道模块示例:
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)。authorization: Bearer <token>;在插件中验证,分配 current_user。Phoenix.Token.sign/verify 处理短期有效的加入参数。Endpoint 中使用 cors_plug 进行配置。使用生成的助手:
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
通道测试:
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:每个测试隔离数据库;使用夹具/工厂进行设置。
:telemetry 事件来自端点、控制器、通道和 Ecto 查询;通过 OpentelemetryPhoenix 和 OpentelemetryEcto 导出。Plug.Telemetry 获取请求指标;添加日志元数据(request_id、user_id)。MIX_ENV=prod mix release;在 config/runtime.exs 中配置运行时。libcluster + 分布式 PubSub 用于多节点在线状态。UserSocket.connect/3 中授权,导致主题暴露。action_fallback → 错误格式不一致。当上下文拥有数据、控制器保持精简、通道使用 PubSub/在线状态并配合严格授权和遥测时,Phoenix API + 通道表现出色。BEAM 处理并发和容错;专注于清晰的边界和实时体验。
每周安装次数
74
代码仓库
GitHub 星标数
18
首次出现
2026年1月23日
安全审计
安装于
opencode57
gemini-cli57
codex56
claude-code55
github-copilot53
cursor52
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
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, 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 modulesSeparate 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
end
Tips
socket "/socket" for Channels; restrict transports as needed.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
end
FallbackController centralizes error translation ({:error, :not_found} → 404 JSON).
Plugs
RequireAuth verifies bearer/session tokens, sets current_user.plug :scrub_params-style transforms in pipelines, not controllers.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
end
Guidelines
Ecto.Multi for multi-step operations.Scrivener, Flop) for large lists.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
end
PubSub 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
end
Best practices
UserSocket.connect/3 before joining topics."tenant:" <> tenant_id <> ":room:" <> room_id).authorization: Bearer <token>; verify in plug, assign current_user.Phoenix.Token.sign/verify for short-lived join params.Endpoint with cors_plug.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
end
Channel 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
end
DataCase : isolates DB per test; use fixtures/factories for setup.
:telemetry events from endpoint, controller, channel, and Ecto queries; export via OpentelemetryPhoenix and OpentelemetryEcto.Plug.Telemetry for request metrics; add logging metadata (request_id, user_id).MIX_ENV=prod mix release; configure runtime in config/runtime.exs.libcluster + distributed PubSub for multi-node Presence.UserSocket.connect/3, leading to topic exposure.action_fallback → inconsistent error shapes.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.
Weekly Installs
74
Repository
GitHub Stars
18
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
opencode57
gemini-cli57
codex56
claude-code55
github-copilot53
cursor52
Lark CLI IM 即时消息管理工具:机器人/用户身份操作聊天、消息、文件下载
32,700 周安装
二进制分析分析师:专业二进制漏洞发现与利用性评估工作流
1 周安装
X/Twitter 内容保存到 Obsidian 工具 - 自动化知识管理插件
1 周安装
Effect模式匹配完全指南:替代复杂if/else,实现类型安全控制流
2 周安装
Effect TypeScript 数据类型详解:Option、Either、Cause、Exit、Data、Chunk、Duration、DateTime
2 周安装
AI图像提示词生成器 - 多样化艺术风格、构图、光照一键生成高质量AI绘画提示
2 周安装
Python UV 包管理工具:比 pip 快 10-100 倍的现代依赖管理方案
2 周安装