In this lesson, we'll go through the basic steps that Phoenix follows in order to present your HTML. Let's start by making sure that we understand how our current dummy HTML page (the one that says "Welcome to Phoenix") is being generated.
The layout template
By default, all pages created in Phoenix will be rendered in a pre-defined App layout template. You can find this template in
The thing to remember about this template is that it should be kept very simple. Think of it as a very thin wrapper that has mostly static content. Good things to put here are your app's header, footer and content container.
In this pre-generated Pheonix layout, there's a
header, some alert-tags for error messages, and a
main-tag that the rest of the page is rendered inside.
Let's try to modify this template a bit! We remove the
header and type "Hello World" in an
h1-tag. Keep the rest as it is!
<!-- lib/messengyr_web/templates/layout/app.html.eex --> <body> <div class="container"> <!-- Remove the whole <header>-part --> <!-- And add this instead: --> <h1>Hello world!</h1> <!-- Keep the rest as it is: --> <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> <!-- ... --> </div> </body>
If you go back to the browser, you should see your new text. Phoenix comes with live reloading bundled in, so any changes that you make to your files are visible almost instantly!
As you might have noticed, our layout template also contains this peculiar line of code:
<%= render @view_module, @view_template, assigns %>
This is where all the other view templates are rendered. To understand how these are rendered, we first need to understand the router.
The router is essentially what connects a URL to a certain page. You can find it at
The file might look daunting at first, but don't worry, it's actually quite simple!
The first thing you see when you open the router-file is this weird line:
use MessengyrWeb, :router.
use is an Elixir macro used to bring in **external modules into the current context **(you might know this as "mixins" in other programming languages), so that we get access to functions like
pipeline. You can see exactly what this line imports by opening the file
lib/messengyr_web.ex and checking the
We'll go through the
plug functions in an upcoming chapter, but for now you should concentrate on the
scope-function further down. You'll notice that the first parameter of this function is a URL path, represented as a string (
scope "/", MessengyrWeb do pipe_through :browser
pipe_through-part tells Phoenix that all paths that start with
"/" (which is all URLs), must go through a series of functions from a specific pipeline before rendering something. In this case, the
:browser-pipeline (defined at the top of the file) makes sure that what we render is valid HTML, and also adds some **extra security features **(like CSRF tokens).
Then we have
get "/", which specifies exactly which path we want to hit (in this case, still just
"/"), and what action to perform. This
get-function creates what's called a route, and in this particular route, we want to perform the
:index-action, defined in the
get "/", PageController, :index
If you now go to a URL that is not defined in any route (for example
/test), you'll see this error message:
Let's create our own route to handle this URL, so that we can understand the flow of an HTTP request in Phoenix!
Routes and Controllers
Since the route
/test isn't defined, we'll create it!
# lib/messengyr_web/router.ex scope "/", Messengyr.Web do pipe_through :browser get "/", PageController, :index get "/test", PageController, :say_hello # Add this line! end
Now, if we go to *http://localhost:4000/test*, our route will invoke the
say_hello/2-action on the
PageController. However, since that action hasn't been defined yet, you'll see this error message:
Controllers are responsible for gathering data, usually from a database, and then forwarding it to a view. For our
say_hello/2-function though, we'll keep things simple, so we won't need any database.
Let's open the file
messengyr_web/controllers/page_controller.ex. You'll see that the
index/2-function is already defined, and that it's calling a
render always returns one of our HTML-templates, but we actually don't have to return HTML -- we can return anything!
Just for fun, let's try to return
"Hello!" in plain text in our
/test-route using the
# lib/messengyr_web/controllers/page_controller.ex #... def say_hello(conn, _params) do text conn, "Hello!" end end
The only requirement for a controller action is that you must always return the
conn struct, no matter what type of content you choose to render.
Views and templates
Let's take a closer look at the
render-function in the controller this time. This is a function provided by Phoenix that invokes a view before rendering a certain template. The name of the view must match the controller name, so when the
index-action in the *Page-*Controller calls
render, it's going to invoke the *Page-*View. Let's open the
PageView module and take a look!
# lib/messengyr_web/views/page_view.ex defmodule MessengyrWeb.PageView do use MessengyrWeb, :view end
As you can see, there's nothing happening here yet. We just have a
use macro that imports some functions for us.
The second parameter of the
"index.html") defines the name of the template. Here, Phoenix again has some convenient naming conventions. Since we're in the
PageController, it's going to look for the template-file in the folder
web/templates/page. If we had invoked
render from a controller called
HelloController, it would look for the file in
Open the file
messengyr_web/templates/page/index.html.eex and you'll see the rest of the dummy template that's generated when we visit *http://localhost:4000*:
Let's remove all this content and just replace it with
"Hello again!", wrapped in a
<!-- lib/messengyr_web/templates/page/index.html.eex --> <p>Hello again!</p>
We now have the most minimalistic website in the world!
The purpose of views is to take the data given to it by the controller, and transform it into something that's easily consumed by the template. As an example, we can define a
my_name/0-function in our
PageView that simply returns our name:
# lib/messengyr_web/views/page_view.ex defmodule MessengyrWeb.PageView do use MessengyrWeb, :view def my_name do "Tristan" end end
Now, we can display this variable in the template, by using the simple EEx templating system:
<!-- lib/messengyr_web/templates/page/index.html.eex --> <p>Hello again <%= my_name %>!</p>
In EEx, the
<%-tags run the content as Elixir code, and the "
=" at the beginning prints the result.
To sum things up...
We've covered a lot of concepts in this chapter so make sure you understand them well before moving on. Here are the main takeaways:
The router connects an endpoint with a controller function through a route.
The controller defines the functions, and gathers data in them that it passes to a view.
The view transforms this data to make it easy to consume before rendering a template.
The template contains HTML code (and eventually some EEx).
Now that you have a grip on these concepts, we're going to start building our real application in the next chapter!