Controllers
Phoenix controllers — actions, params, responses, plugs, and error handling
Controllers handle HTTP requests and return responses. They are Elixir modules with action functions that receive a Plug.Conn struct and params.
Basic 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
endActions
| Action | HTTP verb | Purpose |
|---|---|---|
:index |
GET | List all resources |
:new |
GET | Show creation form |
:create |
POST | Save new resource |
:show |
GET | Display single resource |
:edit |
GET | Show edit form |
:update |
PATCH/PUT | Save changes |
:delete |
DELETE | Remove resource |
Params
# Path params
def show(conn, %{"id" => id}) do
post = Blog.get_post!(id)
render(conn, :show, post: post)
end
# Query params
def index(conn, %{"page" => page}) do
posts = Blog.list_posts(page: page)
render(conn, :index, posts: posts)
end
# All params available
def create(conn, params) do
# params = %{"post" => %{"title" => "...", "body" => "..."}, "_csrf_token" => "..."}
...
endRendering responses
# Render a template with assigns
render(conn, :index, posts: posts)
# Render with status code
conn
|> put_status(:created)
|> render(:show, post: post)
# JSON response
json(conn, %{id: post.id, title: post.title})
# Plain text
text(conn, "OK")
# HTML directly
html(conn, "<h1>Hello</h1>")Redirecting
# To a verified route
redirect(conn, to: ~p"/posts")
# External URL
redirect(conn, external: "https://example.com")
# With flash message
conn
|> put_flash(:info, "Post created successfully.")
|> redirect(to: ~p"/posts")Flash messages
# In controller
conn
|> put_flash(:info, "Operation succeeded.")
|> put_flash(:error, "Something went wrong.")
# In templates (HEEx)
<p class="alert alert-info"><%= flash[:info] %></p>
<p class="alert alert-error"><%= flash[:error] %></p>Plug pipeline
Plugs are composable middleware that transform the connection:
defmodule MyAppWeb.Plugs.RequireAuth do
import Plug.Conn
import Phoenix.Controller
def init(opts), do: opts
def call(conn, _opts) do
if conn.assigns[:current_user] do
conn
else
conn
|> put_flash(:error, "You must be logged in.")
|> redirect(to: ~p"/login")
|> halt()
end
end
endUsing plugs in controllers
defmodule MyAppWeb.DashboardController do
use MyAppWeb, :controller
plug :require_auth when action in [:index, :show]
plug :load_post when action in [:show, :edit, :update, :delete]
def index(conn, _params), do: ...
defp require_auth(conn, _) do
if conn.assigns[:current_user] do
conn
else
conn |> redirect(to: ~p"/login") |> halt()
end
end
defp load_post(conn, _) do
assign(conn, :post, Blog.get_post!(conn.params["id"]))
end
endAction fallbacks
Use action_fallback to handle common error patterns:
defmodule MyAppWeb.PostController do
use MyAppWeb, :controller
action_fallback MyAppWeb.FallbackController
def show(conn, %{"id" => id}) do
case Blog.get_post(id) do
{:ok, post} -> render(conn, :show, post: post)
{:error, :not_found} -> {:error, :not_found}
end
end
end
defmodule MyAppWeb.FallbackController do
use MyAppWeb, :controller
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> put_view(html: MyAppWeb.ErrorHTML, json: MyAppWeb.ErrorJSON)
|> render("404.html", [])
end
endError pages
Custom error pages are defined in ErrorHTML and ErrorJSON:
# lib/my_app_web/controllers/error_html.ex
defmodule MyAppWeb.ErrorHTML do
use MyAppWeb, :html
def render("404.html", _assigns) do
"Page not found"
end
def render("500.html", _assigns) do
"Internal server error"
end
end
# lib/my_app_web/controllers/error_json.ex
defmodule MyAppWeb.ErrorJSON do
def render("404.json", _assigns) do
%{errors: %{detail: "Not found"}}
end
endConn assigns
# Set assigns
conn = assign(conn, :title, "My Page")
conn = assign(conn, title: "My Page", user: user)
# Get assigns
conn.assigns[:title]
conn.assigns.title
# assign_new — only set if not already present
conn = assign_new(conn, :page_title, fn -> "Default" end)