Phoenix Web Development

4.8 (4 reviews total)
By Brandon Richey
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    A Brief Introduction to Elixir and Phoenix
About this book

Phoenix is a modern web development framework that is used to build API’s and web applications. It is built on Elixir and runs on Erlang VM which makes it much faster than other options. With Elixir and Phoenix, you build your application the right way, ready to scale and ready for the increasing demands of real-time web applications.

This book covers the basics of the Phoenix web framework, showing you how to build a community voting application, and is divided into three parts. In the first part, you will be introduced to Phoenix and Elixir and understand the core terminologies that are used to describe them. You will also learn to build controller pages, store and retrieve data, add users to your app pages and protect your database. In the second section you will be able to reinforce your knowledge of architecting real time applications in phoenix and not only debug these applications but also diagnose issues in them. In the third and final section you will have the complete understanding of deploying and running the phoenix application and should be comfortable to make your first application release

By the end of this book, you'll have a strong grasp of all of the core fundamentals of the Phoenix framework, and will have built a full production-ready web application from scratch.

Publication date:
April 2018
Publisher
Packt
Pages
406
ISBN
9781787284197

 

A Brief Introduction to Elixir and Phoenix

In this book, we'll walk through the process of building a functional prototype of a social web application that will rely on real-time data updates. If you've ever used any sort of live voting application out there (such as Straw Poll or Twitter Polls), then you're probably pretty comfortable with the idea already. If not, I'll summarize our application so that you know what you're going to be building throughout this book.

Our application will allow users to register for new accounts on our site. From there, they will have the option to create new polls for other users (either while logged in or anonymously) to vote on. We want to make sure that the votes for the polls are recorded and broadcast out to anyone watching the polls in real-time, so we'll take advantage of Phoenix's built-in channel support to provide this.

In addition, our application will allow users to create chat rooms that will be displayed alongside the poll. After they've voted, users will be allowed to chat and discuss the results live as the votes come in. Users will also be allowed to post images, which will be processed, and uploaded to S3.

Users who create polls will be able to decide who is allowed to vote on their polls; for example, either only logged-in users or any user. Users will be able to modify their polls until they go live.

Users who own the polls should be able to participate in real-time discussions with the people voting on and viewing their polls, including anonymous users. Being able to communicate with each other takes advantage of some of the more advanced real-time concepts that are powering Elixir behind-the-scenes and running separately from the main web portion of our Phoenix application. These concepts demonstrate how we can build larger and more performance-intensive applications!

Now, before we really dive into developing this application, we'll need to make sure we understand some of the tools that are going to help us build this application. The assumption is that you're coming into this with an existing understanding of Elixir, but if not, we will briefly cover some core Elixir concepts while also learning about a tool that will be critical for building a great application in Elixir: the IEx shell and the debugger!

To recap, in this chapter we will cover the following topics:

  • Basic Elixir concepts
  • The interactive Elixir shell, IEx
  • Installing Phoenix
  • Creating a new Phoenix project with Mix tasks
  • An introduction to the Phoenix project structure
 

Introducing IEx and Elixir

We'll start off by talking a little bit about the Elixir programming language that Phoenix sits atop. The most important thing to note, especially if you're coming from another programming language such as Ruby, Go, PHP, JavaScript, or Java, is that there is one significant difference between Elixir and the rest: Elixir is a functional language. While this doesn't change everything dramatically, it does introduce a few gotchas that we will need to get out of the way.

If you're already comfortable with Elixir and IEx, feel free to skip this section, as this is intended to be a quick introduction or refresher to the Elixir language and the IEx debugging tool.

Before we dive too far into those gotchas, let's first get acquainted with a tool that is going to make our development process in Elixir much simpler: IEx.

What is IEx?

The good news: Elixir includes a tool called a Read-Evaluate-Print-Loop (REPL), which is essentially an Elixir shell. You can think of this as being similar to an irb in Ruby or the node shell in Node.js. It is a very detailed and helpful tool that we'll be referring to quite a bit in our journey of building this full web app! To open REPL up (assuming you have Elixir installed), run the following command:

