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
1 | We 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:
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)
1 | This 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. |
2 | No 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.
[...]
scope "/", DemoWeb do
pipe_through :browser
get "/", PageController, :index
get "/products/:id", ProductController, :show (1)
end
[...]
1 | We ask the router to match everything at that position to the parameter :id . |
defmodule DemoWeb.ProductController do
use DemoWeb, :controller
def show(conn, %{"id" => id}) do (1)
conn
|> assign(:id, id) (2)
|> render("show.html")
end
end
1 | We match the id in the params. |
2 | In 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. |
defmodule DemoWeb.ProductView do
use DemoWeb, :view
end
<h1>ID: <%= @id %></h1> (1)
1 | Just 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:
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
1 | Both 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:
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
1 | This show/2 function matches if there is an :id and a :color parameter. |
2 | In addition to :id we have to assign :color too. |
3 | This 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:
<h1>ID: <%= @id %></h1>
<%= if assigns[:color] do %> (1)
<p>Color: <%= @color %></p>
<% end %>
1 | Because 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. |
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
Link with params
Assuming you’d like to link to the next product ID this would be the template:
<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)
1 | We 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. |
Link with query
And if you would like to link to the first product with the query "color=orange", you would use this code:
<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)
1 | This returns a link to http://localhost:4000/products/1?color=orange |
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:
[...]
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.
[...]
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
1 | The 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