Full-Stack React With Phoenix (Chapter 3 | Introduction to Phoenix)

Table of Contents

Chapter 1 | Why Bother?
Chapter 2 | Learning the Basics of Elixir

Before We Begin

Install Phoenix by following the official documentation.

Install an Elixir plugin for your code editor of choice. I’m using Atom.

Important Note: Since writing this book, Phoenix has had a big update to version 1.3. I will be using version 1.2.4 in this book. You can read more about the upgrade here.

Scope of This Chapter

As the title indicates, the point of this chapter will be to get an introduction to Phoenix.

I’ve contemplated the best way to go about this. There are two common teaching styles that I’m not a huge fan of. On one hand, sometimes there are a ton of terminologies and high-level concepts before looking at any code which usually doesn’t retain well. On the other hand, coding can be rushed into while an explanation is lacking turning it into a copy and paste session.

In order to combat both extremes, we will begin by firing up a Phoenix project but carefully explain all the concepts and terminologies as we make a “Hello Phoenix” application.

Firing Up a Phoenix Project

It’s important to begin by mentioning we are going to be running a lot of commands that use mix. When working with JavaScript applications, you are used to installing packages, running scripts, and other stuff using npm and node. When dealing with Elixir stuff, the equivalent is to use mix.

With that aside, let’s get started.

We can generate a new Phoenix project by running the following command:

mix phoenix.new hello_phoenix

In the line above, hello_phoenix is the name of our application. This line is like running the Express.js generator:

express

We can see that a bunch of files were generated for our application. It also asks us to fetch and install dependencies. Type y.

Once that finishes, we should see the following:

We can see that it ran mix deps.get and some npm install stuff.

Wait a second. I thought this wasn’t JavaScript? What’s going on?

Before we unpack that, run cd hello_phoenix and open the project a text editor. Open mix.exs.

This file is the equivalent of package.json as it configures our project. There are just a few things to point out.

The following code contains meta data about our project:

def project do
[app: :hello_phoenix,
version: "0.0.1",
elixir: "~> 1.2",
elixirc_paths: elixirc_paths(Mix.env),
compilers: [:phoenix, :gettext] ++ Mix.compilers,
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
aliases: aliases(),
deps: deps()]
end

This block of code defines our Elixir dependencies:

defp deps do
[{:phoenix, "~> 1.2.4"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_ecto, "~> 3.0"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.6"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"}]
end

When we ran mix phoenix.new hello_phoenix, this file (among others) was generated. Then, it asked us if we wanted to fetch and install these dependencies.

When we accepted, it ran mix deps.get which is the equivalent of running npm install with a pre-populated package.json file. It installed all of the defined dependencies in the deps folder:

What about that npm install command that also ran?

Here is where we have to make clarifications. When using a Node framework like Express, the project directory looks like this:

The public folder can contain static images, JavaScript files, and stylesheets. In our Phoenix project, the static folder is the equivalent of this. This stuff is default web technology for the client side and is not the point of difference between Node.js and Phoenix.

The difference lies in what programming language is used to handling all the server-side coding. In Node.js, this is JavaScript. Therefore, all the dependencies can be specified in package.json and installed via npm. In Phoenix, the server-side code is written in Elixir. Therefore, we need to use mix.deps for the server-side dependencies and npm for the client side dependencies.

All that to say, the Phoenix generator initially created a pre-populated package.json file with the client-side dependencies specified and upon our approval went ahead and npm installed these dependencies.

The second half of the line containing the npm install command was node node_modules/brunch/bin/brunch build. Phoenix uses Brunch.io out of the box for asset management. This is simply an alternative to Webpack. brunch build builds the client side project.

You can look at brunch-config.js and the official Brunch.io documentation if you are curious to learn more. However, we won’t be focusing on this until the next chapter. Just remember, there are dependencies and configurations for both the client-side and server-side. The client-side will be a React application in this book. The server-side will be using Elixir code.

With these explanations aside, run mix phoenix.server to run our application which now available at http://localhost:4000/:

As you can see, there is a pre-populated web page. Let’s unpack all the server-side code that is producing this page and then how we can create a new page that renders “Hello Phoenix”.