$> iex

You should see something similar to the following snippet appear on your screen:

$> iex
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.5.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

Now we can start messing around with some sample Elixir code! Let's start off with a couple of base scenarios.

Variables in Elixir

Like most other programming languages, Elixir has a concept of variables. Variables are, simply put, just a place to store values or data. Variables in Elixir do not require any sort of type definition or declaration that they're variables, as you might see in languages such as JavaScript. We can declare a new variable as simply as follows:

iex(1)> greeting = "Hello There"
"Hello There"
In IEx, the output of any statement entered is always the next line in the shell.

Variables point to memory locations, so when you use them in your code, the values stored in those memory locations are automatically pulled out for you, as shown in the following example:

iex(2)> greeting
"Hello There"

While variables can be reassigned in Elixir, the actual values are immutable! This brings us to our first gotcha in Elixir: immutability.

Immutability in Elixir

The first thing to note is Elixir's concept of immutability. This means that any variables used will retain their value whenever they're passed along. This is a big difference from languages such as Ruby, where the state of any data you pass on is no longer guaranteed to retain its original value. Objects are a good example of this in Ruby.

To demonstrate this concept a little bit better (don't worry about the syntax yet; we'll get to that!), let's take a look at a code snippet in Javascript, as follows:

function upcase_name(p) {
p.name = p.name.toUpperCase();
}

var person = { name: "Brandon" } // Name here is still 'Brandon'
person // Output is { name: "Brandon" }
upcase_name(person)
person // Oh no! Output is now { name: "BRANDON" }

Oh no! We've just introduced a scenario where calling a function is destructive whether we've intended it to be or not! Let's look at a similar block of code in Elixir in the following example:

upcase = fn p ->
Map.put(p, :name, String.upcase(p.name))
end

person = %{ name: "Brandon" } # Name here is still 'Brandon'
upcase.(person)
person # Output for name is still 'Brandon'!

This is likely our intended behavior, and it prevents people from doing wacky things while programming, such as writing functions that end in? (typically used to denote a function that answers a question about the arguments in a Boolean). (Sadly, I've seen this more times than I'd care to admit).

Of course, none of these examples are without their own respective gotchas (and honestly, which programming language nowadays doesn't have its fair share?). For example, the following snippet is considered completely legitimate code in Elixir:

person = %{ name: "Brandon" }
person = %{ name: "Richey" }

But, wait! I hear you cry, I thought Elixir's variables are immutable!Well, the answer to that is that they are immutable! Or rather, the memory locations that they point to are; therefore, every time we create a variable we're essentially creating a pointer to a location in memory. In our case, it stores our previous map into some memory location (we'll call it 0 x 01). When we reassign a person, however, we store the value of %{ name: "Richey" } into a different memory location (we'll call it 0 x 02). This is what is being passed along to our functions when we pass in the person variable; here, we're basically telling Elixir, Hey, use the value stored in memory location 0 x 01, which is what the variable person is currently pointing at. This means that we don't have to worry about the value in 0 x 01 being changed as long as it is still in use.

Understanding the different types in Elixir

Elixir has a number of different built-in types that we'll be working within the course of building our applications. They are the following:

  • Strings (sometimes referred to as binaries)
  • Integers
  • Floats
  • Lists
  • Maps
  • Keyword lists
  • Tuples
  • Modules
  • Functions (functions themselves break into two separate types: module functions and anonymous functions)

Let's take a look at the various representations of these types and use some of the tools in IEx to understand more about what we're working with.

Getting more information with the i helper

IEx has a really handy helper function available to us: i. This function comes in two separate arities, i/0 and i/1 (arities, in this case, being the number of arguments that we have to supply to each version of the function). i/0 displays information about the last value output in IEx, whereas i/1 displays information about a particular value or variable. Let's take a look at a few examples:

iex(3)> x = 5
5
iex(4)> i
Term
5
Data type
Integer
Reference modules
Integer
Implemented protocols
IEx.Info, Inspect, List.Chars, String.Chars

Let's talk about what the preceding example is giving us. First, it tells us the Term, which almost literally means the value itself. Next, we have the Data type, which tells us what Elixir is classifying this particular value as (in the example, it's an integer). Next, it tells us the Reference modules, which is a fancy way of telling us what modules we should use to be able to interact with this particular data type. Finally, it gives us a list of implemented protocols. These are additional modules that provide some sort of behavior that we expect that data type to adhere to if it implements a particular behavior. You can think of them as being similar in concept to interfaces in other programming languages.

