Peace of mind from prototype to production - comprehensive web framework for Elixir
Phoenix's web foundation provides the core infrastructure for handling HTTP requests, routing URLs to controllers, and generating responses. The system is built on a plug-based architecture that processes requests through configurable pipelines.
The endpoint is the entry point for all web requests and defines the foundational configuration.
defmodule Phoenix.Endpoint do
# Supervision and lifecycle
@callback start_link(keyword) :: Supervisor.on_start()
@callback config(atom, term) :: term
@callback config_change(term, term) :: term
# URL and path generation
@callback url() :: String.t()
@callback struct_url() :: URI.t()
@callback path(String.t()) :: String.t()
@callback static_url() :: String.t()
@callback static_path(String.t()) :: String.t()
@callback static_integrity(String.t()) :: String.t() | nil
@callback host() :: String.t()
@callback script_name() :: [String.t()]
# Server information
@callback server_info(Plug.Conn.scheme()) :: {:ok, {String.t(), pos_integer}} | :error
# PubSub integration
@callback subscribe(binary, keyword) :: :ok | {:error, term}
@callback unsubscribe(binary) :: :ok | {:error, term}
@callback broadcast(binary, binary, term) :: :ok | {:error, term}
@callback broadcast!(binary, binary, term) :: :ok
@callback broadcast_from(pid, binary, binary, term) :: :ok | {:error, term}
@callback broadcast_from!(pid, binary, binary, term) :: :ok
@callback local_broadcast(binary, binary, term) :: :ok
@callback local_broadcast_from(pid, binary, binary, term) :: :ok
enddefmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
# Static file serving
plug Plug.Static,
at: "/", from: :my_app, gzip: false,
only: ~w(assets fonts images favicon.ico robots.txt)
# Session configuration
plug Plug.Session,
store: :cookie,
key: "_my_app_key",
signing_salt: "signing_salt"
# Router
plug MyAppWeb.Router
end
# Start endpoint in application supervisor
children = [
MyAppWeb.Endpoint
]
Supervisor.start_link(children, strategy: :one_for_one)Phoenix's router maps URLs to controller actions through a declarative DSL with support for pipelines, scoping, and resource routing.
defmodule Phoenix.Router do
# HTTP verb macros
defmacro get(path, plug, plug_opts \\ [], options \\ [])
defmacro post(path, plug, plug_opts \\ [], options \\ [])
defmacro put(path, plug, plug_opts \\ [], options \\ [])
defmacro patch(path, plug, plug_opts \\ [], options \\ [])
defmacro delete(path, plug, plug_opts \\ [], options \\ [])
defmacro options(path, plug, plug_opts \\ [], options \\ [])
defmacro head(path, plug, plug_opts \\ [], options \\ [])
defmacro match(verb_or_verbs, path, plug, plug_opts \\ [], options \\ [])
# Resource routing
defmacro resources(path, controller, opts \\ [])
defmacro resources(path, controller, opts, do_block)
# Scoping and organization
defmacro scope(options \\ [], do: context)
defmacro scope(path, options \\ [], do: context)
defmacro scope(path, alias, options \\ [], do: context)
# Pipeline system
defmacro pipeline(name, do: block)
defmacro plug(plug, opts \\ [])
defmacro pipe_through(pipes)
# Forwarding
defmacro forward(path, plug, plug_opts \\ [], router_opts \\ [])
enddefmodule MyAppWeb.Router do
use Phoenix.Router
# Define pipelines
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {MyAppWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
# Browser routes
scope "/", MyAppWeb do
pipe_through :browser
get "/", PageController, :index
resources "/users", UserController
resources "/posts", PostController, only: [:index, :show]
end
# API routes
scope "/api/v1", MyAppWeb do
pipe_through :api
resources "/users", UserController, except: [:new, :edit]
end
# Admin routes with scoping
scope "/admin", MyAppWeb.Admin, as: :admin do
pipe_through [:browser, :require_admin]
resources "/users", UserController
resources "/settings", SettingController
end
endControllers handle incoming requests, process parameters, interact with contexts, and render responses.
defmodule Phoenix.Controller do
# Request introspection
def action_name(Plug.Conn.t()) :: atom
def controller_module(Plug.Conn.t()) :: atom
def router_module(Plug.Conn.t()) :: atom
def endpoint_module(Plug.Conn.t()) :: atom
# Response rendering
def render(Plug.Conn.t(), binary | keyword) :: Plug.Conn.t()
def render(Plug.Conn.t(), binary, keyword) :: Plug.Conn.t()
def render(Plug.Conn.t(), atom, binary, keyword) :: Plug.Conn.t()
# Format-specific responses
def json(Plug.Conn.t(), term) :: Plug.Conn.t()
def text(Plug.Conn.t(), iodata) :: Plug.Conn.t()
def html(Plug.Conn.t(), iodata) :: Plug.Conn.t()
def redirect(Plug.Conn.t(), keyword) :: Plug.Conn.t()
# View and layout management
def put_view(Plug.Conn.t(), atom | keyword) :: Plug.Conn.t()
def put_new_view(Plug.Conn.t(), atom | keyword) :: Plug.Conn.t()
def view_module(Plug.Conn.t(), atom | nil) :: atom | nil
def put_layout(Plug.Conn.t(), binary | false | {atom, binary}) :: Plug.Conn.t()
def put_new_layout(Plug.Conn.t(), binary | false | {atom, binary}) :: Plug.Conn.t()
def put_root_layout(Plug.Conn.t(), binary | false | {atom, binary}) :: Plug.Conn.t()
def layout(Plug.Conn.t(), atom | nil) :: binary | false | {atom, binary} | nil
def root_layout(Plug.Conn.t(), atom | nil) :: binary | false | {atom, binary} | nil
# URL helpers
def put_router_url(Plug.Conn.t(), binary) :: Plug.Conn.t()
def put_static_url(Plug.Conn.t(), binary) :: Plug.Conn.t()
def current_path(Plug.Conn.t()) :: binary
def current_path(Plug.Conn.t(), map | keyword) :: binary
def current_url(Plug.Conn.t()) :: binary
# Content negotiation
def accepts(Plug.Conn.t(), [binary]) :: Plug.Conn.t()
def put_format(Plug.Conn.t(), atom) :: Plug.Conn.t()
def get_format(Plug.Conn.t()) :: atom
# Security
def protect_from_forgery(Plug.Conn.t(), keyword) :: Plug.Conn.t()
def put_secure_browser_headers(Plug.Conn.t(), map) :: Plug.Conn.t()
def scrub_params(Plug.Conn.t(), binary) :: Plug.Conn.t()
# Flash messages
def fetch_flash(Plug.Conn.t(), keyword) :: Plug.Conn.t()
def put_flash(Plug.Conn.t(), atom | binary, term) :: Plug.Conn.t()
def get_flash(Plug.Conn.t()) :: map
def get_flash(Plug.Conn.t(), atom | binary) :: term
def clear_flash(Plug.Conn.t()) :: Plug.Conn.t()
def merge_flash(Plug.Conn.t(), Enumerable.t()) :: Plug.Conn.t()
# File downloads
def send_download(Plug.Conn.t(), atom, keyword) :: Plug.Conn.t()
# JSONP support
def allow_jsonp(Plug.Conn.t(), keyword) :: Plug.Conn.t()
enddefmodule MyAppWeb.UserController do
use Phoenix.Controller, formats: [:html, :json]
def index(conn, _params) do
users = MyApp.Accounts.list_users()
render(conn, "index.html", users: users)
end
def show(conn, %{"id" => id}) do
case MyApp.Accounts.get_user(id) do
nil ->
conn
|> put_flash(:error, "User not found")
|> redirect(to: "/users")
user ->
case get_format(conn) do
"html" -> render(conn, "show.html", user: user)
"json" -> json(conn, %{user: user})
end
end
end
def create(conn, %{"user" => user_params}) do
case MyApp.Accounts.create_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully")
|> redirect(to: "/users/#{user.id}")
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
user = MyApp.Accounts.get_user!(id)
{:ok, _user} = MyApp.Accounts.delete_user(user)
conn
|> put_flash(:info, "User deleted successfully")
|> redirect(to: "/users")
end
endPhoenix provides compile-time route verification using the ~p sigil.
defmodule Phoenix.VerifiedRoutes do
defmacro sigil_p(path, modifiers)
def path(Plug.Conn.t() | Phoenix.Socket.t(), binary) :: binary
def url(Plug.Conn.t() | Phoenix.Socket.t(), binary) :: binary
end# In controllers, views, templates
def show(conn, %{"id" => id}) do
user = MyApp.Accounts.get_user!(id)
redirect(conn, to: ~p"/users/#{user}")
end
# In templates
<%= link "View User", to: ~p"/users/#{@user}" %>
<%= link "Edit", to: ~p"/users/#{@user}/edit" %>
# With query parameters
~p"/search?#{[q: @query, page: @page]}"
# Static paths (verified at compile time)
~p"/assets/app.css"# config/config.exs
config :my_app, MyAppWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "your_secret_key_base",
render_errors: [view: MyAppWeb.ErrorView, accepts: ~w(html json), layout: false],
pubsub_server: MyApp.PubSub,
live_view: [signing_salt: "your_signing_salt"],
server: true# Enable helpers in controllers and views
use Phoenix.Router, helpers: false # Disable route helpers
# Custom pipeline plugs
pipeline :auth do
plug MyAppWeb.Plugs.RequireAuth
plug MyAppWeb.Plugs.LoadCurrentUser
end# Custom errors
defmodule Phoenix.Router.NoRouteError do
@moduledoc "Exception raised when no route is found"
defexception [:conn, :router]
end
defmodule Phoenix.MissingRequiredKeys do
@moduledoc "Exception raised when required configuration is missing"
defexception [:keys]
end
# In controllers
def show(conn, %{"id" => id}) do
case MyApp.Accounts.get_user(id) do
nil ->
raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
user ->
render(conn, "show.html", user: user)
end
endInstall with Tessl CLI
npx tessl i tessl/hex-phoenix