Phoenix Framework and LiveView development skill for building production-ready Elixir web applications. Use when working with Phoenix 1.7+, LiveView, Ecto schemas, contexts, migrations, controllers, or any Elixir/Phoenix development task. Triggers on requests involving (1) Creating or editing LiveViews, LiveComponents, or function components, (2) Writing Ecto schemas, changesets, or migrations, (3) Implementing Phoenix contexts with CRUD operations, (4) Building real-time features with PubSub, (5) API development with controllers and JSON views, (6) Authentication and authorization patterns, (7) Testing Phoenix applications.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Phoenix Framework and LiveView development skill for building production-ready Elixir web applications. Use when working with Phoenix 1.7+, LiveView, Ecto schemas, contexts, migrations, controllers, or any Elixir/Phoenix development task. Triggers on requests involving (1) Creating or editing LiveViews, LiveComponents, or function components, (2) Writing Ecto schemas, changesets, or migrations, (3) Implementing Phoenix contexts with CRUD operations, (4) Building real-time features with PubSub, (5) API development with controllers and JSON views, (6) Authentication and authorization patterns, (7) Testing Phoenix applications.
Phoenix LiveView Development
Production-ready patterns for Phoenix 1.7+, LiveView, and Ecto.
defmodule MyApp.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :body, :text
field :status, Ecto.Enum, values: [:draft, :published], default: :draft
field :deleted_at, :utc_datetime
belongs_to :user, MyApp.Accounts.User
has_many :comments, MyApp.Blog.Comment
has_many :likes, MyApp.Blog.PostLike
has_many :likers, through: [:likes, :user]
timestamps(type: :utc_datetime)
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body, :status, :user_id])
|> validate_required([:title, :body, :user_id])
|> validate_length(:title, min: 3, max: 255)
|> foreign_key_constraint(:user_id)
end
end
Conventions:
Ecto.Enum for status fields with defined values
timestamps(type: :utc_datetime) always
Soft delete with deleted_at field
@doc false for changeset unless documented
has_many :through for virtual associations
Context Pattern
defmodule MyApp.Blog do
import Ecto.Query, warn: false
alias MyApp.Repo
alias MyApp.Blog.Post
# List - exclude soft-deleted
def list_posts do
from(p in Post, where: is_nil(p.deleted_at))
|> Repo.all()
|> Repo.preload([:user])
end
# IMPORTANT: Batched counts to prevent N+1 queries
# When displaying counts for a list, NEVER query per-item
def counts_by_post_ids(post_ids) when is_list(post_ids) do
from(c in Comment,
where: c.post_id in ^post_ids,
group_by: c.post_id,
select: {c.post_id, count(c.id)}
)
|> Repo.all()
|> Map.new()
end
def get_post!(id) do
Repo.get!(Post, id)
|> Repo.preload([:user, :comments])
end
def create_post(attrs \\ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
end
# Ownership validation pattern
def update_post_with_ownership(%Post{} = post, attrs, user_id) do
cond do
post.user_id != user_id -> {:error, :unauthorized}
post.status == :archived -> {:error, :archived}
true -> update_post(post, attrs)
end
end
def update_post(%Post{} = post, attrs) do
post
|> Post.changeset(attrs)
|> Repo.update()
end
def soft_delete_post(%Post{} = post) do
post
|> Post.changeset(%{deleted_at: DateTime.utc_now()})
|> Repo.update()
end
def change_post(%Post{} = post, attrs \\ %{}) do
Post.changeset(post, attrs)
end
# Self-interaction prevention
def like_post(post_id, user_id) do
post = get_post!(post_id)
if post.user_id == user_id do
{:error, :cannot_like_own_post}
else
# insert like
end
end
end
defmodule MyApp.Repo.Migrations.CreatePosts do
use Ecto.Migration
def change do
create table(:posts) do
add :title, :string, null: false
add :body, :text
add :status, :string, default: "draft"
add :deleted_at, :utc_datetime
add :user_id, references(:users, on_delete: :delete_all), null: false
timestamps(type: :utc_datetime)
end
create index(:posts, [:user_id])
create index(:posts, [:status])
create index(:posts, [:deleted_at])
end
end
Join table:
def change do
create table(:post_likes) do
add :post_id, references(:posts, on_delete: :delete_all), null: false
add :user_id, references(:users, on_delete: :delete_all), null: false
timestamps(type: :utc_datetime)
end
create index(:post_likes, [:post_id])
create index(:post_likes, [:user_id])
create unique_index(:post_likes, [:post_id, :user_id])
end
on_delete options:
:delete_all - Cascade delete owned resources
:nilify_all - Set NULL for optional references
:restrict - Prevent deletion if references exist
Router Pattern
defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
# Public routes
scope "/", MyAppWeb do
pipe_through :browser
live_session :public,
on_mount: [{MyAppWeb.UserAuth, :mount_current_user}] do
live "/posts", PostLive.Index, :index
live "/posts/:id", PostLive.Show, :show
end
end
# Authenticated routes
scope "/", MyAppWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :authenticated,
on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated}] do
live "/posts/new", PostLive.Index, :new
live "/posts/:id/edit", PostLive.Show, :edit
end
end
# Admin routes
scope "/admin", MyAppWeb.Admin do
pipe_through [:browser, :require_authenticated_user, :require_admin]
live_session :admin,
on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated},
{MyAppWeb.AdminAuth, :ensure_admin}] do
live "/users", UserLive.Index
end
end
end
Testing Pattern
defmodule MyApp.BlogTest do
use MyApp.DataCase
alias MyApp.Blog
describe "posts" do
test "list_posts/0 returns all posts" do
post = post_fixture()
assert Blog.list_posts() == [post]
end
test "create_post/1 with valid data" do
user = user_fixture()
attrs = %{title: "Title", body: "Body", user_id: user.id}
assert {:ok, %Post{title: "Title"}} = Blog.create_post(attrs)
end
test "update_post_with_ownership/3 unauthorized" do
post = post_fixture()
other_user = user_fixture()
assert {:error, :unauthorized} =
Blog.update_post_with_ownership(post, %{title: "New"}, other_user.id)
end
end
end
LiveView test:
defmodule MyAppWeb.PostLiveTest do
use MyAppWeb.ConnCase
import Phoenix.LiveViewTest
test "renders posts", %{conn: conn} do
post = post_fixture()
{:ok, view, html} = live(conn, ~p"/posts")
assert html =~ post.title
assert has_element?(view, "h2", post.title)
end
test "deletes post", %{conn: conn, user: user} do
post = post_fixture(user_id: user.id)
{:ok, view, _html} = live(conn, ~p"/posts")
assert view
|> element("button", "Delete")
|> render_click()
refute has_element?(view, "h2", post.title)
end
end
Quick Reference
Mix Commands
mix phx.new my_app # New project
mix phx.server # Start server
iex -S mix phx.server # Start with IEx
mix ecto.create # Create database
mix ecto.migrate # Run migrations
mix ecto.rollback # Rollback last migration
mix ecto.gen.migration name # Generate migration
mix phx.gen.live Blog Post posts # Generate LiveView CRUD
mix phx.gen.auth Accounts User users# Auth system
mix test# Run tests
mix format # Format code