Phoenix
Phoenix is Elixir's flagship web framework: fast, productive, real-time.
45.1 Getting started
mix archive.install hex phx_new
mix phx.new blog # --no-ecto, --database sqlite3, --no-html, --live, --binary-id
cd blog
mix ecto.create
mix phx.server # or iex -S mix phx.server → http://localhost:400045.2 Project structure
lib/
├── blog/ # business logic ("contexts"), Ecto schemas, Repo
│ ├── application.ex
│ ├── repo.ex
│ └── posts.ex # context module
└── blog_web/ # web interface
├── endpoint.ex
├── router.ex
├── controllers/
├── components/ # function components, layouts, core_components.ex
└── live/ # LiveViews
priv/repo/migrations/
assets/ # JS, CSS (esbuild + tailwind)
config/
test/45.3 Request lifecycle
Endpoint (plugs: static, logger, parsers, session) → Router (pipelines + routes) → Controller action → View/Component → response.
45.4 Plug — the composable HTTP abstraction
Plug.Conn functions: assign/3, put_status/2, put_resp_header/3, send_resp/3, halt/1, fetch_session/2, get_session/2, put_session/3.
defmodule BlogWeb.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, "Login required") |> redirect(to: "/login") |> halt()
end
end
end45.5 Router
mix phx.routes lists routes. Verified routes: ~p"/posts/#{post}" are checked at compile time.
defmodule BlogWeb.Router do
use BlogWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {BlogWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api, do: plug(:accepts, ["json"])
scope "/", BlogWeb do
pipe_through :browser
get "/", PageController, :home
resources "/posts", PostController # index/show/new/create/edit/update/delete
live "/dashboard", DashboardLive
end
scope "/api", BlogWeb.API do
pipe_through :api
resources "/posts", PostController, only: [:index, :show]
end
end45.6 Controllers
defmodule BlogWeb.PostController do
use BlogWeb, :controller
alias Blog.Posts
def index(conn, _params), do: render(conn, :index, posts: Posts.list_posts())
def create(conn, %{"post" => params}) do
case Posts.create_post(params) do
{:ok, post} ->
conn |> put_flash(:info, "Created!") |> redirect(to: ~p"/posts/#{post}")
{:error, changeset} ->
render(conn, :new, changeset: changeset)
end
end
end
# JSON API
def show(conn, %{"id" => id}), do: json(conn, %{data: Posts.get_post!(id)})45.7 Templates & components (HEEx)
HEEx validates HTML at compile time; {@x} (or <%= @x %>) interpolates with automatic escaping. Slots: slot :inner_block, render_slot(@inner_block).
defmodule BlogWeb.PostHTML do
use BlogWeb, :html
embed_templates "post_html/*"
attr :post, :map, required: true
def post_card(assigns) do
~H"""
<div class="card">
<h2>{@post.title}</h2>
<p :if={@post.published}>Published</p>
<ul><li :for={tag <- @post.tags}>{tag}</li></ul>
<.link navigate={~p"/posts/#{@post}"}>Read more</.link>
</div>
"""
end
end45.8 Contexts & generators
Contexts group related functionality (Accounts, Catalog), hiding Ecto details from the web layer.
mix phx.gen.html Posts Post posts title:string body:text published:boolean
mix phx.gen.json Accounts User users email:string:unique
mix phx.gen.live Catalog Product products name:string price:decimal
mix phx.gen.context / phx.gen.schema
mix phx.gen.auth Accounts User users # complete authentication system45.9 Channels & PubSub
Phoenix.Presence tracks who is online across a cluster (CRDT-based).
# Server
defmodule BlogWeb.RoomChannel do
use Phoenix.Channel
def join("room:" <> _id, _params, socket), do: {:ok, socket}
def handle_in("new_msg", %{"body" => body}, socket) do
broadcast!(socket, "new_msg", %{body: body})
{:noreply, socket}
end
end
# Anywhere
Phoenix.PubSub.subscribe(Blog.PubSub, "posts")
Phoenix.PubSub.broadcast(Blog.PubSub, "posts", {:post_created, post})45.10 Testing Phoenix
DataCase wraps each test in a sandboxed DB transaction (Ecto.Adapters.SQL.Sandbox) — tests run concurrently.
defmodule BlogWeb.PostControllerTest do
use BlogWeb.ConnCase
test "GET /posts", %{conn: conn} do
conn = get(conn, ~p"/posts")
assert html_response(conn, 200) =~ "Listing Posts"
end
end45.11 Security features
CSRF protection, secure headers, HTML escaping, parameterized SQL via Ecto, Plug.Crypto, password hashing (bcrypt_elixir, argon2_elixir), Sobelow for audits.