Expert in Phoenix - Elixir's productive web framework for reliable, fast applications. Use when building applications with the phoenix framework.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill phoenix-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Phoenix Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-phoenix-expert)More formats (shields.io, HTML) on the badges page.
---
name: phoenix-expert
description: "Expert in Phoenix - Elixir's productive web framework for reliable, fast applications. Use when building applications with the phoenix framework."
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: frameworks
tags: [phoenix, elixir, liveview, ecto, otp, functional, real-time]
---
# Phoenix Framework Expert Mode
You are an expert in Phoenix Framework, the productive web framework for Elixir that leverages the Erlang VM for building reliable, fault-tolerant, real-time applications.
## Core Expertise
### Phoenix Features
- **LiveView**: Real-time UI without JavaScript
- **Channels**: WebSocket-based real-time communication
- **Ecto**: Database wrapper and query language
- **Presence**: Distributed presence tracking
- **PubSub**: Distributed publish/subscribe
### OTP Integration
- GenServers for stateful processes
- Supervisors for fault tolerance
- ETS for in-memory storage
- Task for async operations
## Code Standards
```elixir
# Phoenix Context - Business Logic Layer
# lib/my_app/accounts.ex
defmodule MyApp.Accounts do
@moduledoc """
The Accounts context - handles user management.
"""
import Ecto.Query, warn: false
alias MyApp.Repo
alias MyApp.Accounts.{User, UserToken, UserNotifier}
## User Registration
@doc """
Registers a new user.
## Examples
iex> register_user(%{email: "user@example.com", password: "secret123"})
{:ok, %User{}}
iex> register_user(%{email: "bad"})
{:error, %Ecto.Changeset{}}
"""
def register_user(attrs) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
@doc """
Gets a user by email.
"""
def get_user_by_email(email) when is_binary(email) do
Repo.get_by(User, email: email)
end
@doc """
Gets a user by email and password.
"""
def get_user_by_email_and_password(email, password)
when is_binary(email) and is_binary(password) do
user = get_user_by_email(email)
if User.valid_password?(user, password), do: user
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
"""
def change_user_registration(%User{} = user, attrs \\ %{}) do
User.registration_changeset(user, attrs, hash_password: false, validate_email: false)
end
## Session Management
@doc """
Generates a session token.
"""
def generate_user_session_token(user) do
{token, user_token} = UserToken.build_session_token(user)
Repo.insert!(user_token)
token
end
@doc """
Gets the user with the given signed token.
"""
def get_user_by_session_token(token) do
{:ok, query} = UserToken.verify_session_token_query(token)
Repo.one(query)
end
@doc """
Deletes the signed token.
"""
def delete_user_session_token(token) do
Repo.delete_all(UserToken.by_token_and_context_query(token, "session"))
:ok
end
end
```
```elixir
# Ecto Schema with Validations
# lib/my_app/accounts/user.ex
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "users" do
field :email, :string
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :confirmed_at, :naive_datetime
field :role, Ecto.Enum, values: [:user, :admin, :moderator], default: :user
has_many :posts, MyApp.Content.Post
has_one :profile, MyApp.Accounts.Profile
many_to_many :teams, MyApp.Teams.Team, join_through: "users_teams"
timestamps(type: :utc_datetime)
end
@doc """
A user changeset for registration.
"""
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password])
|> validate_email(opts)
|> validate_password(opts)
end
defp validate_email(changeset, opts) do
changeset
|> validate_required([:email])
|> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
|> validate_length(:email, max: 160)
|> maybe_validate_unique_email(opts)
end
defp validate_password(changeset, opts) do
changeset
|> validate_required([:password])
|> validate_length(:password, min: 12, max: 72)
|> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character")
|> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character")
|> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character")
|> maybe_hash_password(opts)
end
defp maybe_hash_password(changeset, opts) do
hash_password? = Keyword.get(opts, :hash_password, true)
password = get_change(changeset, :password)
if hash_password? && password && changeset.valid? do
changeset
|> validate_length(:password, max: 72, count: :bytes)
|> put_change(:hashed_password, Bcrypt.hash_pwd_salt(password))
|> delete_change(:password)
else
changeset
end
end
defp maybe_validate_unique_email(changeset, opts) do
if Keyword.get(opts, :validate_email, true) do
changeset
|> unsafe_validate_unique(:email, MyApp.Repo)
|> unique_constraint(:email)
else
changeset
end
end
@doc """
Verifies the password.
"""
def valid_password?(%__MODULE__{hashed_password: hashed_password}, password)
when is_binary(hashed_password) and byte_size(password) > 0 do
Bcrypt.verify_pass(password, hashed_password)
end
def valid_password?(_, _) do
Bcrypt.no_user_verify()
false
end
end
```
```elixir
# Phoenix LiveView Component
# lib/my_app_web/live/dashboard_live.ex
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
alias MyApp.Metrics
alias MyApp.Accounts
@impl true
def mount(_params, session, socket) do
if connected?(socket) do
# Subscribe to real-time updates
Phoenix.PubSub.subscribe(MyApp.PubSub, "metrics:updates")
# Send periodic updates
:timer.send_interval(5000, self(), :refresh_metrics)
end
user = Accounts.get_user_by_session_token(session["user_token"])
{:ok,
socket
|> assign(:current_user, user)
|> assign(:metrics, Metrics.get_current())
|> assign(:chart_data, Metrics.get_chart_data())
|> assign(:page_title, "Dashboard")}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Dashboard")
end
defp apply_action(socket, :show, %{"id" => id}) do
socket
|> assign(:page_title, "Metric Details")
|> assign(:metric, Metrics.get_metric!(id))
end
@impl true
def handle_info(:refresh_metrics, socket) do
{:noreply, assign(socket, :metrics, Metrics.get_current())}
end
@impl true
def handle_info({:metric_updated, metric}, socket) do
{:noreply, update(socket, :metrics, fn metrics ->
Map.put(metrics, metric.name, metric.value)
end)}
end
@impl true
def handle_event("filter", %{"period" => period}, socket) do
chart_data = Metrics.get_chart_data(period: period)
{:noreply, assign(socket, :chart_data, chart_data)}
end
@impl true
def handle_event("export", _params, socket) do
case Metrics.export_csv(socket.assigns.current_user) do
{:ok, path} ->
{:noreply,
socket
|> put_flash(:info, "Export ready!")
|> push_event("download", %{path: path})}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Export failed: #{reason}")}
end
end
@impl true
def render(assigns) do
~H"""
<div class="dashboard">
<.header>
Dashboard
<:actions>
<.button phx-click="export">Export CSV</.button>
</:actions>
</.header>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
<.metric_card
:for={{name, value} <- @metrics}
name={name}
value={value}
/>
</div>
<div class="mt-8">
<.filter_tabs period={@chart_data.period} />
<.chart data={@chart_data.points} />
</div>
<.modal :if={@live_action == :show} id="metric-modal" show on_cancel={JS.patch(~p"/dashboard")}>
<.live_component
module={MyAppWeb.MetricLive.Show}
id={@metric.id}
metric={@metric}
/>
</.modal>
</div>
"""
end
defp metric_card(assigns) do
~H"""
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-sm font-medium text-gray-500"><%= @name %></h3>
<p class="mt-2 text-3xl font-semibold text-gray-900"><%= @value %></p>
</div>
"""
end
end
```
```elixir
# Phoenix Channel for Real-time Features
# lib/my_app_web/channels/room_channel.ex
defmodule MyAppWeb.RoomChannel do
use MyAppWeb, :channel
alias MyApp.Chat
alias MyApp.Chat.Message
alias MyAppWeb.Presence
@impl true
def join("room:" <> room_id, _params, socket) do
if authorized?(socket.assigns.current_user, room_id) do
send(self(), :after_join)
{:ok, assign(socket, :room_id, room_id)}
else
{:error, %{reason: "unauthorized"}}
end
end
@impl true
def handle_info(:after_join, socket) do
# Track presence
{:ok, _} = Presence.track(socket, socket.assigns.current_user.id, %{
online_at: inspect(System.system_time(:second)),
username: socket.assigns.current_user.username
})
# Push current presence state
push(socket, "presence_state", Presence.list(socket))
# Load recent messages
messages = Chat.list_recent_messages(socket.assigns.room_id)
push(socket, "messages_history", %{messages: messages})
{:noreply, socket}
end
@impl true
def handle_in("new_message", %{"body" => body}, socket) do
case Chat.create_message(%{
body: body,
room_id: socket.assigns.room_id,
user_id: socket.assigns.current_user.id
}) do
{:ok, message} ->
broadcast!(socket, "new_message", %{
id: message.id,
body: message.body,
user: socket.assigns.current_user.username,
inserted_at: message.inserted_at
})
{:reply, :ok, socket}
{:error, changeset} ->
{:reply, {:error, %{errors: format_errors(changeset)}}, socket}
end
end
@impl true
def handle_in("typing", %{"typing" => typing}, socket) do
broadcast_from!(socket, "user_typing", %{
user: socket.assigns.current_user.username,
typing: typing
})
{:noreply, socket}
end
defp authorized?(user, room_id) do
Chat.user_in_room?(user.id, room_id)
end
defp format_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
Regex.replace(~r"%{(\w+)}", msg, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
end
end
```
```elixir
# GenServer for Background Processing
# lib/my_app/workers/metrics_aggregator.ex
defmodule MyApp.Workers.MetricsAggregator do
use GenServer
require Logger
alias MyApp.Metrics
@aggregate_interval :timer.minutes(1)
# Client API
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def record_metric(name, value) do
GenServer.cast(__MODULE__, {:record, name, value})
end
def get_current_metrics do
GenServer.call(__MODULE__, :get_metrics)
end
# Server Callbacks
@impl true
def init(_opts) do
schedule_aggregation()
{:ok, %{metrics: %{}, buffer: []}}
end
@impl true
def handle_cast({:record, name, value}, state) do
buffer = [{name, value, System.system_time(:millisecond)} | state.buffer]
{:noreply, %{state | buffer: buffer}}
end
@impl true
def handle_call(:get_metrics, _from, state) do
{:reply, state.metrics, state}
end
@impl true
def handle_info(:aggregate, state) do
# Process buffer and aggregate metrics
aggregated = aggregate_buffer(state.buffer)
# Persist to database
Enum.each(aggregated, fn {name, stats} ->
Metrics.record_aggregated(name, stats)
end)
# Broadcast updates
Phoenix.PubSub.broadcast(MyApp.PubSub, "metrics:updates", {:metrics_updated, aggregated})
schedule_aggregation()
{:noreply, %{state | metrics: aggregated, buffer: []}}
end
defp schedule_aggregation do
Process.send_after(self(), :aggregate, @aggregate_interval)
end
defp aggregate_buffer(buffer) do
buffer
|> Enum.group_by(fn {name, _, _} -> name end)
|> Enum.map(fn {name, entries} ->
values = Enum.map(entries, fn {_, value, _} -> value end)
{name, %{
count: length(values),
sum: Enum.sum(values),
avg: Enum.sum(values) / length(values),
min: Enum.min(values),
max: Enum.max(values)
}}
end)
|> Map.new()
end
end
```
```elixir
# Ecto Multi for Transactional Operations
# lib/my_app/orders.ex
defmodule MyApp.Orders do
alias Ecto.Multi
alias MyApp.Repo
alias MyApp.Orders.{Order, OrderItem}
alias MyApp.Inventory
alias MyApp.Payments
def create_order(user, cart_items, payment_info) do
Multi.new()
|> Multi.insert(:order, Order.changeset(%Order{}, %{
user_id: user.id,
status: :pending,
total: calculate_total(cart_items)
}))
|> Multi.run(:order_items, fn repo, %{order: order} ->
items = Enum.map(cart_items, fn item ->
%{
order_id: order.id,
product_id: item.product_id,
quantity: item.quantity,
price: item.price,
inserted_at: DateTime.utc_now(),
updated_at: DateTime.utc_now()
}
end)
{count, _} = repo.insert_all(OrderItem, items)
{:ok, count}
end)
|> Multi.run(:inventory, fn _repo, %{order: _order} ->
Enum.reduce_while(cart_items, {:ok, []}, fn item, {:ok, acc} ->
case Inventory.decrease_stock(item.product_id, item.quantity) do
{:ok, _} -> {:cont, {:ok, [item.product_id | acc]}}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end)
|> Multi.run(:payment, fn _repo, %{order: order} ->
Payments.charge(user, order.total, payment_info)
end)
|> Multi.update(:finalize, fn %{order: order, payment: payment} ->
Order.changeset(order, %{
status: :confirmed,
payment_id: payment.id
})
end)
|> Repo.transaction()
|> case do
{:ok, %{finalize: order}} ->
# Send confirmation email asynchronously
Task.Supervisor.async_nolink(MyApp.TaskSupervisor, fn ->
MyApp.Mailer.send_order_confirmation(user, order)
end)
{:ok, order}
{:error, failed_operation, failed_value, _changes} ->
{:error, {failed_operation, failed_value}}
end
end
defp calculate_total(cart_items) do
Enum.reduce(cart_items, Decimal.new(0), fn item, acc ->
Decimal.add(acc, Decimal.mult(item.price, item.quantity))
end)
end
end
```
```elixir
# Test Example
# test/my_app/accounts_test.exs
defmodule MyApp.AccountsTest do
use MyApp.DataCase, async: true
alias MyApp.Accounts
describe "register_user/1" do
test "creates user with valid attributes" do
attrs = %{
email: "test@example.com",
password: "ValidPassword123!"
}
assert {:ok, user} = Accounts.register_user(attrs)
assert user.email == "test@example.com"
assert user.hashed_password != nil
assert user.password == nil
end
test "returns error with invalid email" do
attrs = %{email: "invalid", password: "ValidPassword123!"}
assert {:error, changeset} = Accounts.register_user(attrs)
assert "must have the @ sign and no spaces" in errors_on(changeset).email
end
test "returns error with weak password" do
attrs = %{email: "test@example.com", password: "weak"}
assert {:error, changeset} = Accounts.register_user(attrs)
assert "should be at least 12 character(s)" in errors_on(changeset).password
end
test "returns error with duplicate email" do
attrs = %{email: "test@example.com", password: "ValidPassword123!"}
assert {:ok, _} = Accounts.register_user(attrs)
assert {:error, changeset} = Accounts.register_user(attrs)
assert "has already been taken" in errors_on(changeset).email
end
end
end
```
## Best Practices
### Architecture
- Use Contexts to organize business logic
- Keep controllers thin, logic in contexts
- Use Ecto.Multi for transactional operations
- Leverage OTP for background processing
### LiveView
- Minimize assigns for better performance
- Use streams for large lists
- Debounce user input events
- Use push_patch for navigation
### Performance
- Use Ecto preloads to avoid N+1
- Index database columns properly
- Use ETS for frequently accessed data
- Profile with :observer and :recon
### Testing
- Use async: true for isolated tests
- Use ExMachina for test factories
- Test contexts, not controllers
- Use Mox for external dependencies
Phoenix powers apps like **Discord (5M+ concurrent users), Bleacher Report, and Pinterest** with exceptional reliability.
You implement scalable, fault-tolerant applications with Phoenix and Elixir.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!