Getting more information with the h helper

IEx also provides us with another incredibly helpful built-in function: h. Similar to i, h has two arities available for us to use: h/0, which displays the help page for IEx, and h/1, which displays the help for a particular module. So, while the following example won't work:

iex(5)> h 5
Invalid arguments for h helper: 5

This following example will:

iex(6)> h Integer
Integer

Functions for working with integers.

Using i and h effectively will go a long way towards helping us understand more of Elixir as we go along!

Using IEx and helpers to understand types

Going back to our integer example, we saw that there were a few different protocols that integers implement that we can take advantage of, but they may not make immediate sense to us. Let's use String.Chars as an example, where we'll call the h helper on String.Chars to learn more about the module from its documentation:

iex(7)> h String.Chars
String.Chars
The String.Chars protocol is responsible for converting a structure to a binary
(only if applicable).
The only function required to be implemented is to_string/1, which does the
conversion.
The to_string/1 function automatically imported by Kernel invokes this
protocol. String interpolation also invokes to_string/1 in its arguments. For
example, "foo#{bar}" is the same as "foo" <> to_string(bar).

Neat! It's like having a language guide built into our programming environment. It's difficult to stress just how useful this ends up being in practice, especially if you're like me and like doing programming where there's little access to the internet and you need to figure out how to use something like String.Chars. Reading the previous example, we see one particular snippet:

The only function required to be implemented is to_string/1, which does the conversion.

Let's dive into that further, again using h/1, as shown in the following example:

iex(8)> h String.Chars.to_string/1
def to_string(term)
Converts term to a string.

The preceding snippet tells us a huge amount about that particular module and function. Based on the description and provided function signature, we can infer that the Integer module implements a String.Chars protocol, which that means it needs to implement a to_string/1 function that matches the preceding function signature. Therefore, we can further infer that the way to convert an integer to a string is with the following method:

iex(9)> Integer.to_string(5)
"5"

Et voila! We've followed the chain of using i/1 and h/1 to figure out exactly how to perform an operation on a particular data set, as well as what assumptions we can make about the protocols implemented for that particular data type, and so on. Given this particular revelation, let's start expanding on this a little bit more and take a look at some of the other data types that exist in Elixir and a sample of the other operations that we can perform on them:

iex(10)> i 1.0
Term
1.0
Data type
Float
Reference modules
Float
Implemented protocols
IEx.Info, Inspect, List.Chars, String.Chars

So, if we wanted to represent something with decimal places, we'd use a float. Types in Elixir are inferred, so you don't have to explicitly specify the type for each variable. In addition, you can store any type in the same variable when you reassign it, so something like the following snippet is a perfectly valid operation (despite being very bad practice):

iex(11)> x = 5
5
iex(12)> x = "Hello"
"Hello"

If you're coming from a language such as Ruby or JavaScript none of this will be very surprising, but if you're coming from a language that is strongly-typed, this might be a little more off-putting. There are stricter ways to enforce types using tools such as Dialyzer, but in my experience, I've found those to be used relatively rarely. Let's now try using the information helper on a string of data to see what information we get back from IEx. Take a look at the following example:

iex(13)> i "Hello There"
Term
"Hello There"
Data type
BitString
Byte size
11
Description
This is a string: a UTF-8 encoded binary. It's printed surrounded by
"double quotes" because all UTF-8 encoded codepoints in it are printable.
Raw representation
<<72, 101, 108, 108, 111, 32, 84, 104, 101, 114, 101>>
Reference modules
String, :binary
Implemented protocols
IEx.Info, Collectable, Inspect, List.Chars, String.Chars

