Our task for this guide is to add two new pages to our Phoenix application. One will be a purely static page, and the other will take part of the path from the url as input and pass it through to a template for display. Along the way, we will gain familiarity with the basic components of a Phoenix application: the router, controllers, views and templates.
When Phoenix generates a new application for us, it builds a top level directory structure like this.
├── _build
├── config
├── deps
├── lib
├── priv
├── test
├── web
Most of our work in this guide will be in the web
directory, which looks like this when expanded.
├── channels
├── controllers
│ └── page_controller.ex
├── models
├── router.ex
├── templates
│ ├── layout
│ │ └── application.html.eex
│ └── page
│ └── index.html.eex
└── views
| ├── error_view.ex
| ├── layout_view.ex
| └── page_view.ex
└── web.ex
All of the files which are currently in the controllers, templates and views directories are there to create the "Welcome to Phoenix!" page we saw in the last guide. We will see how we can re-use some of that code shortly.
All of our application's static assets live in priv/static
in the directory appropriate for each type of file - css, images or js. We place assets that require a build phase into web/static
, and the source files are built into their respective app.js/app.css
bundles within priv/static
. We won't be making any changes here for now, but it is good to know where to look for future reference.
priv
└── static
└── images
└── phoenix.png
web
└── static
├── css
| └── app.css
├── js
│ └── app.js
└── vendor
└── phoenix.js
The lib
directory also contains files we should know about. Our application's endpoint is at lib/hello_phoenix/endpoint.ex
, and our application file (which starts our application and its supervision tree) is at lib/hello_phoenix.ex
.
lib
├── hello_phoenix
| ├── endpoint.ex
│ └── repo.ex
└── hello_phoenix.ex
Enough prep, let's get on with our first new Phoenix page!
###A New Route
Routes map unique HTTP verb/path pairs to controller/action pairs which will handle them. Phoenix generates a router file for us in new applications at web/router.ex
. This is where we will be working for this section.
The route for our "Welcome to Phoenix!" page from the previous Up And Running Guide looks like this.
get "/", PageController, :index
Let's digest what this route is telling us. Visiting http://localhost:4000/ issues an http GET request to the root path. All requests like this will be handled by the index
function in the HelloPhoenix.PageController
module defined in web/controllers/page_controller.ex
.
The page we are going to build will simply say "Hello World, from Phoenix!" when we point our browser to http://localhost:4000/hello.
The first thing we need to do to create that page is define a route for it. Let's open up web/router.ex
in a text editor. It should currently look like this.
defmodule HelloPhoenix.Router do
use HelloPhoenix.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
# Other scopes may use custom stacks.
# scope "/api", HelloPhoenix do
# pipe_through :api
# end
end
For now, we'll ignore the pipelines and the use of scope
here and just focus on adding a route. (We cover these topics in the Routing Guide, if you're curious.)
Let's add a new route to the router that maps a GET
request for /hello
to the index
action of a soon-to-be-created HelloPhoenix.HelloController
.
get "/hello", HelloController, :index
The scope "/"
block of our router.ex
file should now look like this.
scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "/hello", HelloController, :index
end
###A New Controller
Controllers are Elixir modules, and actions are Elixir functions defined in them. The purpose of actions is to gather any data and perform any tasks needed for rendering. Our route specifies that we need a HelloPhoenix.HelloController
module with an index/2
action.
To make that happen, let's create a new web/controllers/hello_controller.ex
file, and make it look like the following.
defmodule HelloPhoenix.HelloController do
use HelloPhoenix.Web, :controller
plug :action
def index(conn, _params) do
render conn, "index.html"
end
end
We'll save a discussion of use HelloPhoenix.Web, :controller
and plug :action
for the Controllers Guide. For now, let's focus on the index/2
action.
All controller actions take two arguments. The first is conn
, a struct which holds a ton of data about the request. The second is params
, which are the request parameters. Here, we are not using params
, and we avoid compiler warnings by adding the leading _
.
The core of this action is render conn, "index.html"
. This tells Phoenix to find a template called index.html.eex
and render it. Phoenix will look for the template in a directory named after our controller, so web/templates/hello
.
Note: Using an atom as the template name will also work here, render conn, :index
, but the template will be chosen based off the Accept headers, ie "index.html"
or "index.json"
, etc.
The modules responsible for rendering are views, and we'll make a new one of those next.
###A New View
Phoenix views have several important jobs. They render templates. They also act as a presentation layer for raw data from the controller, preparing it for use in a template. Functions which perform this transformation should go in a view.
As an example, say we have a data structure which represents a user with a first_name
field and a last_name
field, and in a template, we want to show the user's full name. We could write code in the template to merge those fields into a full name, but the better approach is to write a function in the view to do it for us, then call that function in the template. The result is a cleaner and more legible template.
In order to render any templates for our HelloController
, we need a HelloView
. The names are significant here - the first part of the names of the view and controller must match. Let's create an empty one for now, and leave a more detailed description of views for later. Create web/views/hello_view.ex
and make it look like this.
defmodule HelloPhoenix.HelloView do
use HelloPhoenix.Web, :view
end
###A New Template
Phoenix templates are just that, templates into which data can be rendered. The standard templating engine Phoenix uses is eex, which stands for Embedded Elixir. All of our template files will have the .eex
file extension.
Templates are scoped to a view, which are scoped to controller. In practice, this simply means that we create a directory named after the controller in the web/templates
directory. For our hello page, that means we need to create a hello
directory under web/templates
and then create an index.html.eex
file within it.
Let's do that now. Create web/templates/hello/index.html.eex
and make it look like this.
<div class="jumbotron">
<h2>Hello World, from Phoenix!</h2>
</div>
Now that we've got the route, controller, view and template, we should be able to point our browsers at http://localhost:4000/hello and see our greeting from Phoenix!
There are a couple of interesting things to notice about what we just did. We didn't need to stop and re-start the server while we made these changes. Yes, Phoenix has hot code re-loading! Also, even though our index.html.eex
file consisted of only a single div tag, The page we get is a full html document. Our index template is rendered into the application layout - web/templates/layout/application.html.eex
. If you open it, you'll see a tag that looks like this: <%= @inner %>
, which is what injects our rendered template into the layout before the html is sent off to the browser.
##Another New Page
Let's add just a little complexity to our application. We're going to add a new page that will recognize a piece of the url, label it as a "messenger" and pass it through the controller into the template so our messenger can say hello.
As we did last time, the first thing we'll do is create a new route.
###A New Route
For this exercise, we're going to re-use the HelloController
we just created and just add a new show
action. We'll add a line just below our last route, like this.
scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack.
get "/", PageController, :index
get "/hello", HelloController, :index
get "/hello/:messenger", HelloController, :show
end
Notice that we put the atom :messenger
in the path. Phoenix will take whatever value that appears in that position in the url and pass a Dict with the key messenger
pointing to that value to the controller.
For example, if we point the browser at: http://localhost:4000/hello/Frank, the value of ":messenger" will be "Frank".
###A New Action
Requests to our new route will be handled by the HelloPhoenix.HelloController
show
action. We already have the controller at web/controllers/hello_controller.ex
, so all we need to do is edit that file and add a show
action to it. This time, we'll need to keep the params that get passed into the action so that we can pass the messenger to the template. To do that, we add this show function to the controller.
def show(conn, %{"messenger" => messenger}) do
render conn, "show.html", messenger: messenger
end
There are a couple of things to notice here. We pattern match against the params passed into the show function so that the messenger
variable will be bound to the value we put in the :messenger
position in the url. For example, if our url is http://localhost:4000/hello/Frank, the messenger variable would be bound to Frank
.
Within the body of the show
action, we also pass a third argument into the render function, a key value pair where :messenger
is the key, and the messenger variable is passed as the value.
It's good to remember that the keys to the params Dict will always be strings.
###A New Template
For the last piece of this puzzle, we'll need a new template. Since it is for the show
action of the HelloController
, it will go into the web/templates/hello
directory and be called show.html.eex
. It will look surprisingly like our index.html.eex
template, except that we will need to display the name of our messenger.
To do that, we'll use the special eex tags for executing Elixir expressions - <%= %>
. Notice that the initial tag has an equals sign like this: <%=
. That means that any Elixir code that goes between those tags will be executed, and the resulting value will replace the tag. If the equals sign were missing, the code would still be executed, but the value would not appear on the page.
And this is what the template should look like.
<div class="jumbotron">
<h2>Hello World, from <%= @messenger %>!</h2>
</div>
Our messenger appears as @messenger
. In this case, this is not a module attribute. It is special bit of metaprogrammed syntax which stands in for Dict.get(assigns, :messenger)
. The result is much nicer on the eyes and much easier to work with in a template.
We're done. If you point your browser here: http://localhost:4000/hello/Frank, you should see a page that looks like this:
Play around a bit. Whatever you put after /hello/ will appear on the page as your messenger.