Anatomy of a Phoenix App

Written by

Today, there are so many different tech stacks available that it can be hard to commit to just one. I've been researching a ton of different languages and frameworks to try and find a combination that I really like. The winner: Elixir and Phoenix.


Elixir is a fun language that is really comfortable to write and packs a lot of really cool features that are actually useful in real-life situations. Phoenix is a web framework that provides a clear path for development while still allowing for flexibility.

In this article, I want to give a high-level overview of concepts involved in building a Phoenix app so people interested in Elixir and Phoenix can determine if it’s for them.

Things to know about Elixir


Elixir is a programming language that was created by José Valim, a member of the Ruby on Rails Core Team. Although Elixir is a young language, it has a very rich ecosystem and I haven't had difficulty finding packages to accomplish common goals.

Elixir is a compiled language but it runs on the Erlang VM or BEAM. Erlang was originally designed for telecom applications; this means Erlang was designed with real-time, highly available, fault-tolerant, and concurrent systems in mind, which is great for Elixir. 

However, if you come across something that you can't accomplish in Elixir, you always have the option to break out to the Erlang environment where you have an entirely different ecosystem of features and packages. This is something that should probably only be used as an escape hatch and I have not delved into this particular feature myself; you are on your own if you try this, but it's nice to know the option is there.


Most of the code you write in Elixir is actually run within processes; this isn't immediately apparent when you first start building. It's important to note that these are not OS processes but lightweight Erlang processes that are more akin to threads. Each process is an encapsulated bit of code that manages its own state and communicates with the rest of the app by sending and receiving messages.

All of these processes are managed in a supervision tree. There is a supervisor that orchestrates all of its subprocesses. This idea of using processes and supervisors to manage trees is what gives Elixir its fault-tolerant quality. If a single process crashes, only that one crashes and the supervisor can determine whether it should restart it.

Things to know about Phoenix

General Philosophy

Phoenix is a modern web app framework built for Elixir. It is highly opinionated but in a good way. You probably think when hearing a framework is “highly opinionated” that it's going to have great support for the usual needs in building an API, but as soon as you need to break out and do something unusual you have no choice but to rely on weird hacks and ugly code. With Phoenix, I haven’t seen this problem. Its architecture lets you weave in any code you need with ease.

Aside from this, Phoenix follows a general MVC architecture with just a few caveats. Rather than stopping at the number of abstractions that yield an intriguing acronym, they took it just a little bit further. At first, this may seem overly verbose and adds a ton of boilerplate code; but as your app grows you'll be grateful that your code is automatically organized in a manner that makes for easy refactors and extending.


Elixir relies on a library called Plug to coordinate changes. Essentially, Plug allows you to define small functions or modules that take in a data structure – typically a connection – and returns it with slight modifications. This means that every request to your Phoenix server essentially passes through a series of functions until all the necessary data is available to send as a response.


As you dig around the code in a Phoenix app you'll see a lot of random words being used as if they are keywords; they are a feature of Elixir that generates more code in the form of macros. At first, I was a little worried by the "magic" that these macros provide, but used tastefully, they can be a great help and reduce a lot of complexity. Macros are another feature I haven't had time to take a deep dive into, but I can see how they are helpful (I can also see how they could be extremely dangerous).

The Phoenix App Architecture

As mentioned earlier, Phoenix follows an MVC pattern, so the flow through your app should be familiar:

Endpoint -> Router -> Controller/Context -> View -> Template

In order to follow along, we'll spin up a Phoenix app. This requires Elixir installed on your machine. For the sake of brevity, I will not include the steps to get Elixir installed, but I recommend managing your installation with asdf. The website has a great article that explains it:

Once you have it installed, we can use mix to install the Phoenix app generator:

$ mix archive.install hex phx_new 1.5.1

We can now generate a new Phoenix project by running:

$ mix anatomy_app

Finally, we can move into our new project directory and start our server:

$ cd anatomy_app$ mix phx.server

With our new project set up, onward to the architecture!


When a connection is received by a Phoenix app, the first stop is the endpoint. The endpoint is just a Plug that manages a few concerns:

  • It provides a wrapper for starting and stopping processes that are part of the supervision tree.
  • It defines the initial Plug pipeline to pass the request through.
  • It manages configurations for your application.

If we open up the file that defines our endpoint, lib/anatomy_app_web/endpoint.ex, we see that the file uses the Plug and socket macros to define the usual things a web server needs to do: setting up sockets, handling static content, managing session, and forwarding the connection to the router.


