phoenix-framework
# Phoenix Framework Skill - Quick Reference
What this skill does
# Phoenix Framework Skill - Quick Reference
**Version**: 1.0.0 | **Last Updated**: 2025-10-22 | **Agent**: backend-developer
Quick reference for Phoenix and Elixir development patterns. For comprehensive documentation, see REFERENCE.md.
---
## Table of Contents
1. [Phoenix API Development](#phoenix-api-development)
2. [OTP Patterns](#otp-patterns)
3. [Phoenix LiveView](#phoenix-liveview)
4. [Ecto Database Operations](#ecto-database-operations)
5. [Phoenix Channels](#phoenix-channels)
6. [Background Jobs (Oban)](#background-jobs-oban)
7. [Production Deployment](#production-deployment)
8. [Testing with ExUnit](#testing-with-exunit)
9. [Security Best Practices](#security-best-practices)
10. [Performance Optimization](#performance-optimization)
---
## Phoenix API Development
### RESTful Controller Pattern
```elixir
defmodule MyAppWeb.PostController do
use MyAppWeb, :controller
alias MyApp.Blog
alias MyApp.Blog.Post
# List all posts
def index(conn, _params) do
posts = Blog.list_posts()
render(conn, "index.json", posts: posts)
end
# Show single post
def show(conn, %{"id" => id}) do
post = Blog.get_post!(id)
render(conn, "show.json", post: post)
end
# Create post
def create(conn, %{"post" => post_params}) do
case Blog.create_post(post_params) do
{:ok, post} ->
conn
|> put_status(:created)
|> render("show.json", post: post)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render("error.json", changeset: changeset)
end
end
# Update post
def update(conn, %{"id" => id, "post" => post_params}) do
post = Blog.get_post!(id)
case Blog.update_post(post, post_params) do
{:ok, updated_post} ->
render(conn, "show.json", post: updated_post)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render("error.json", changeset: changeset)
end
end
# Delete post
def delete(conn, %{"id" => id}) do
post = Blog.get_post!(id)
{:ok, _post} = Blog.delete_post(post)
send_resp(conn, :no_content, "")
end
end
```
### Phoenix Context Pattern
```elixir
defmodule MyApp.Blog do
@moduledoc """
The Blog context - business logic for blog operations
"""
alias MyApp.Repo
alias MyApp.Blog.Post
import Ecto.Query
# List all posts with associations preloaded
def list_posts do
Post
|> preload(:author)
|> order_by(desc: :inserted_at)
|> Repo.all()
end
# Get single post with error handling
def get_post!(id) do
Post
|> preload(:author)
|> Repo.get!(id)
end
# Create post with validation
def create_post(attrs) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
end
# Update post
def update_post(%Post{} = post, attrs) do
post
|> Post.changeset(attrs)
|> Repo.update()
end
# Delete post
def delete_post(%Post{} = post) do
Repo.delete(post)
end
end
```
### Routing with Pipelines
```elixir
# router.ex
defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :api do
plug :accepts, ["json"]
end
pipeline :authenticated do
plug MyAppWeb.Auth.Pipeline
end
scope "/api", MyAppWeb do
pipe_through :api
# Public routes
post "/login", AuthController, :login
post "/register", AuthController, :register
# Authenticated routes
pipe_through :authenticated
resources "/posts", PostController
resources "/comments", CommentController
end
end
```
---
## OTP Patterns
### GenServer Pattern
```elixir
defmodule MyApp.Cache do
use GenServer
# Client API
def start_link(opts \\\\ []) do
GenServer.start_link(__MODULE__, %{}, opts)
end
def get(server, key) do
GenServer.call(server, {:get, key})
end
def put(server, key, value) do
GenServer.cast(server, {:put, key, value})
end
# Server Callbacks
@impl true
def init(_state) do
{:ok, %{}}
end
@impl true
def handle_call({:get, key}, _from, state) do
{:reply, Map.get(state, key), state}
end
@impl true
def handle_cast({:put, key, value}, state) do
{:noreply, Map.put(state, key, value)}
end
@impl true
def handle_info(:cleanup, state) do
# Periodic cleanup
{:noreply, %{}}
end
end
```
### Supervisor Tree
```elixir
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# Database connection pool
MyApp.Repo,
# PubSub for real-time features
{Phoenix.PubSub, name: MyApp.PubSub},
# Phoenix Endpoint
MyAppWeb.Endpoint,
# Custom GenServer
{MyApp.Cache, name: MyApp.Cache},
# Task Supervisor for async tasks
{Task.Supervisor, name: MyApp.TaskSupervisor},
# Oban for background jobs
{Oban, Application.fetch_env!(:my_app, Oban)}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
### Task for Async Operations
```elixir
# Fire and forget
Task.start(fn -> send_notification(user) end)
# Supervised task
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn ->
process_large_file(file_path)
end)
# Task with await
task = Task.async(fn -> fetch_external_data() end)
result = Task.await(task, 5000) # 5 second timeout
```
---
## Phoenix LiveView
### Basic LiveView Component
```elixir
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
# Subscribe to real-time updates
Phoenix.PubSub.subscribe(MyApp.PubSub, "metrics:updates")
# Schedule periodic refresh
:timer.send_interval(30_000, self(), :refresh)
end
{:ok, assign(socket, :metrics, get_metrics())}
end
@impl true
def render(assigns) do
~H"""
<div class="dashboard">
<h1>Dashboard</h1>
<.metrics_display metrics={@metrics} />
<button phx-click="refresh">Refresh</button>
</div>
"""
end
@impl true
def handle_event("refresh", _params, socket) do
{:noreply, assign(socket, :metrics, get_metrics())}
end
@impl true
def handle_info({:metric_update, new_metrics}, socket) do
{:noreply, assign(socket, :metrics, new_metrics)}
end
@impl true
def handle_info(:refresh, socket) do
{:noreply, assign(socket, :metrics, get_metrics())}
end
defp get_metrics do
# Fetch metrics from database or cache
end
end
```
### LiveView Form with Validation
```elixir
defmodule MyAppWeb.UserLive.Form do
use MyAppWeb, :live_view
alias MyApp.Accounts
alias MyApp.Accounts.User
@impl true
def mount(_params, _session, socket) do
changeset = Accounts.change_user(%User{})
{:ok, assign(socket, changeset: changeset)}
end
@impl true
def render(assigns) do
~H"""
<.form
for={@changeset}
phx-change="validate"
phx-submit="save"
>
<.input field={@changeset[:name]} label="Name" />
<.input field={@changeset[:email]} label="Email" type="email" />
<.button>Save</.button>
</.form>
"""
end
@impl true
def handle_event("validate", %{"user" => user_params}, socket) do
changeset =
%User{}
|> Accounts.change_user(user_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, changeset: changeset)}
end
@impl true
def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.create_user(user_params) do
{:ok, user} ->
{:noreply,
socket
|> put_flash(:info, "User created successfully")
|> redirect(to: ~p"/users/#{user}")}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
end
```
### LiveView Streams for Performance
```elixir
defmodule MyAppWeb.PostsLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> stream(:posts, MyApp.Blog.list_posts())Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.