Router

We have used Phoenix.Router plenty of times in Phoenix Basics, and you probably already understand the basics. So this is just a small chapter with some useful extra details.

All code in this chapter will result from this base application:

$ mix phx.new demo --no-ecto --no-dashboard (1)
$ cd demo
1We don’t include the Phoenix.LiveDashboard (--no-dashboard) to keep our router setup tidy. Otherwise, there will be many routes created just for the real-time performance monitoring dashboard, which is not a topic in this chapter.

This results in a minimal router setup:

lib/demo_web/router.ex
defmodule DemoWeb.Router do
  use DemoWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

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

  scope "/", DemoWeb do
    pipe_through :browser

    get "/", PageController, :index
  end
end

At this stage, we will concentrate on the last function, which starts with scope "/".

Display existing routes

Running the command mix phx.routes will return a list of all defined routes. In our vanilla demo system, this is the output:

$ mix phx.routes
Compiling 13 files (.ex)
Generated demo app
page_path  GET  /                    DemoWeb.PageController :index (1)
websocket  WS   /socket/websocket    DemoWeb.UserSocket (2)
1This route is for a HTTP GET request for the root path - which gets matched by / - of our application. It triggers the :index action in the DemoWeb.PageController controller.
2No need to discuss websockets in this chapter. I will remove that line in following examples to clean up the output.
In more complex applications with a dashboard, the output of mix phx.routes gets quite a bit longer. Then mix phx.routes | grep "search-item" is a useful way of filtering the output.

Params

There are many times when you will want to set a parameter, such as ID, in the route. The following example will look at how we can set the parameter in the router, and how we can access it in the controller.

lib/demo_web/router.ex
[...]

scope "/", DemoWeb do
  pipe_through :browser

  get "/", PageController, :index
  get "/products/:id", ProductController, :show (1)
end

[...]
1We ask the router to match everything at that position to the parameter :id.
lib/demo_web/controllers/product_controller.ex
defmodule DemoWeb.ProductController do
  use DemoWeb, :controller

  def show(conn, %{"id" => id}) do (1)
    conn
    |> assign(:id, id) (2)
    |> render("show.html")
  end
end
1We match the id in the params.
2In this example, we just add the id to the assigns map. Normally, we would have fetched the product with that ID from the database and manipulated the data before rendering the template.
lib/demo_web/views/product_view.ex
defmodule DemoWeb.ProductView do
  use DemoWeb, :view
end
lib/demo_web/templates/product/show.html.eex
<h1>ID: <%= @id %></h1> (1)
1Just a simple display.

If you open http://localhost:4000/products/1 in your browser, you’ll see this log entry in the terminal:

[info] GET /products/1
[debug] Processing with DemoWeb.ProductController.show/2
  Parameters: %{"id" => "1"}
  Pipelines: [:browser]
[info] Sent 200 in 373µs

And here is the screenshot:

http://localhost:4000/products/1

Query String

To handle query strings, we do not need to add anything to the router. We just need to make some small changes to the controller.

For example, if we add the query string "color=blue" to our product request (http://localhost:4000/products/1?color=blue) and open this URL in the browser, we can see from the log entry (shown below) that Phoenix has automatically added color to the params (parameters) map.

[info] GET /products/1
[debug] Processing with DemoWeb.ProductController.show/2
  Parameters: %{"color" => "blue", "id" => "1"} (1)
  Pipelines: [:browser]
[info] Sent 200 in 600µs
1Both parameters have been added to the params map.

Without adding anything to the router, the query string parameter color can be accessed in the controller, but we need to make a few changes to the controller:

lib/demo_web/controllers/product_controller.ex
defmodule DemoWeb.ProductController do
  use DemoWeb, :controller

  def show(conn, %{"id" => id, "color" => color}) do (1)
    conn
    |> assign(:id, id)
    |> assign(:color, color) (2)
    |> render("show.html")
  end

  def show(conn, %{"id" => id}) do (3)
    conn
    |> assign(:id, id)
    |> render("show.html")
  end
end
1This show/2 function matches if there is an :id and a :color parameter.
2In addition to :id we have to assign :color too.
3This show/2 function matches if there is only an :id parameter.
The order of the show/2 functions in the controller is significant. If we use the other order for this specific example the %{"id" ⇒ id, "color" ⇒ color} would never match because %{"id" ⇒ id} always matches first if it’s the first function.

Lastly we have to change the template:

lib/demo_web/controllers/product_controller.ex
<h1>ID: <%= @id %></h1>

<%= if assigns[:color] do %> (1)
<p>Color: <%= @color %></p>
<% end %>
1Because we call this template from two different functions we have to take care of the case when the color assigns hasn’t taken place. Alternatively, we could use a different template for each function.
http://localhost:4000/products/1?color=blue

A view of the routes:

$ mix phx.routes
Compiling 1 file (.ex)
   page_path  GET  /                                      DemoWeb.PageController :index
product_path  GET  /products/:id                          DemoWeb.ProductController :show

Assuming you’d like to link to the next product ID this would be the template:

lib/demo_web/controllers/product_controller.ex
<h1>ID: <%= @id %></h1>

<%= if assigns[:color] do %> (1)
<p>Color: <%= @color %></p>
<% end %>

<%= link "Next", to: Routes.product_path(@conn, :show, String.to_integer(@id) + 1) %> (1)
1We use the DemoWeb.ProductController :show route and add the id to it. To increase the current @id we have to call String.to_integer/1 first.

And if you would like to link to the first product with the query "color=orange", you would use this code:

lib/demo_web/controllers/product_controller.ex
<h1>ID: <%= @id %></h1>

<%= if assigns[:color] do %> (1)
<p>Color: <%= @color %></p>
<% end %>

<%= link "First product in orange", to: Routes.product_path(@conn, :show, 1, color: "orange") %> (1)

Multilevel Paths

In the previous easy code examples, we always put the routes on the first level. But of course, you can use paths with sublevels too. Here’s an example:

lib/demo_web/router.ex
[...]
scope "/", DemoWeb do
  pipe_through :browser

  get "/an-other-test/abc/def/", PageController, :index
end
[...]

No surprise here:

$ mix phx.routes
Compiling 1 file (.ex)
page_path  GET  /an-other-test/abc/def    DemoWeb.PageController :index

Wildcards

You don’t have to define the route precisely. You can use the * wildcard too.

lib/demo_web/router.ex
[...]
scope "/", DemoWeb do
  pipe_through :browser

  get "/names/st*an", PageController, :index
end
[...]

This route matches /names/stefan and /names/stephan:

[info] GET /names/stefan
[debug] Processing with DemoWeb.PageController.index/2
  Parameters: %{"an" => ["stefan"]} (1)
  Pipelines: [:browser]
[info] Sent 200 in 4ms
[info] GET /names/stephan
[debug] Processing with DemoWeb.PageController.index/2
  Parameters: %{"an" => ["stephan"]}
  Pipelines: [:browser]
[info] Sent 200 in 1ms
1The parameter is automatically named an, and it contains the whole match. You can use that to do some interesting things.

Misc

There’s no need to replicate the official documentation in this chapter. By now, you understand the concept of Phoenix.Router. You’ll find solutions for all the exceptional cases which are not handled in this chapter in the official documentation at https://hexdocs.pm/phoenix/Phoenix.Router.html