Here, we see that we have a standard string and that there are a lot of the same implemented protocols that we saw on floats and integers as well. We see the same few protocols (as well as a new one, Collectable). Now, if we want to see the operations provided by one of the reference modules (string in our case), IEx provides another awesome way to get that information out. In our IEx console, we can simply type in String. (notice the period!) and then hit Tab on our keyboard, for example:

iex(14)> String.
Break Casing Chars
Normalizer Tokenizer Unicode
at/2 capitalize/1 chunk/2
codepoints/1 contains?/2 downcase/1
duplicate/2 ends_with?/2 equivalent?/2
first/1 graphemes/1 jaro_distance/2
last/1 length/1 match?/2
myers_difference/2 next_codepoint/1 next_grapheme/1
next_grapheme_size/1 normalize/2 pad_leading/2
pad_leading/3 pad_trailing/2 pad_trailing/3
printable?/1            printable?/2            replace/3
replace/4 replace_leading/3 replace_prefix/3
replace_suffix/3 replace_trailing/3 reverse/1
slice/2 slice/3 split/1
split/2 split/3 split_at/2
splitter/2 splitter/3 starts_with?/2
to_atom/1 to_charlist/1 to_existing_atom/1
to_float/1 to_integer/1 to_integer/2
trim/1 trim/2 trim_leading/1
trim_leading/2 trim_trailing/1 trim_trailing/2
upcase/1 valid?/1

Let's use h/1 again to get a little more information about a particular string and the operations we can perform on it, as shown in the following snippet:

iex(15)> h String.replace/4
def replace(subject, pattern, replacement, options \\ [])
Returns a new string created by replacing occurrences of pattern in subject with replacement.
The pattern may be a string or a regular expression.
By default it replaces all occurrences but this behaviour can be controlled through the :global option; see the "Options" section below.

## Options
• :global - (boolean) if true, all occurrences of pattern are replaced with replacement, otherwise only the first occurrence is replaced. Defaults to true
• :insert_replaced - (integer or list of integers) specifies the position where to insert the replaced part inside the replacement. If any position given in the :insert_replaced option is larger than the replacement string, or is negative, an ArgumentError is raised.

# ...

That's a lot of information, yes, but it's also all incredibly useful. Let's start with a very simple operation and replace the e in our greeting variable to an x. We see that the signature for String.replace/4 is replace(subject, pattern, replacement, options \\ []). Given that, let's quickly create a greeting variable and change every e to an x:

iex(1)> greeting = "Hello"
iex(2)> String.replace(greeting, "e" ,"x", global: true)

"Hxllo"

Your objects have no power here

Another thing that might be a little more difficult to contend with, especially if you're coming from a language where object-oriented programming is treated as a first-class citizen, is that objects are not a thing you can use in Elixir to organize your code or group data and functionality together. Again, let's use JavaScript as an example, as follows:

var greeting = "Hello There"
greeting.toUpperCase() # Convert a string to upper case by calling a function on the "greeting" string object
# End result: "HELLO THERE"

Here, you'll instead want to get used to the concept of using modules and functions to accomplish the same things. You can think of modules as a collection of related functions that operate on particular sets or types of data, or are otherwise grouped together by some sort of common element. So, the preceding block of code would instead be written as the following in Elixir:

greeting = "Hello There"
String.upcase(greeting) # Results in the same "HELLO THERE" message as the Javascript example

This is going to be a very common pattern that you should familiarize yourself, as you will frequently see function called as [Module Name].[Function Name]([Arguments]). Try to shy away from calling functions on objects and data and instead get used to calling functions with the module name.

You're not required to do this; there are actually ways to shorten this even further through the use of the alias and import statements!
 

Introduction to Phoenix

Phoenix is a web framework that sits on top of the Elixir programming language, much in the same way that Rails sits atop Ruby or Express sits atop Node and JavaScript. There are a few areas where Phoenix shares a lot of the same core ideas and beliefs of other frameworks and areas where Phoenix differs pretty heavily.

