TerminalSkills/skills

phoenix

Phoenix is an Elixir web framework built on the BEAM VM for fault-tolerant, real-time applications.

View source

How to use

  1. Copy the installation command and review the source package before installing it in your own agent environment.
  2. Check the source document for required tools, credentials and supported hosts.
  3. Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/TerminalSkills/skills --skill phoenix

Before you start

  • Installation does not include a model subscription, API allowance or permission to access your files.
  • Repository stars and installation counts describe activity, not an independent quality or security test.
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Phoenix

Overview

Phoenix leverages the Erlang VM (BEAM) for massive concurrency and fault tolerance. LiveView enables rich, real-time UIs with server-rendered HTML, with little custom JavaScript. This skill targets Phoenix 1.8 (1.8.16 checked) with LiveView 1.x and Ecto 3.

Instructions

Installation

bash
mix archive.install hex phx_new      # asks to confirm; add --force to skip the prompt
mix phx.new blog --database postgres # PostgreSQL is the default
cd blog
mix setup                            # deps.get, ecto.setup, assets.setup, assets.build
mix phx.server                       # http://localhost:4000 (dev server binds to localhost)

On Linux install inotify-tools for live reload. The examples below use the app name my_app; substitute your own.

Ecto Schema and Migration

elixir
# lib/my_app/articles/article.ex — Ecto schema
defmodule MyApp.Articles.Article do
  use Ecto.Schema
  import Ecto.Changeset

  schema "articles" do
    field :title, :string
    field :slug, :string
    field :body, :string
    field :published, :boolean, default: false
    belongs_to :author, MyApp.Accounts.User
    timestamps()
  end

  def changeset(article, attrs) do
    article
    |> cast(attrs, [:title, :body, :published, :author_id])
    |> validate_required([:title, :body, :author_id])
    |> validate_length(:title, max: 200)
    |> unique_constraint(:slug)
    |> generate_slug()
  end

  defp generate_slug(changeset) do
    case get_change(changeset, :title) do
      nil -> changeset
      title -> put_change(changeset, :slug, slugify(title))
    end
  end

  defp slugify(title) do
    title |> String.downcase() |> String.replace(~r/[^a-z0-9]+/u, "-") |> String.trim("-")
  end
end
elixir
# priv/repo/migrations/20261024120000_create_articles.exs — migration
defmodule MyApp.Repo.Migrations.CreateArticles do
  use Ecto.Migration

  def change do
    create table(:articles) do
      add :title, :string, null: false, size: 200
      add :slug, :string, null: false
      add :body, :text, null: false
      add :published, :boolean, default: false
      add :author_id, references(:users, on_delete: :delete_all), null: false
      timestamps()
    end

    create unique_index(:articles, [:slug])
  end
end

Context Module

elixir
# lib/my_app/articles.ex
defmodule MyApp.Articles do
  import Ecto.Query
  alias MyApp.Repo
  alias MyApp.Articles.Article

  def list_published do
    Article
    |> where(published: true)
    |> order_by(desc: :inserted_at)
    |> preload(:author)
    |> Repo.all()
  end

  def get_published_by_slug!(slug) do
    Article |> where(slug: ^slug, published: true) |> preload(:author) |> Repo.one!()
  end

  def search(term) do
    Article
    |> where([a], a.published and ilike(a.title, ^"%#{term}%"))
    |> order_by(desc: :inserted_at)
    |> preload(:author)
    |> Repo.all()
  end

  def create_article(attrs) do
    %Article{}
    |> Article.changeset(attrs)
    |> Repo.insert()
  end
end

Router

elixir
# lib/my_app_web/router.ex — routing
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  # pipeline :browser (session, flash, root layout, CSRF) is generated by mix phx.new
  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", MyAppWeb do
    pipe_through :browser
    live "/articles", ArticleLive.Index, :index
    live "/articles/:slug", ArticleLive.Show, :show   # Show: see mix phx.gen.live
  end

  scope "/api", MyAppWeb.API do
    pipe_through :api
    resources "/articles", ArticleController, only: [:index, :create]
  end
end

LiveView

elixir
# lib/my_app_web/live/article_live/index.ex
defmodule MyAppWeb.ArticleLive.Index do
  use MyAppWeb, :live_view
  alias MyApp.Articles
  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign(socket, articles: Articles.list_published(), search: "")}
  end

  @impl true
  def handle_event("search", %{"query" => query}, socket) do
    {:noreply, assign(socket, articles: Articles.search(query), search: query)}
  end
end
heex
<div>
  <h1>Articles</h1>
  <form phx-change="search">
    <input type="text" name="query" value={@search} phx-debounce="300" placeholder="Search..." />
  </form>
  <div :for={article <- @articles}>
    <h2><.link navigate={~p"/articles/#{article.slug}"}>{article.title}</.link></h2>
    <p>By {article.author.email}</p>
  </div>
</div>

JSON Controller

elixir
# lib/my_app_web/controllers/api/article_controller.ex — API controller
defmodule MyAppWeb.API.ArticleController do
  use MyAppWeb, :controller
  alias MyApp.Articles

  def index(conn, _params) do
    json(conn, %{data: Enum.map(Articles.list_published(), &article_json/1)})
  end

  def create(conn, %{"article" => params}) do
    case Articles.create_article(params) do
      {:ok, article} -> conn |> put_status(:created) |> json(%{data: article_json(article)})
      {:error, changeset} -> conn |> put_status(422) |> json(%{errors: format_errors(changeset)})
    end
  end

  defp article_json(a), do: %{id: a.id, title: a.title, slug: a.slug}

  defp format_errors(changeset) do
    Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
  end
end

Channels (Real-time)

