Subscribe to access all episodes. View plans →
Published May 27, 2018
Phoenix 1.3
One of the first things you’ll probably do when building a new Phoenix application is to define some routes.
In this episode we’ll look at how we can use the Phoenix to create different routes for our application.
Let’s start by creating a new Phoenix application named teacher and we’ll install the dependencies.
Then change into the directory and create our database:
$ mix phx.new teacher
$ cd teacher
$ mix ecto.create
Now let’s see what routes Phoenix gives us to start.
We can run $ mix phx.routes
to print out the defined routes.
Running it, we see there’s one route defined a get request that’s handled by our PageController
index action, page_path GET / TeacherWeb.PageController :index
.
Lets see how it’s defined in our routers. We’ll open our router.ex
and we can see where Phoenix defined our route.
It’s making a GET request at “/“ which will then be handled by the index function in our PageController
, which maps to what we saw when we listed our routes get "/", PageController, :index
.
Now that we see where our current route is defined, let’s walk through the rest of the module so we have an understanding of what’s going on.
At the top of our module, the first thing is use TeacherWeb, :router
. This let’s us use Phoenix’s router functions in our application’s router.
Then we see a pipeline
for the browser. A pipeline
allows us to group a common set of plugs together that we want to use for a certain type of request.
Our browser pipeline, for example, uses plugs that are always executed for browser requests like fetching the session, getting flash messages, and protecting against cross site request forgery or CSRF.
Now once we have our pipeline built up, we can run our connection through it by calling pipe_through
. In addition to our browser
pipeline Phoenix also creates a api
pipeline.
By default it just checks that the request is JSON. But if we wanted to create another Plug that, for example, verified an API token, this would be a great place to include it.
If you’re unfamiliar with how plugs work, you can learn more about them in episodes 19 and episode 20.
Now the scope
allows us to organize our routes by grouping them together. Here it’s taking a path (“/“) and an alias, in our case TeacherWeb
Now that we have a basic understanding of what’s happening let’s define some new routes. Let’s create an route that will accept a post request We’ll use the post
function, give it a path, in this we’ll just use “/create”, the controller PageController
and the function on the controller we want to call - :create
.
Now we’ll need to define our create
function in order to handle our new route. Let’s open our PageController
and we’ll define our create
function.
Our create function will take the connection and the params. We’ll ignore our params here since we won’t use them in our function.
In an actual create we’d probably insert some kind of record, for our example we’ll pretend it’s doing something.
Then let’s display a message to let the user know everything succeeded and we’ll render our index template.
lib/teacher_web/controllers/page_controller.ex
...
def create(conn, _params) do
:timer.sleep(1_000) #create something
conn
|> put_flash(:info, "Your record was created")
|> render("index.html")
end
...
Let’s go back to the command line and list all our routes again:
$ mix phx.routes
We see our new route is listed and it’s using the correct method of post and our PageController
and create
function.
Now let’s add link for our new route in a template. We’ll open our app.html.eex
template.
And we’ll create a new link
with the text “Create”. We want this link to go to our new route, so let’s use page_path
helper, passing it our connection and the atom :create
and because this is not a get
but a post
request we’ll need to specify the method.
Great, now let’s start up our server.
$ mix phx.server
And in our browser, let’s click our new link. We see our flash message displayed - great our new route is working.
Let’s go back to our router.ex
and if we now wanted to add more routes, like one for put
or delete
we could do these one-by-one, however an easier way would be to define all the RESTful routes for our resource at once.
We can do exactly that with the resources
function. Let’s see what that would look like for a “movies” resource.
We’d call resources provide a path, in this case “/movies”, then we’ll give it a controller, MovieController
.
lib/teacher_web/router.ex
...
resources "/movies", MovieController
...
And if we go to the command line and list our routes.
$ mix phx.routes
movie_path GET /movies TeacherWeb.MovieController :index
movie_path GET /movies/:id/edit TeacherWeb.MovieController :edit
movie_path GET /movies/new TeacherWeb.MovieController :new
movie_path GET /movies/:id TeacherWeb.MovieController :show
movie_path POST /movies TeacherWeb.MovieController :create
movie_path PATCH /movies/:id TeacherWeb.MovieController :update
PUT /movies/:id TeacherWeb.MovieController :update
movie_path DELETE /movies/:id TeacherWeb.MovieController :delete
We see the new routes for our movies are listed. Now one common option is to limit the routes created when using the resources
function. For example, if we wanted to create all of our RESTful routes except, :delete, we can pass the option expect with a list of actions to exclude, in this case :delete
. except: [:delete]
.
lib/teacher_web/router.ex
...
resources "/movies", MovieController, except: [:delete]
...
And if we ran our routes again, the DELETE action wont be included.
$ mix phx.routes
movie_path GET /movies TeacherWeb.MovieController :index
movie_path GET /movies/:id/edit TeacherWeb.MovieController :edit
movie_path GET /movies/new TeacherWeb.MovieController :new
movie_path GET /movies/:id TeacherWeb.MovieController :show
movie_path POST /movies TeacherWeb.MovieController :create
movie_path PATCH /movies/:id TeacherWeb.MovieController :update
PUT /movies/:id TeacherWeb.MovieController :update
The resources
function also lets us use nested routes.
Let’s say wanted a route to list the actors for a movie, one way we could do that is to create an “/actors” route that’s nested in our “/movies” resource.
We’ll it to use a do block and define a resource for our actors, giving it a path “/actors” and an ActorController
. Then let’s limit this to only create the :index action.
To do that we’ll use another option, only
and give it a list of the actions we want to create, in this case :index
. From the command line, we’ll list our routes again. And we see our new “/actors” route is listed.
$ mix phx.routes
movie_path GET /movies TeacherWeb.MovieController :index
movie_path GET /movies/:id/edit TeacherWeb.MovieController :edit
movie_path GET /movies/new TeacherWeb.MovieController :new
movie_path GET /movies/:id TeacherWeb.MovieController :show
movie_path POST /movies TeacherWeb.MovieController :create
movie_path PATCH /movies/:id TeacherWeb.MovieController :update
PUT /movies/:id TeacherWeb.MovieController :update
movie_actor_path GET /movies/:movie_id/actors TeacherWeb.ActorController :index
Let’s look at one last feature of Phoenix routes - creating a wildcard route. Let’s have our new route handle get requests and by using the asterisk, it will catch any route that comes in.
We’ll append “thing”, allowing us to access the requested path in the params under the key “thing” and let’s handle this in our PageController
with a function we’ll call wildcard
: get "/*thing", PageController, :wildcard
.
Then let’s open our page_controller.ex
and let’s write our function.
Inside it we’ll get value of the matched path that will be under the key “thing”. Then let’s display it with a flash message and we’ll render our “index.html” template.
lib/teacher_web/controllers/page_controller.ex
...
def wildcard(conn, params) do
path = Map.fetch!(params, "thing")
conn
|> put_flash(:info, "Caught this route - #{path}")
|> render("index.html")
end
...
Let’s try it out. We’ll go to the browser and type in a route we haven’t defined: http://localhost:4000/nothing/
And great our path “nothing” is displayed in our message.
This also works for a url like this: http://localhost:4000/nothing/is/here
But now if we go back to our homepage we see our message is still displayed.
And if we check our development server logs, we see a message telling us that our wildcard will always match.
So let’s go back to our router.ex
. It will always match because it’s at the top of the scope and let’s move our wildcard route it to the bottom.
Then let’s try our homepage again - and great it’s back to rendering as it should.
Now instead of matching all routes, let’s say we only wanted our wildcard route to match if the path starts with a specific pattern, say “phoenix”.
We’ll put what we want to match on just before our asterisk: get "/phoenix*thing", PageController, :wildcard
.
Let’s try putting “phoenix” at the beginning of our path: http://localhost:4000/phoenix-is-great
Great - it caught our route.
Let’s try another route: http://localhost:4000/phoenix/is/great
Perfect it works for that as well. Then if we try a route that doesn’t exist and is not prefixed by “phoenix” - we get a no route error.
And just to be sure - our homepage still works.