Phoenix is very much designed to be a standard Elixir application and thus abides by the same rules and the same principles, but also encompasses all of the same things that make Elixir so great to work with! In my mind, the first and biggest thing that Phoenix gets via Elixir is first-class and dead-simple concurrency support. Writing code that performs and scales well on multiple core machines (that is, basically every single computer out there nowadays) is a breeze, and when you're just starting out building your Phoenix application, you won’t even have to think about it. It isn't until you start diving more heavily into the more advanced topics, such as OTP, GenServers, and Async Tasks, where you really need to start thinking deeply about your concurrency models and how everything fits together.

Phoenix also benefits very heavily from being based on a functional immutable language. Large codebases remain very simple to reason with, and the fear of introducing significant breaking changes because of a mutation deeply-nested in your code is now a thing of the past! If you're coming from a language where you've built a complex application and a simple change ends up breaking everything, this should be a breath of fresh air to you. I referenced this in one of my earlier examples, but when you actually see it in action and understand why it works the way it does, trust me, you won't want to go back to the old ways of building applications!

Before we do anything else, let's talk briefly about how to actually install Phoenix so that you can get started on working with it as soon as possible. The best (and most up-to-date) instructions can be found on the Phoenix framework website (https://phoenixframework.org), but just in case, here you are. Please note that the following instructions assume you already have Elixir installed.

Installing Phoenix 1.3

For best results, you'll want to make sure you have the most recent version of Hex installed on your local machine. Again, the assumption here is that you already have Elixir installed on your computer:

$ mix local.hex
Found existing entry: /Users/brandon.richey/.mix/archives/hex-0.16.1
Are you sure you want to replace it with "https://repo.hex.pm/installs/1.5.0/hex-0.17.1.ez"? [Yn] y
* creating /Users/brandon.richey/.mix/archives/hex-0.17.1

Now we'll move on to installing Phoenix itself. Installing Phoenix is a little bit different from just installing something via a hex package on an existing Elixir project. You'll want to use Phoenix’s project generators to get your project up and running, as there is a lot of additional setups required compared to a small, standard Elixir project. This is why you need to go through a separate process to install Phoenix.

To install the Phoenix framework and get access to the Mix tasks needed to create new Phoenix projects, you'll want to run the following code:

$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez
* creating /Users/brandon.richey/.mix/archives/phx_new
 

Creating a new Phoenix project

Now that we have something in place to help us understand Elixir and Phoenix and we've installed everything necessary, we can start to discuss how to actually begin implementing our first project in Phoenix. To get started on a project and generate the Phoenix application skeleton we'll need to start developing, we'll run a special command using the installed Mix application that came with Elixir. We also added the Phoenix Hex archive to our local Hex installation, so we should have access to a few new Phoenix-specific Mix tasks, such as mix phx.new!

Running the Phoenix Mix Task

Phoenix has gone through a few iterations of general architecture and project structures, but as of version 1.3, they've stuck on a design that is incredibly elegant and easy to use. Instead of the sometimes clumsy system they previously had, where you had your models, your controllers, your views, and your templates and nothing in between, you can now design things around controllers, views, templates, contexts, and schemas. Before we start diving into what those differences are and why they’re so important, let's talk about the general project structure of a Phoenix application.

The first major thing is that Phoenix applications are considered first-class Elixir projects, so as such follow the same major rules. Generally, Elixir projects should have a test directory and a lib directory. Your test directory is where any new tests you write and any tests you run will live out of lib, on the other hand, is where the actual code for any actual work should go. Let's explore how these directories will get filled out by creating a small sample throwaway Phoenix application.

To create a new Phoenix application, we'll start off by running the mix task phx.new:

$ mix phx.new sampler
* creating sampler/config/config.exs
* creating sampler/config/dev.exs
* creating sampler/config/prod.exs
* creating sampler/config/prod.secret.exs
* creating sampler/config/test.exs
* creating sampler/lib/sampler/application.ex
* creating sampler/lib/sampler.ex
* creating sampler/lib/sampler_web/channels/user_socket.ex
* creating sampler/lib/sampler_web/views/error_helpers.ex
* creating sampler/lib/sampler_web/views/error_view.ex
* creating sampler/lib/sampler_web/endpoint.ex
* creating sampler/lib/sampler_web/router.ex
* creating sampler/lib/sampler_web.ex
* creating sampler/mix.exs
* creating sampler/README.md
* creating sampler/test/support/channel_case.ex
* creating sampler/test/support/conn_case.ex
* creating sampler/test/test_helper.exs
* creating sampler/test/sampler_web/views/error_view_test.exs
* creating sampler/lib/sampler_web/gettext.ex
* creating sampler/priv/gettext/en/LC_MESSAGES/errors.po
* creating sampler/priv/gettext/errors.pot
* creating sampler/lib/sampler/repo.ex
* creating sampler/priv/repo/seeds.exs
* creating sampler/test/support/data_case.ex
* creating sampler/lib/sampler_web/controllers/page_controller.ex
* creating sampler/lib/sampler_web/templates/layout/app.html.eex
* creating sampler/lib/sampler_web/templates/page/index.html.eex
* creating sampler/lib/sampler_web/views/layout_view.ex
* creating sampler/lib/sampler_web/views/page_view.ex
* creating sampler/test/sampler_web/controllers/page_controller_test.exs
* creating sampler/test/sampler_web/views/layout_view_test.exs
* creating sampler/test/sampler_web/views/page_view_test.exs
* creating sampler/.gitignore
* creating sampler/assets/brunch-config.js
* creating sampler/assets/css/app.css
* creating sampler/assets/css/phoenix.css
* creating sampler/assets/js/app.js
* creating sampler/assets/js/socket.js
* creating sampler/assets/package.json
* creating sampler/assets/static/robots.txt
* creating sampler/assets/static/images/phoenix.png
* creating sampler/assets/static/favicon.ico

Fetch and install dependencies? [Yn]

We'll now be asked if we want to fetch and install dependencies. If we say yes, a few things will happen:

  1. First off, all of the standard Elixir dependencies that exist for all Phoenix applications will be downloaded from hex and compiled.
  2. Next, all of the Node/JavaScript dependencies that Phoenix applications depend on (assuming you did not use the --no-brunch flag when you created your new Phoenix application) will be downloaded from npm and compiled.
  3. Assets will be built and set up appropriately via Brunch.

If we say yes, we should see something akin to the following output:

* running mix deps.get
* running mix deps.compile
* running cd assets npm instal node node_modules/brunch/bin/brunch build

We are all set! Go into your application by running:

$ cd sampler

Then configure your database in config/dev.exs and run:

$ mix ecto.create

Start your Phoenix app with:

$ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

$ iex -S mix phx.server

When everything is done, we can enter our new project directory, create our database, and start up our Phoenix server!

$ cd sampler && mix ecto.create
Compiling 13 files (.ex)
Generated sampler app
The database for Sampler.Repo has been created
To create a database, you will need to have a username and password set up for your database server of choice that has the correct permissions or roles assigned that allow you to create databases. If you are working with a role or user that does not have that permission, you can also create the database manually and instead run mix ecto.reset.

Running the Phoenix server for the first time

There are two ways to start up the Phoenix server, and now that the application is set up and the database is there to support our application, we can move on to actually running it! The two different ways to run your application are either via Mix phx.server or in the IEx console. In the context of your application requiring debugging support via IEx, you need -S Mix phx.server. As a general rule, I only run my application in a local development environment through IEx. This allows you to get interactive debugging via IEx.pry, as well as the ability to perform things like Ecto queries or test calling public functions in modules.

Remember when we talked about the interactive help functions that are built-in to Elixir and can be accessed via IEx? Well, you can do the same thing with Phoenix functions, too! For example, let's say we want to know a little more information about how we send out JSON content directly from a controller, as shown in the following example:

iex(4)> h Phoenix.Controller.json

def json(conn, data)

Sends JSON response.

It uses the configured :format_encoders under the :phoenix application for
:json to pick up the encoder module.

## Examples

iex> json conn, %{id: 123}

Okay, so we now have our application and we have things up and running. If we visit our browser window, we should see the Phoenix startup page, as follows:

Phoenix's default application structure

As mentioned previously, Phoenix is itself a full-featured Elixir application, and as a result, tends to follow a lot of the same conventions and rules that other Elixir applications follow. We’ve briefly touched on the topic previously, but now it's time to get into it and take a look at how Phoenix projects are structured so that we can understand how to build on top of Phoenix. The starting directory structure looks a little something like the following:

├── README.md
├── assets
│ ├── brunch-config.js
│ ├── css
│ ├── js
│ ├── package.json
│ ├── static
│ └── vendor
├── config
│ ├── config.exs
│ ├── dev.exs
│ ├── prod.exs
│ ├── prod.secret.exs
│ └── test.exs
├── lib
│ ├── testapp
│ ├── testapp.ex
│ ├── testapp_web
│ └── testapp_web.ex
├── mix.exs
├── priv
│ ├── gettext
│ └── repo
└── test
├── support
├── test_helper.exs
└── testapp_web

Configuration files

First, we have a config directory in the root of our Phoenix application. This is where all of the application and web service configuration files will live. We typically have the config.exs, dev.exs, test.exs, prod.secret.exs, and prod.exs files by default with our Phoenix application. The first file, config.exs, is a file that stores generic configuration details that should be the same across all run-time environments for your application, whether they're testing, development, or production environments. The next few (dev.exs, test.exs, and prod.exs) are your environment-specific configuration files. Finally, we have our prod.secret.exs, which is auto-generated by Phoenix but not checked into source control by default. This file should never be checked into the source control for your application, as this is where sensitive information should live, such as API keys, secret keys, and database passwords. If this is ever checked into source control then it would be in the history of your application, which would be a pretty significant security concern for anyone having to maintain your Phoenix application!

Assets files

Next, we have our assets directory. This is where all of the front-end assets (images, CSS, JavaScript, and so on) live, as well as Brunch and NPM. (Your node_modules directory, for example, is found here.) The idea here is that by divorcing your front-end code and files from the rest of the work that Phoenix needs to do, Phoenix's developers can instead focus on making Phoenix great and leave the front-end and asset compilation problems to better solutions such as Brunch or Webpack, as well make asset compilation tool choices separately from those that created the framework. Even though Phoenix comes with Brunch by default, it's still straightforward to replace it with Webpack if that’s more suited to your speed.

Private files

There is also a priv directory, which is a little trickier to explain. Priv contains a lot of the helper and application setup code that is required. This contains your Repo's seeds file and migrations that need to be run (which we'll cover later), translations to your application via gettext, and finally, your static asset files (which have been built/compiled/transpiled/etc from your assets directory).

Tests

Your test directory contains all of the ExUnit tests for your application, which itself is broken up into two starting directories: (application name)_web and support (plus a test_helper Elixir script that contains some initialization code). (app)_web is used to contain the web application tests that cover controllers, contexts, views, templates, and so on, so you can think of this as the container for all of your Phoenix application tests. Support is instead used to bolster the functionality of your tests and make it easier to write new tests and share common logic or helpers. For the most part, we won't be modifying the code inside here very often. Most of the changes that we're going to make will instead be in the test/(app)_web directory and sub-directories.

Other directories

We also have a few miscellaneous directories that you won't really be interacting with at all. _build and deps, for example, contain various build artifacts and Elixir application dependencies (these won't actually get created until you build your application).

The most important directory: lib

Now we'll move on to the real meat of our Phoenix application: lib. Lib is where our application lives. By default, our Phoenix application is broken into two separate ideas. Using the application name of Sampler as an example, we will see two directories: lib/sampler and lib/sampler_web. The idea behind this is that every web application in Phoenix has at least two separate applications that work together to get the job done. The first is divorced from the idea of anything web-related, so there are no controllers or templates, JSON, or anything similar. One of the most common patterns that you’ll see here is all of your database-specific Ecto logic through contexts and schemas.

A context is a collection or boundary of domain-specific logic. For example, if you have users and organizations in your project, you may have a unifying context to those separate schemas called something like Accounts.