Note: There are hideous errors constantly being spewed in command line:

This is because we ignored database configuration. However, there’s no need to do that until later on this book so just open a new tab and ignore.

Exploring Phoenix

Disclaimer

As we unpack the generated code and create our “Hello Phoenix” app, I want to make it very clear that you’re not the only one who may be overwhelmed with all the generated files and folders. When I learned React, for example, I purposely ditched create-react-app or any other template so that I could build the entire project from scratch. While I would prefer to be able to do that with Phoenix, there’s just simply not enough time and energy to do that. We are going to have to approach this as “I only am going to care about what I need to know at the moment” so bear with me.

Overview

In the absolute broadest sense, the starting point of our project is a connection and the end point is a rendered application:

A user connects via a URL and an application is rendered for them to access. This is pretty straightforward. However, we can get more specific.

There are 5 major layers that are passed through between the connection and the resulting application:

  1. Endpoint
  2. Router
  3. Controllers
  4. Views
  5. Templates

We will unpack each of these layers in order. As we will see, all of these layers are modules containing functions. Given that Elixir is a functional programming language, this entire process can be seen as calling functions until we have our application rendered.

Endpoint

The endpoint is the first layer contacted after a connection. The endpoint code can be found in lib/hello_phoenix/endpoint.ex. In this file, we just need to know the gist of what’s happening.

With a quick scan, you will frequently see plug Plug. These are plugs in action. Plugs come from an Elixir dependency called Plug. You can simply think of Plug as a library of pre-existing modules and functions which we can “plug” into Phoenix to do common server-side stuff in web applications.

Let’s go through the important plugs in endpoint.ex so we get what’s going on.

First, plug Plug.Static serves the static files found in our static folder (web/static). Static files include images, JavaScript files, and stylesheets.

Then, plug Plug.RequestId and plug Plug.Logger are used to log information about requests.

plug Plug.Parsers allows for parsing the body of request for different content types.

plug Plug.Session stores the session in a cookie with a signature. This information could be used to store relevant information about sessions in a database.

After all this, we dispatch requests to a router using plug HelloPhoenix.Router. Router is a very important layer which we will examine next.

Router

You can open up the router file called router.ex directly under the web folder. The code that looks the most intimidating is the pipeline blocks:

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

A pipeline is actually quite simple. A pipeline organizes functions together so you can carry out a task on certain requests.

These pipelines are called where the requests are handled such as the following:

scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
  get "/", PageController, :index
end

In the code above, we are handling requests in the scope of the root path of our application. On the root path, we want to get content which requires passing through the PageController, and specifically, running the index function within it (more on this shortly).

Before that, however, we can say: “Pipe through the browser pipeline so we can use the default browser stack. Then, go ahead and get the content on this path by passing through to the controller.”

A quick note. You may have seen a lot of atoms (i.e. :browser) being used in the Phoenix code. Here, we have a really clear example of how atoms can be used to call functions:

pipe_through :browser

Anyways, it’s time to go on to the next layer which is controllers. In the code we had above, we specifically want to pass through to the PageController and run the index function in it.

Controllers

The basic usage of a controller is to invoke rendering of a view.

In web/controllers/page_controller.ex, the index function takes the connection and renders index.html:

def index(conn, _params) do
render conn, "index.html"
end

You’ll notice _params is grayed out. This would be updated if we had incoming parameters passed down from the router. We will look at an example of doing that later on in this chapter.

The "index.html" corresponds with a template. However, there needs to be a view for each template so we will look at that next.

View

Views have the shortest amount of code but they were the hardest thing to wrap my mind around. For example, the flow between the router and controller was pretty clear. There was an explicit call to a function within a controller from the router on a certain request:

get "/", PageController, :index

However, in our controller, we invoked the rendering of index.html:

render conn, "index.html"

The index.html that is being invoked is defined in a template file under web/templates/page/index.html.eex.

While there is no explicit call to a function or anything that would indicate a flow into writing a view file, a view file is expected for each template invoked by a controller. The view file will do the actual rendering of the template.

Think of it this way, I’m sure there’s been a time in your life when someone moved you to do something without saying words and giving you a certain look.

