Phoenix & LiveView Cheatsheet

Quick reference for Phoenix web framework and LiveView — routing, controllers, contexts, LiveView, and components.

Project Setup

Command Description
mix phx.new my_app Create new Phoenix project
mix phx.new my_app --no-ecto Without database
mix phx.new my_app --no-html API-only project
mix phx.new my_app --live With LiveView (default since 1.6)
mix phx.new my_app --umbrella Umbrella project
mix phx.server Start dev server
iex -S mix phx.server Start server with IEx

Generators

Command Description
mix phx.gen.html Blog Post posts title:string body:text HTML scaffold
mix phx.gen.json Blog Post posts title:string body:text JSON scaffold
mix phx.gen.live Blog Post posts title:string body:text LiveView scaffold
mix phx.gen.schema Blog Post posts title:string body:text Schema + migration only
mix phx.gen.context Blog Post posts title:string body:text Context + schema only
mix phx.gen.auth Authentication system
mix phx.routes Show all routes

Routing

# lib/my_app_web/router.ex
scope "/", MyAppWeb do
  pipe_through :browser

  get "/", PageController, :index
  get "/about", PageController, :about
  resources "/posts", PostController
  live "/dashboard", DashboardLive
  live "/users/:id", UserLive.Show
  live "/posts", PostLive.Index
  live "/posts/new", PostLive.New
  live "/posts/:id/edit", PostLive.Edit
end

# API scope
scope "/api", MyAppWeb.API do
  pipe_through :api

  resources "/posts", PostController, except: [:new, :edit]
end

Controller

defmodule MyAppWeb.PostController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    posts = Blog.list_posts()
    render(conn, :index, posts: posts)
  end

  def show(conn, %{"id" => id}) do
    post = Blog.get_post!(id)
    render(conn, :show, post: post)
  end

  def create(conn, %{"post" => post_params}) do
    case Blog.create_post(post_params) do
      {:ok, post} ->
        conn
        |> put_flash(:info, "Post created.")
        |> redirect(to: ~p"/posts/#{post}")
      {:error, changeset} ->
        render(conn, :new, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    post = Blog.get_post!(id)
    {:ok, _} = Blog.delete_post(post)

    conn
    |> put_flash(:info, "Post deleted.")
    |> redirect(to: ~p"/posts")
  end
end

LiveView

defmodule MyAppWeb.DashboardLive do
  use MyAppWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign(socket, count: 0, items: [])}
  end

  @impl true
  def handle_event("increment", _, socket) do
    {:noreply, update(socket, :count, &(&1 + 1))}
  end

  @impl true
  def handle_event("add_item", %{"name" => name}, socket) do
    {:noreply, update(socket, :items, &[name | &1])}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <h1>Count: <%= @count %></h1>
      <button phx-click="increment">+</button>
      <.simple_form for={@item_form} phx-submit="add_item">
        <.input field={@item_form[:name]} />
        <:actions><.button>Add</.button></:actions>
      </.simple_form>
      <ul>
        <%= for item <- @items do %>
          <li><%= item %></li>
        <% end %>
      </ul>
    </div>
    """
  end
end

LiveView Events & Bindings

Attribute Description
phx-click Click event
phx-submit Form submit event
phx-change Form input change event
phx-blur Blur event
phx-focus Focus event
phx-keydown Keydown event
phx-keyup Keyup event
phx-hook Attach a JS hook
phx-debounce Debounce events (ms or "blur")
phx-throttle Throttle events (ms)
phx-disable-with Disable element during request
phx-update DOM merge strategy

Verifying Routes (sigil ~p)

# Routes are verified at compile time
~p"/posts"           # => "/posts"
~p"/posts/#{post}"   # => "/posts/42"
~p"/posts?page=2"   # => "/posts?page=2"

Ecto Quick Reference

# Schema
schema "posts" do
  field :title, :string
  field :body, :text
  field :views, :integer, default: 0
  belongs_to :user, MyApp.Accounts.User
  has_many :comments, MyApp.Blog.Comment
  timestamps()
end

# Changeset
def changeset(post, attrs) do
  post
  |> cast(attrs, [:title, :body, :user_id])
  |> validate_required([:title, :body])
  |> validate_length(:title, min: 3, max: 200)
end

# Queries
import Ecto.Query

Post |> where([p], p.views > 100) |> Repo.all()
Post |> order_by(desc: :inserted_at) |> limit(10) |> Repo.all()
Post |> where(user_id: ^user_id) |> Repo.all()

Repo.get(Post, 42)
Repo.get_by!(Post, slug: "hello-world")
Repo.insert(%Post{title: "Hi"})
Repo.update(post_changeset)
Repo.delete(post)