A Schema, on the other hand, is the one-to-one mapping of a database table to an application concept. Schemas need to be separated from the functionality of your code and used strictly to describe the shape of the data; this keeps your code pure from side-effects and helps make your code significantly easier to test in the long run.

Finally, we have lib/sampler_web. This is where all of the Phoenix-specific work gets done, so everything related to controller logic, templates, or views goes here in the appropriate sub-directory. Channel logic also falls underneath this particular design pattern. This is also where the endpoint.ex file lives, which defines how Plug constructs the Phoenix response to browser/API consumer/etc. It also determines how requests are parsed and how information is passed along to the Phoenix router.

Speaking of the router, our Phoenix router.ex file also lives here (again, because this is Phoenix-specific logic rather than the overarching common application functionality). This is where we will set up all of the rules to describe how to communicate between the browser and Phoenix's controllers.

A note about how data flows in Phoenix

If you're coming from the world of another web framework you might already be familiar with languages such as routers, controllers, templates, and views, but if you're not, let's talk a little bit about what those are, what they mean, and how the data flows in a Phoenix request all the way back out to the browser response. If you're already familiar with these topics, feel free to skip ahead to the next chapter.

When a request is received to a Phoenix application, the first thing that happens is that it gets handled by the endpoint. The endpoint's job is to take a look at the requests and to determine if there are any special functions or handlers that need to deal with the request. The request will flow through a series of plugs. A plug is a set of functionalities that takes in the connection data structure and performs a series of transformations and queries against the incoming request (for example, determining if the originator of the request is an approved server or making sure that the content types make sense). A plug must always take in a connection data structure (typically referred to as conn) and return out a conn, either modified or not. It can also additionally take in a set of options to determine how to modify the conn.

From there, the conn gets moved into the router via the endpoint. The router figures out where the information is coming from and where it is trying to get to. This is done via a combination of the URL hit, the content type, and so on. From here, if a valid route is found, the router will pass the conn on to the controller. A controller's job in a Phoenix application is to provide a means of determining what further transformations need to occur on the conn (which will eventually become our response from the server), as well as doing additional application logic such as running database queries, putting together templates, setting HTTP status codes, and much more. We can think of this as the glue for the different parts of our application that will help us construct our server's response.

The controllers then tell the view how to put together the data in the response and pass along the information it needs to determine the overall shape and feel of the response. It might be a JSON data structure if we're working with an API, or it may be a particular HTML template that we're sending back to the user with further information that is then sent along to the template. We can think of it as if the router answers where the controller answers what, and the view answers how. If we're not working with something simple, such as a JSON API, then there is an additional step that we have to work through called the template. The template helps Phoenix understand how, given a connection object, to build a response and what response to build, and a set of data to pass along to the consumer to build the logic that provides the nice look and feel we'd expect of a modern web app sending back HTML with layouts, partial pieces of content, and an overall structure.

 

Summary

We should now have a fine working knowledge of a standard Phoenix application. We should now understand how the application's structure, what the different pieces of the Phoenix application are, and how they're all used in conjunction to send something back to the browser when we, for example, send a request to http://localhost:4000/.

In the following chapters, we'll dive more into each of the functional areas of a Phoenix application and understand them at an even deeper level than this. Given that we have a strong foundation, we now need to move on to the real way to learn Phoenix: by building a new Phoenix application from start to finish!

About the Author
  • Brandon Richey

    Brandon Richey is a software engineer and a React enthusiast who has written several popular React tutorials. He has been making professional and hobby programming projects spanning healthcare, personal sites, recruiting, and game development for nearly 20 years! Brandon is the author of the Packt book 'Create React App 2 Quick Start Guide'.

    Browse publications by this author
Latest Reviews (4 reviews total)
So far so good. Not got too far into the book as other things have affected me - the same as a lot of people in 2020. And then there was Christmas. Hopefully, in the New Year I'll get into it more
Buena relación calidad - precio , libro muy actualizado
Great experience, already purchased many books from Packt. Thanks
Recommended For You
Phoenix Web Development
Unlock this book and the full library FREE for 7 days
Start now