A Phoenix Field Guide For Djangonauts

First, let me get this out of the way: this is not a Django vs Phoenix post. We at Cheesecake Labs believe polyglotism is good; it gives us options. Since the beginning, we’ve mostly been a Django shop for backend services and will continue to be for many years to come. It is a stable, fully-fledged and widely adopted framework that’s used to power a shocking amount of large applications, including many client projects that we’ve developed over the years.

However, being a pragmatic dev shop, we’re open to new tech and, every once in a while, we work on projects with requirements where Django is not the best fit. In the past, we’ve used Clojure and GraphQL and we started using React right at the beginning, when most devs had reservations about it and Angular was all the rage. One of the new things that we’ve been experimenting with in the last few months is the Phoenix framework.

Phoenix is written in Elixir, a new language that shares the same VM as Erlang. As a recovering Erlang programmer, I was always very skeptical about Elixir. However, after a few weeks knee-deep in a Phoenix/Elixir project, I can confidently say that I was wrong: it is awesome. So here’s a guide for the djangonauts hoping to make the leap to Phoenix. But first, let’s start with a few reasons why to choose Phoenix for your next project.


Why Phoenix?

Simplicity and Modularity

Not only did Phoenix learn many lessons from the Django and Rails worlds, but it also benefits from the underlying functional programming model of the host language: everything’s about data flow. You have a small number of known data structures being transformed by pure functions that you string together. Also, a Phoenix application depends on fewer moving parts than a typical Django one.


Phoenix can handle far more requests per unit of hardware than Django, using its unparalleled capabilities for soft-real time features.

The Beam

The Erlang virtual machine is famous for being a marvel of engineering that was built for robust fault tolerance and massive concurrency. It allows for low latency garbage collection, lightweight processes with their own stack and heap that can be cheaply spawned (Erlang can run millions of these little processes and one of them crashing doesn’t affect the others), hot code reload and excellent debugging and monitoring tools. Phoenix takes advantage of all of this in a comprehensive package.

About The Guide

This guide assumes you at least know what the Phoenix Framework is. If this isn’t the case, head down to phoenixframework.org and learn more.


Since most Linux-based distributions (and MacOS) come with Python pre-installed, a simple pip install Django is usually enough to get rolling. In the case of Phoenix, since Elixir doesn’t come pre-installed with the OS, you’ll need to install both Erlang (the underlying runtime) and Elixir. But fear not, Elixir is just a brew install elixir away (it also installs Erlang) on MacOS and most modern package managers provide a recent binary.

After that, you need to install the Hex package manager: mix local.hex. Hex is a package manager for the Erlang VM, so this is the equivalent of installing Pip.

Now you can install Phoenix:

mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez

Whereas in Django you use the django-admin command to generate a new project, in Phoenix you use the Mix tool (more on mix later) to run pre-defined tasks: mix phoenix.new my_project.


Mix is a build tool that comes with Elixir; you will use it to create a new project, manage its dependencies and compile it. There’s no real equivalent in Django-land, but it sort of fills the role of Django management commands (manage.py) + pip + a task runner.

To get a list of available tasks, just type mix help


This is a step that is non-existent in Django/Python so it should be reinforced: Elixir is a compiled language, so every time you fetch new dependencies or update your code, you’ll need to recompile your project. Thankfully, Phoenix has a great system for automating this. Live reload instantly refreshes your browser when you change and save JS/CSS/template files, it feels like magic and works out of the box. Here’s a good explanation of how it works.

Folders and Structure

So you’ve created a new project and are now wondering what the heck all those folders are. Now, the first thing to understand is that, unlike a Django project that follows it’s own conventions, a Phoenix project follows closely the structure of an Elixir project, which is also based on the typical Erlang/OTP layout. Here’s the lowdown:
lib/ where your app code will live (web and non-web; more on this later);
assets/ for CSS, javascript and static files. Like the static/ folder in Django;
config/ like your settings.py file;
deps/ external libraries that are automatically fetched by mix;
priv/ application-specific files that you need alongside your code, like migrations;
test/ all files regarding tests;
mix.exs like a requirements.txt file.

Phoenix encourages you to organize your code in a way that doesn’t tie itself with the framework so that your business logic is separated from your web delivery logic; Phoenix is not your application, it’s just a presentation layer. This is why inside your lib/ folder you will find my_project/ and my_project_web/.


