Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
91
91%
Does it follow best practices?
Impact
91%
1.37xAverage score across 56 eval scenarios
Advisory
Suggest reviewing before use
:api pipeline — don't mix HTML and JSON pipelines; API routes skip CSRF and sessions{:error, changeset} must become {"errors": {...}}/api/v1/) — not headers; URL versioning is visible and cacheableFallbackController for consistent error handling — every action returns {:ok, result} or {:error, reason}Follow these steps in order when constructing a new API endpoint:
:api (or :api_auth) pipeline scope with a versioned URL prefixaction_fallback MyAppWeb.FallbackController and return {:ok, _} / {:error, _} from every action{"errors": {...}}) before proceedingApiAuth) to protected scopes; confirm that missing/invalid tokens yield 401 with a JSON bodyindex accepts page/per_page params and never returns an unbounded collection❌ Bad — one mixed pipeline (drags CSRF/session into JSON) and header-based versioning:
scope "/api", MyAppWeb do
pipe_through :browser # CSRF + session cookies on a JSON API
# version selected from a header — invisible and hard to cache
resources "/posts", PostController
end✅ Good — a dedicated :api pipeline with a versioned URL prefix:
defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :api do
plug :accepts, ["json"]
end
pipeline :api_auth do
plug MyAppWeb.Plugs.ApiAuth
end
# Public endpoints
scope "/api/v1", MyAppWeb.API.V1, as: :api_v1 do
pipe_through :api
post "/auth/login", AuthController, :login
post "/auth/register", AuthController, :register
end
# Protected endpoints
scope "/api/v1", MyAppWeb.API.V1, as: :api_v1 do
pipe_through [:api, :api_auth]
resources "/posts", PostController, except: [:new, :edit]
end
end❌ Bad — no action_fallback, error handling duplicated inline, unstructured error body:
def create(conn, %{"post" => post_params}) do
case Blog.create_post(post_params) do
{:ok, post} ->
json(conn, %{data: post})
{:error, _changeset} ->
# plain string, no status control, inconsistent shape
json(conn, %{error: "could not create"})
end
end✅ Good — action_fallback handles every {:error, _}; actions stay focused on the happy path:
defmodule MyAppWeb.API.V1.PostController do
use MyAppWeb, :controller
alias MyApp.Blog
alias MyApp.Blog.Post
action_fallback MyAppWeb.FallbackController
def index(conn, params) do
page = Map.get(params, "page", "1") |> String.to_integer()
per_page = Map.get(params, "per_page", "20") |> String.to_integer() |> min(100)
{posts, total} = Blog.list_posts(page: page, per_page: per_page)
conn
|> put_resp_header("x-total-count", to_string(total))
|> json(%{
data: Enum.map(posts, &post_json/1),
meta: %{page: page, per_page: per_page, total: total}
})
end
def create(conn, %{"post" => post_params}) do
with {:ok, %Post{} = post} <- Blog.create_post(post_params) do
conn
|> put_status(:created)
|> put_resp_header("location", ~p"/api/v1/posts/#{post}")
|> json(%{data: post_json(post)})
end
end
defp post_json(post) do
%{
id: post.id,
title: post.title,
body: post.body,
inserted_at: post.inserted_at
}
end
enddefmodule MyAppWeb.FallbackController do
use MyAppWeb, :controller
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> json(%{errors: %{detail: "Resource not found"}})
end
def call(conn, {:error, :unauthorized}) do
conn
|> put_status(:unauthorized)
|> json(%{errors: %{detail: "Not authorized"}})
end
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> json(%{errors: format_changeset_errors(changeset)})
end
defp format_changeset_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
end
enddefmodule MyAppWeb.Plugs.ApiAuth do
import Plug.Conn
import Phoenix.Controller
def init(opts), do: opts
def call(conn, _opts) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
{:ok, user} <- MyApp.Accounts.get_user_by_api_token(token) do
assign(conn, :current_user, user)
else
_ ->
conn
|> put_status(:unauthorized)
|> json(%{errors: %{detail: "Invalid or missing token"}})
|> halt()
end
end
end| ❌ Don't | ✅ Do |
|---|---|
Route JSON through the :browser pipeline | Use a dedicated :api pipeline (plug :accepts, ["json"]) |
| Version via a custom header | Version via URL prefix (/api/v1/) — visible and cacheable |
Handle {:error, _} inline in every action | Set action_fallback and return {:ok, _}/{:error, _} |
Return changeset or a bare string on error | Render structured JSON (%{errors: %{...}}) with a proper status |
Return an unbounded index collection | Paginate with page/per_page, cap per_page |
| Trust or echo a missing/invalid token | Reject in the auth plug with 401 + JSON body and halt() |
| Predecessor | This Skill | Successor |
|---|---|---|
| elixir-essentials | phoenix-json-api | testing-essentials |
| security-essentials | phoenix-json-api | req-http-client |
.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
skills
frameworks
ash-framework
infrastructure
orchestration
elixir-skill-router
personas
phoenix
quality
security
security-essentials
tooling
mix-tasks-generators