Open the file lib/anatomy_app_web/router.ex. Much like the endpoint file, it’s using macros to create different Plug pipelines to pass the request through before passing to the controllers. The router is a great place to make any updates to your connection that you will need downstream. Since the router is deciding which controller to forward the request to, it's a great place to include your authorization and authentication code.

Looking at the file, we see several macros:

  • pipeline - This macro is used to group Plugs together to be run as a unit. You can see we have a pipeline called browser and it is just doing some general webserver tasks like fetching the session and performing basic security checks.
  • scope - The scope macro allows you to group certain routes/groups of routes together so that all requests are run through the same set of Plugs. A great use case for this would be hiding admin routes that verify the user is an admin before allowing them to continue.
  • get - This is simply defining a route for any GET requests made to “/” and forwards the connection to the index function in the PageController. Phoenix provides macros for all request types and includes a resources macro for defining them at once following a naming convention.

Once the request makes it through the appropriate pipeline of Plugs for its scope it is passed along to the controllers.


The controller is where we see the “opinionated” structure that Phoenix enforces. Rather than having a controller that manages reading, updating, collecting, AND organizing, it splits these responsibilities. This isn't really a revolutionary idea and you've probably seen it done with things like an ORM, where we write controllers that dispatch to models, services, etc. In Phoenix, we have contexts.

Contexts are logical groupings of our data that manage data validation, access, and making updates to storage. To make this process easier, Phoenix uses a library called Ecto. Ecto gives us some macros that map the schema of our data structures to tables in our database. It also handles changes made to our database using a construct known as a changeset. Essentially, changesets allow us to group the changes that take place in different requests and run validations before committing them. Again, this seems like an unnecessary abstraction, but being able to scope the changes gives us a lot of power and is really useful in eliminating those that could break our schema/app. The full power of Ecto and changesets are beyond the scope of this article; however, the docs for Ecto are pretty thorough and easy to understand:

With our contexts in place, we can then use them in our controllers to gather/orchestrate data and pass along to views. The project we generated does not include any contexts, but they reside in /lib/anatomy_app. If we were to create an accounts context we would create the following files:

  • /lib/anatomy_app/accounts.ex
  • /lib/anatomy_app/accounts/user.ex

This is a hypothetical set up, but essentially lib/anatomy_app/accounts/user.ex defines a user context that manages reading and writing data, and lib/anatomy_app/accounts.ex uses this context to orchestrate data access/reading for the entire context. These can then be used in our controller for fetching and working with the data.

We were provided with a single controller in the project that we generated and it can be found at lib/anatomy_app_web/controllers/page_controller.ex. If we open this file up, we see it is very sparse:

defmodule TestWeb.PageController do  
	use TestWeb, :controller    
    	def index(conn, _params) do  
    render(conn, "index.html")  

What’s happening here is we instructed Elixir to bring in all the functionality that Phoenix provides for controllers with the use statement. We then define a function index that takes a connection and passes it to the index.html template for rendering. If you’ll recall, back in our router, the index route was defined like this:

get "/", PageController, :index

Knowing the contents of our controller, this statement should make a little more sense. All it is saying is: “Forward any GET requests to ‘/’ to the PageController’s index function.”


Just like with contexts and controllers, Phoenix splits our code for handling views into separate pieces. For the most part, views will be pretty sparse; Phoenix's naming conventions and "magic" remove a lot of the code. If you’re building a full web app with Phoenix, meaning you are using Phoenix's templating to generate a site, you will mostly have formatting and data preparation code. If you're building a JSON API, views are where you will do renaming and formatting.

For both a web app and a JSON API, we then pass the data from the views to a template to be rendered. Phoenix provides an HTML templating language that should be very familiar. Within our templates, we can access the connection and data we've accrued throughout our Plug pipeline. Phoenix templates are written much like components in React; each file maps to a certain view and handles displaying data for that page. The markup language is a little odd at first but becomes familiar pretty quickly. It also provides some nice features, such as links to easily weave the different parts of our application together.


When learning a new language or framework, it's very easy to find short, focused articles; however, I typically struggle with getting a high-level understanding of the different pieces and how they fit together. I wrote this comprehensive overview for people coming to Elixir and Phoenix to help them determine if they want to continue learning. I hope this helps!

Frequently Asked Questions