Routing

Phoenix router — scopes, pipelines, resources, verified routes, and helpers

The Phoenix router maps HTTP requests to controller actions or LiveView modules. It is defined in lib/my_app_web/router.ex.

Basic routing

defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", MyAppWeb do
    pipe_through :browser

    get "/", PageController, :index
  end
end

Pipelines

Pipelines are stacks of plugs applied to requests. Common built-in plugs:

Plug Purpose
:accepts Content negotiation
:fetch_session Load session data
:fetch_live_flash Merge flash for LiveView
:put_root_layout Set the root layout
:protect_from_forgery CSRF protection
:put_secure_browser_headers Security headers (X-Frame-Options, etc.)
:fetch_flash Load flash messages

HTTP verbs

get "/posts", PostController, :index
get "/posts/new", PostController, :new
get "/posts/:id", PostController, :show
post "/posts", PostController, :create
put "/posts/:id", PostController, :update
patch "/posts/:id", PostController, :update
delete "/posts/:id", PostController, :delete

Resources

resources generates all RESTful routes at once:

resources "/posts", PostController
# Generates: index, new, create, show, edit, update, delete

resources "/posts", PostController, only: [:index, :show]
resources "/posts", PostController, except: [:delete]

# Nested resources
resources "/users", UserController do
  resources "/posts", PostController
end
# => /users/:user_id/posts, /users/:user_id/posts/:id, etc.

Resource routes generated

Verb Path Action Helper
GET /posts :index post_path(:index)
GET /posts/new :new post_path(:new)
POST /posts :create post_path(:create)
GET /posts/:id :show post_path(:show, post)
GET /posts/:id/edit :edit post_path(:edit, post)
PATCH /posts/:id :update post_path(:update, post)
DELETE /posts/:id :delete post_path(:delete, post)

Scopes

Group routes under a URL prefix and/or module namespace:

# Module namespace is inferred from scope path
scope "/api", MyAppWeb.API do
  pipe_through :api

  resources "/posts", PostController  # => MyAppWeb.API.PostController
end

# Custom alias
scope "/admin", as: :admin do
  pipe_through :browser

  resources "/users", Admin.UserController
end
# => /admin/users, admin_user_path(:index)

LiveView routes

scope "/", MyAppWeb do
  pipe_through :browser

  # Single LiveView
  live "/dashboard", DashboardLive

  # LiveView with params
  live "/users/:id", UserLive.Show

  # LiveView with action
  live "/posts", PostLive.Index
  live "/posts/new", PostLive.New
  live "/posts/:id/edit", PostLive.Edit
end

Verified routes (sigil ~p)

Verified routes are checked at compile time against the router:

# In templates and views
~p"/posts"
~p"/posts/#{post}"
~p"/posts/#{post.id}"

# With query params
~p"/posts?page=2"
~p"/posts?#{[page: 2, sort: :title]}"

Tip: Always use ~p instead of string paths or helpers. It catches typos and renamed routes at compile time.

Inspecting routes

mix phx.routes              # List all routes
mix phx.routes MyAppWeb.Router  # Explicit router module

Forwarding

Delegate all requests under a path to another router or plug:

forward "/health", MyAppWeb.HealthCheckPlug
forward "/api/v1", MyAppWeb.API.V1.Router

Custom pipeline plugs

pipeline :authenticated do
  plug MyAppWeb.Plugs.RequireAuth
  plug :put_layout, html: {MyAppWeb.Layouts, :dashboard}
end

scope "/app", MyAppWeb do
  pipe_through [:browser, :authenticated]

  get "/profile", ProfileController, :show
  live "/settings", SettingsLive
end