elixir
# lib/my_app_web/channels/user_socket.ex — authenticate before any channel joins
defmodule MyAppWeb.UserSocket do
  use Phoenix.Socket
  channel "room:*", MyAppWeb.RoomChannel

  @impl true
  def connect(%{"token" => token}, socket, _connect_info) do
    case Phoenix.Token.verify(MyAppWeb.Endpoint, "user socket", token, max_age: 86_400) do
      {:ok, user_id} -> {:ok, assign(socket, :user_id, user_id)}
      {:error, _} -> :error
    end
  end

  def connect(_params, _socket, _connect_info), do: :error

  @impl true
  def id(socket), do: "user_socket:#{socket.assigns.user_id}"
end

# lib/my_app_web/channels/room_channel.ex
defmodule MyAppWeb.RoomChannel do
  use MyAppWeb, :channel

  def join("room:" <> _room_id, _params, socket), do: {:ok, socket}

  def handle_in("message", %{"body" => body}, socket) do
    broadcast!(socket, "message", %{body: body, user: socket.assigns.user_id})
    {:noreply, socket}
  end
end

Register the socket in endpoint.ex: socket "/socket", MyAppWeb.UserSocket, websocket: true, longpoll: false.

Testing

elixir
defmodule MyApp.ArticlesTest do
  use MyApp.DataCase
  import MyApp.AccountsFixtures   # created by `mix phx.gen.auth Accounts User users`

  test "create_article/1 builds a slug" do
    attrs = %{title: "Launch notes", body: "Content", author_id: user_fixture().id}
    assert {:ok, article} = MyApp.Articles.create_article(attrs)
    assert article.slug == "launch-notes"
  end
end

Key Commands

mix phx.gen.live Articles Article articles title:string body:text generates a LiveView CRUD with migration and tests; mix ecto.migrate, mix phx.routes, mix test, iex -S mix phx.server (dev server with REPL), mix phx.gen.secret (value for SECRET_KEY_BASE).

Examples

Example 1: "Add a blog section with search"

Run mix phx.gen.auth Accounts User users for authors, add the schema, migration and context above, run mix ecto.migrate, and add the live routes. Typing in the search box sends a search event (debounced 300 ms) and the list re-renders without a page load.

Example 2: "Give me a JSON endpoint for mobile"

Add the /api scope and ArticleController. curl -s -X POST localhost:4000/api/articles -H 'content-type: application/json' -d '{"article":{"title":"Launch notes","body":"Hello","author_id":1}}' returns 201 with {"data":{"id":1,"title":"Launch notes","slug":"launch-notes"}}; a missing title returns 422 with {"errors":{"title":["can't be blank"]}}.

Guidelines

  • Production config is read from config/runtime.exs: set SECRET_KEY_BASE, DATABASE_URL, PHX_HOST and PHX_SERVER=true as environment variables; build with MIX_ENV=prod mix release.
  • Authenticate sockets and check authorization in every handle_event / handle_in; clients can send any event name and payload.
  • Pass only whitelisted fields to cast/3 (never cast(attrs, Map.keys(attrs))) and never cast privileged fields such as is_admin.
  • Keep the :browser pipeline's CSRF protection; skip it only in :api pipelines that authenticate with tokens.
  • Use phx-debounce on inputs that fire on every keystroke, and stream/3 instead of assigning large lists to the socket.

Key Patterns

  • Business logic lives in context modules; controllers and LiveViews only delegate to them.
  • Validate through changesets; Ecto never lazy-loads, so preload explicitly.
  • Use Ecto.Multi for multi-step transactions and Phoenix.PubSub.broadcast to push updates across LiveView processes.
from this repository

More skills

All skills
TerminalSkills
Community

3dsmax-rendering

Scripts and configures production rendering in Autodesk 3ds Max with the V-Ray and Corona renderers: output size and files, render elements, denoising, light mix, batch and command-line rendering, and network rendering. Use when a user asks to set up a production render, render several cameras in one batch, render from the command line or on a render farm, add render passes for compositing, or cut render time for archviz and product shots.

installs
0
GitHub stars
180
Updated
Oct 25
TerminalSkills
Community

3dsmax-scripting

Covers scripting Autodesk 3ds Max, the 3D modeling and rendering application, with MAXScript and Python (pymxs): scene manipulation, object creation, material assignment, camera and light setup, batch operations, and file I/O. Use when tasks involve automating repetitive 3ds Max workflows, batch processing scenes, running scripts headless with 3dsmaxbatch, creating custom tools, or scripting scene setup for archviz, product visualization, or VFX.

installs
0
GitHub stars
180
Updated
Oct 25
TerminalSkills
Community

3proxy

3proxy is a small open-source proxy server that runs HTTP/HTTPS, SOCKS4/5, SNI and TCP/UDP port-mapping proxies from one config file. Use when a user asks to set up an HTTP or SOCKS5 proxy, add proxy users and passwords, write 3proxy access rules, chain or rotate upstream (parent) proxies, limit bandwidth, connections or monthly traffic per user, run 3proxy in Docker, or fix a 3proxy.cfg that will not start.

installs
0
GitHub stars
180
Updated
Oct 25
TerminalSkills
Community

a2a-protocol

Builds Agent2Agent (A2A) servers and clients, the open protocol (originally from Google, now under the Linux Foundation) that lets AI agents from different frameworks call each other. Use when the user wants to create an A2A-compliant agent, build an Agent Card, implement task management, connect agents across frameworks, set up agent discovery, handle streaming responses, implement push notifications, or orchestrate multi-agent workflows. Trigger words: a2a, agent to agent, agent2agent, a2a pro

installs
0
GitHub stars
180
Updated
Oct 25