Django is an MVC framework, but it deviates from standard MVC terminology. In Django, the controllers become the views, and the views become the template. In Phoenix, the controllers are really the controllers (Django views), the templates are also the templates and the views are reusable bits of front-end code, like the Django template tags.

Database Schema & Migrations

In Django, migrations are automatic by default. This is both a source of extreme productivity in the beginning and acute pain when things go wrong. Migrations are not automatic in Phoenix, but there are quite a few tools to help you write migrations without falling back to SQL. The migration files are located in the priv/ folder and can be scaffolded by running the mix phx.gen.schema task.

Shell and Debugging

One of the great things about Django is how easy it is to debug or test a small piece of code of your app. Just run ./manage.py shell and you’ve got a Python shell session with your project settings preloaded (for an even better experience, try iPython + shell_plus from django-extensions). Phoenix takes this a level further by allowing you to connect to your running app and inspect it live: iex -S mix phx.server.

It is very common to use pdb when you need access to the context of a function call in Django. IEx.pry gives you a similar interactive debugging experience.


Phoenix owes its modularity to Plug. It is a standardised interface for composing functionality for different HTTP servers, pretty much like a modern WSGI. The thing is, unlike Django, where you define global middleware in a Django-specific interface, all the pieces in Phoenix are implemented as plugs: endpoints, routers, and controllers, which makes Phoenix very modular and composable. Imagine if most Django functionality were written as WSGI apps. That’s pretty much the reality of Phoenix apps.

Rest APIs

The most obvious option for rest APIs in Django is DRF, which is an external package that acts as a framework on top the framework. Some people love this approach, some people hate it. You can build a restful API without the need for any extra package in Phoenix.


It is not a secret that Django templates are very slow and use lots of memory. Unless your project is very small, it is recommended that you cache your templates. Caching is almost unnecessary in Phoenix since the compiled templates become Erlang IO lists that occupy the same chunk of memory across multiple requests. This means that while in Django, for every new request the framework creates a new render template, which uses a different chunk of memory, in Phoenix, these requests are being served from the same chunks of immutable memory. This is a huge win for memory footprint.


There are no models as you know them in Django because there is no ORM. Phoenix uses Ecto as the DB interface and when Ecto fetches a row in the database, it doesn’t have to convert that row into an object (remember, Elixir is not object oriented), you just get back a map. When you want to insert or modify your data, you use changesets.

Instead of models, you define schemas and use Ecto to interact with your database. Conceptually this is probably the hardest part to figure out but also the most rewarding. Ecto makes every sql query obvious and shooting yourself in the foot with N+1 queries becomes much more difficult than with Django’s ORM. Also, like SQLAlchemy, Ecto is not married to Phoenix.


Both frameworks have an abstraction layer above WebSockets called Channels. Phoenix was designed from the ground up for WebSockets connections, so you won’t need an external package like django-channels.


Nothing beats the Django admin for getting off the ground quickly and unfortunately, Phoenix does not come with a default admin interface. However, there are a few packages that do a pretty decent job, like ex_admin.

Background Processing and Tasks

Any reasonably complex web app will eventually need some sort of concurrency support: scheduling tasks, running something in the background or after a request has finished (like sending e-mails). In Django, the usual solution for this is a task queue like Celery or django-rq. Although these libraries are mature and battle-tested, this adds a layer of complexity: you now have to add another moving part that might fail to your app (like Redis or Mongo) and you’ll also have to launch worker processes and monitor queues. This is unnecessary in Phoenix/Elixir since you can just spawn worker processes, send messages and, when you need persistence, use Mnesia, a heavy duty real-time distributed database that ships with the Erlang runtime.


Django uses urls.py for routing. Phoenix has a similar convention: the router.exs file.

Javascript Integration

This part is up to the developer in Django. Phoenix comes out of the box with a pre-defined layout, tasks, and a build tool (Brunch) to automatically compile and integrate your JS files with your project.


Django is an excellent choice for a wide range of apps and will continue to evolve for many years to come. It has a huge ecosystem, the best documentation you could hope for in an open source project and that should be taken into account by those planning to use Phoenix. If, however, Phoenix is the right choice for your project, I believe you will be pleasantly surprised by the framework’s maturity and productivity.

About the author.

Rodrigo Landerdahl
Rodrigo Landerdahl

Aimless contrarian, insurgent lisper, coffee addict. Loves sports and great food.