We could call this an implicit form of communication.

In the same way, the controller invokes a view to render implicitly when it runs:

render conn, "index.html" # or any other template

However, you’ll see that there are several pre-populated views in our project. How do we know which view (that is implicitly being told to render a template) is associated with our PageController?

Well, the naming is very important. Since we have a controller called PageController, Phoenix will expect a view called PageView. The association is through the name. Additionally, there should be a folder underneath the templates folder called page which contains the template being invoked. We can see this in our project directory:

Let’s look at page_view.ex:

defmodule HelloPhoenix.PageView do
use HelloPhoenix.Web, :view
end

In this file, all that is needed to cause a template to render is the following line:

use HelloPhoenix.Web, :view

With all this aside, let’s move on to looking at the template.

Template

The index.html.eex file looks like just normal HTML. However, because this is a template, we can do some special things such as can be seen in the following line:

<h2><%= gettext "Welcome to %{name}", name: "Phoenix!" %></h2>

gettext is an Elixir dependency which just provides a module that is being used to inject a name. You can change the value of name and see the changes reflected:

A generated Phoenix project also contains an app template found in app.html.eex.

There’s no controller for invoking this template as it is not attached to any request and is used to nest views:

<main role="main">
<%= render @view_module, @view_template, assigns %>
</main>

There are a few other things here worth mentioning.

There’s some paragraph tags which output errors for us if something goes wrong:

<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>

There’s also the inclusion of our app.js file in our static folder which does nothing right now:

<script src="<%= static_path(@conn, "/js/app.js") %>"></script>

You can also see how static file paths are referenced.

Everything is worth keeping except for the header which we can remove:

Now, the only thing rendering is the index template.

What if we request the following?

http://localhost:4000/hello

We get an error:

Let’s use this path to display “Hello Phoenix” using a parameter in the path as data.

Hello Phoenix

First, open up router.ex and let’s get some content on a new path:

scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
  get "/", PageController, :index
get "/hello/:data", HelloController, :message
end

:data specifies a dynamic parameter that will be passed to our HelloController. So, /users/phoenix would pass phoenix.

We specifically want to call the message function within this controller.

Next, let’s create a controller called hello_controller.ex within the controllers folder:

defmodule HelloPhoenix.HelloController do
use HelloPhoenix.Web, :controller
  def message(conn, _params) do
render conn, "hello.html"
end
end

Based off of the existing PageController, we just need to make some slight tweaks.

In addition, we also assign the params in a map:

def message(conn, %{"data" => data}) do

This makes our params (data) easily accessible through an associative collection.

On top of this, we also need to make data accessible in our template like so:

render conn, "hello.html", data: data

Before we create the template, first we create a view in a file called hello_view.ex:

defmodule HelloPhoenix.HelloView do
use HelloPhoenix.Web, :view
end

Now, we can create a folder for our template within the templates folder called hello. Then, create a file called hello.html.eex.

In here, we simply can inject our data into a message:

<div class="jumbotron">
<h2><%= @data %></h2>
</div>

<%= %> surrounds Phoenix stuff in HTML. @data is used to inject our data param.

Woot woot! We are all done!

Type in http://localhost:4000/hello/phoenix to see the following:

If we do http://localhost:4000/hello/elixir, we can see it update:

Sweet! Round of applause!


Before we wrap things up, let’s recap the follow from our connection to rendering an application.

First, gather some info and other stuff through an endpoint using plugs.

Then, the router can handle different requests and call a function within a controller.

The controller invokes the rendering of a template and implicitly expects a view to be added.

The view allows for the rendering of a template.

The template can define HTML and inject data.

Views are all nested within the app.html.eex template.

Final Code

Available on GitHub.

Concluding Thoughts

Phoenix is pretty intimidating to start, not going to lie. However, I hope this has been a good explanation as I’ve had many “a-ha” moments as I wrote this.

In the next chapter, we will implement a React frontend in our Phoenix application.

Chapter 4

Chapter 4 is now available.

Sign Up for Notifications

Get notified when each chapter is released.


Cheers,
Mike Mangialardi
Founder of Coding Artist