Learn how to deploy Elixir apps on Heroku
Tags: elixir, computerscience, programming, beginners
Published: 2021-02-12 08:45:37

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/so3bki7r18trxobkd2gf.jpg)

With every app we decide to take out to the world, there's one important decision to make when the time comes.

Where is the best place to deploy it?

The short answer is: "It depends, probably there's no one best place actually".

The long answer however, will depend on what your needs are, your budget, the setup of your current applications, company restrictions etc.

Particularly, I think a really convenient way to deploy your apps in no time is using Platform as a Service (PaaS) options like [Heroku](https://www.heroku.com/).

Heroku is a platform that allows you to abstract yourself from all the complexity that comes from deploying and maintaining an app, and to focus your efforts on the app you want to build. Here's a [video](https://www.youtube.com/watch?v=_N8Zf_nPZkQ&feature=emb_logo&ab_channel=Heroku) from Heroku's Greg Nokes explaining the concept behind their platform.

Heroku has this idea of [buildpacks](https://devcenter.heroku.com/articles/buildpacks), which transform your code into a slug that can be run on their platform and abstract all the complexity.

They support a small range of popular programming languages using these buildpacks, however Elixir is not one of them.

But not all hope is lost, we have the ability to use custom buildpacks to support it ourselves. And there is [one already available](https://github.com/HashNuke/heroku-buildpack-elixir) that Akash Manohar and his contributors built for us.

If this is all new for you, don't worry, we're going to start putting up the pieces together in a bit.

Let's build a very small app using Elixir that queries an open API and displays the data via HTTP in a JSON format.

Before you get started, you'll need a Heroku account. You can create a new one for free at [heroku.com](https://signup.heroku.com/), and there's a free tier we can use for our development projects.

If you're not too familiar with Elixir, you can check my [hello-world-elixir](https://rarias.dev/hello-world-elixir/) article to give you an overview.

This article will be divided into 5 parts

1. Creating your Elixir app skeleton.
2. Writing the basic functionality.
3. Testing it works locally.
4. Creating your Heroku app.
5. Deploying and testing your app.

## 1. Creating your Elixir app skeleton

There are multiple ways to create an Elixir app. You could use one of the available frameworks or you could potentially create the files yourself one by one for example. In this exercise, we'll use the convenient [mix new](https://hexdocs.pm/mix/Mix.Tasks.New.html) helper that will create most of the initial structure for us.

Go to your project folder on your shell, then type `mix new simple_app --sup`

```bash
❯ mix new simple_app --sup
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/simple_app.ex
* creating lib/simple_app/application.ex
* creating test
* creating test/test_helper.exs
* creating test/simple_app_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd simple_app
    mix test

Run "mix help" for more commands.
```

This will create a basic skeleton for your app, including a module called `SimpleApp` and a basic supervisor.
Make sure to check your version of Elixir. Syntax and structure might differ slightly on different versions. I'm using 1.11.3 for the following exercise.

Let's go ahead and compile our app first to test the hello method.

```bash
❯ cd simple_app
❯ mix compile
Compiling 2 files (.ex)
Generated simple_app app
```

We can use the interactive console to test manually or just use `mix test` to use the test suite

```bash
❯ iex -S mix
Erlang/OTP 23 [erts-11.1.7]

Interactive Elixir (1.11.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> SimpleApp.hello()
:world
```

By typing `SimpleApp.hello()` we are invoking the `hello` method created by the helper and we should get a symbol `:world` in return if everything is set up correctly.

## 2. Writing the basic functionality

For this app, we'll use the public [API of Coindesk](https://www.coindesk.com/coindesk-api) to query the price of Bitcoin today. If you'd rather prefer to use a different API, you can find a list of public ones on this [Github repo](https://github.com/public-apis/public-apis#cryptocurrency). The data that we're getting is really not that important for the purpose of this exercise.

Before continuing however, you may want to check the API you choose is operational and to check the format of the returned data. I'd use `curl` in my shell, but `wget`, Postman or any other UI client should do the trick.

```bash
❯ curl https://api.coindesk.com/v1/bpi/currentprice.json
```

To integrate the API via Elixir let's use the HTTP wrapper [Tesla](https://github.com/teamon/tesla). There are many good options out there, such as the good old [Httpoison](https://github.com/edgurgel/httpoison). However, Tesla has some added benefits. I won't go into details as it's not the purpose of this article, but it's worth checking out.

To install Tesla, we need to first add the dependency to our `mix.exs` file

```elixir
defp deps do
  [
		{:tesla, "~> 1.4.0"},
		{:hackney, "~> 1.16.0"},
		{:jason, ">= 1.0.0"}
		...
	]
end
```

Notice that I also included `hackney` and `jason`. We'll use Hackey as the adapter to actually perform our http calls. Tesla uses `httpc` as the default adapter, but Hackney is far better in many aspects.

We'll need `jason` too as it's a required JSON parser for our middleware. It's also more performant than other JSON parsers so it's good to have anyway.

Go back to your shell and install the newly added dependencies

```bash
❯ mix deps.get
```

You may not have `hex` installed in your system yet, so if you get prompted an option to do it, just say yes.

The last part of the Tesla setup is adding the adapter to our config file. If you haven't created the file yet, just put it under `config/config.exs`.

We can then add the Hackney adapter so your config file should look like this

```elixir
use Mix.Config

config :tesla, adapter: Tesla.Adapter.Hackney
```

And that's it, we're done configuring. Throw in a `mix compile` for good measure, to make sure we're still good to go and we haven't made any typos.

### The API calling class

To query the Coinbase API via Tesla, we're going to create a model.

I like to have business logic inside a models folder because it helps me keep things organised on larger projects. Go ahead and create `lib/models/coinbase.ex`

Inside it we'll need 3 things, our module definition, our Tesla middleware configuration and the method(s) we want to abstract.

```elixir
defmodule Coinbase do
  @moduledoc """  
  A module abstraction used to interact with the coinbase http API  
  """
  use Tesla

  plug Tesla.Middleware.BaseUrl, "https://api.coindesk.com"
  plug Tesla.Middleware.Headers, [{"Content-Type", "application/json"}]
  plug Tesla.Middleware.JSON

  @doc """  
  Returns the Bitcoin Price Index from coinbase  
  """
  def bpi_current_price do
    get("/v1/bpi/currentprice.json")
  end
end
```

We can test this right away in our `iex` console

```bash
❯ iex -S mix
Erlang/OTP 23

Interactive Elixir (1.11.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Coinbase.bpi_current_price
{:ok,
 %Tesla.Env{
   __client__: %Tesla.Client{adapter: nil, fun: nil, post: [], pre: []},
   __module__: Coinbase,
   body: ...,
   headers: [
     ...
   ],
   method: :get,
   opts: [],
   query: [],
   status: 200,
   url: "https://api.coindesk.com/v1/bpi/currentprice.json"
 }}
```

### Displaying the data

With our base model done, the last thing we need is something to display the data in an easy to read format.

We are going to be using the light http server [Cowboy](https://github.com/ninenines/cowboy) and the adapter [Plug](https://github.com/elixir-plug/plug) to return our data.

Exploring the different server options and frameworks along with their pros and cons is out of the scope of this article, so we'll stick with the simplest solution.

Let's go back to our `mix.exs` file to add Plug and Cowboy to our dependencies

```bash
 defp deps do
  [
		{:tesla, "~> 1.4.0"},
		{:hackney, "~> 1.16.0"},
		{:jason, ">= 1.0.0"},
		{:plug_cowboy, "~> 2.0"} # NEW!
		...
	]
end
```

Get those deps, compile the project and we're ready to move forward

```bash
❯ mix deps.get
❯ mix compile
```

Now, without caring for organisation too much, let's add a little router file that will expose our endpoint to the world and call the API we've created previously.

Go ahead and create `lib/router.ex` and define a basic Plug endpoint

```elixir
defmodule SimpleAppRouter do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/bpi" do
    send_resp(conn, 200, "BPI price coming soon...")
  end

  match _ do
    send_resp(conn, 404, "Wrong place mate")
  end
end
```

If you're not familiar with it, feel free to check the inner workings of Plug in their [documentation](https://github.com/elixir-plug/plug). For now, the code above is fairly self-explanatory I hope. All we need to know is that we're using the `Plug.Router` capabilities and exposing an endpoint `/bpi` which we're going to use to retrieve our data and to show it.

We can now proceed to tell our supervision tree that our Router is ready to roll.

Go to the `lib/simple_app/application.ex` where you'll find the application file created by the `mix new` helper. Here we will ask our application to start the router under the supervision tree.

Your resulting file will look like this

```elixir
defmodule SimpleApp.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc """
  Application that starts the http router for `SimpleApp`.
  """

  use Application

  @impl true
  def start(_type, _args) do
    children = [
      {Plug.Cowboy, scheme: :http, plug: SimpleAppRouter, options: [port: 8080]}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: SimpleApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

You can go ahead and test it all works fine by running your server with

```elixir
❯ mix run --no-halt
```

To display the Coinbase data, all we need to do is go back to our `SimpleAppRouter` and add the Coinbase call. Your router function will then look like this

```elixir
get "/bpi" do
  send_resp(conn, 200, Coinbase.bpi_current_price)
end
```

If you remember the format of the `bpi_current_price` function response, you may have noticed that it includes not only the body of the response but also headers, process status etc which we don't necessarily want to display. So to fix this, go back to `lib/models/coinbase.ex` and modify the function to only return the response body instead

```elixir
def bpi_current_price do
    {:ok, response} = get("/v1/bpi/currentprice.json")
    response.body
 end
```

Bear in mind we're not doing any sort of validations or error considerations for the sake of simplicity.

## 3. Testing it works locally

Now with the app finally ready, we can test it works in our local environment.

Go ahead and run your server again

```bash
❯ mix run --no-halt
```

Then in your browser or curl client, you should be able to obtain the results on [`http://localhost:8080/bpi`](http://localhost:8080/bpi)

```bash
❯ curl http://localhost:8080/bpi
HTTP/1.1 200 OK
...
server: Cowboy

{ ..."chartName":"Bitcoin","bpi":{"USD":{"code":"USD"... }
```

## 4. Creating your Heroku app

Before we move entirely into the Heroku app creation, there's one last thing we need to change in the code. 

Notice how we used port `8080` as the default, but once in Heroku our app port will be dynamic and determined by them. For this reason, we need to read the port from the environment variables.

To do this, go ahead and update the port 8080 inside the application file with one taken from the system environment variables.

```elixir
children = [
  {Plug.Cowboy, 
		scheme: :http, 
		plug: SimpleAppRouter, 
		options: [port: (System.get_env("PORT") || "8080") |> String.to_integer()]
  }
]
```

This has the added benefit of defaulting to 8080 if the `PORT` environment variable does not exist, which is convenient when using it locally.

To create our Heroku app we're going to be using the [heroku-cli](https://devcenter.heroku.com/articles/heroku-cli). However, if you feel more comfortable using an UI, you can go to your [Heroku dashboard](https://dashboard.heroku.com/apps) and the steps are pretty straightforward.

The first thing you need is a Heroku account, which I'm going to assume you already have. If you don't, just sign up on their [website](https://www.heroku.com/) using your preferred email and password.

PRO TIP: If you happen to have an existing Heroku account that you do not want 
to use for this exercise, you can use the [heroku-accounts](https://github.com/heroku/heroku-accounts) plugin to manage
multiple accounts easily.

```markdown
PRO TIP: If you happen to have an existing Heroku account that you do not want 
to use for this exercise, you can use the [heroku-accounts](https://github.com/heroku/heroku-accounts) plugin to manage
multiple accounts easily. 
```

To create a new Heroku app using the cli, just go to the root directory of your app in the shell and type

```bash
❯ heroku create simple-app-elixir
Creating ⬢ simple-app-elixir... done
https://simple-app-elixir.herokuapp.com/ | https://git.heroku.com/simple-app-elixir.git
```

Where `simple-app-elixir` is a unique name you want to give to your app.

Because Heroku doesn't have any default buildpack for Elixir at the time of writing, we need to set our app's buildpack to HashNuke's version of it.

```bash
❯ heroku buildpacks:set \
  https://github.com/HashNuke/heroku-buildpack-elixir.git -a simple-app-elixir
```

For this buildpack to work in production, we need to specify a default configuration on our Heroku app. In the root of the project go ahead and create a file named `elixir_buildpack.config` and fill it with your versions of the OTP and Elixir.

```bash
erlang_version=23.0
elixir_version=1.11
always_rebuild=false
runtime_path=/app
```

The last thing we need to do is to tell Heroku what process to run when deploying the app. For this we can use a `Procfile`. In our Procfile we will specify that we want `mix` to run our app which in turn will run the Cowboy server.

Create another file at the root of your project called `Procfile` (without any file extension), and add the following inside it

```bash
web: mix run --no-halt
```

Notice this is the same command that we use to run our server locally. The buildpack will actually use this same command as default if no Profile is found, but it's good practice to define it ourselves anyway.

## 5. Deploying and testing your app

Now we're ready to deploy. Heroku has a `git` based deployment system, which is really convenient because all we need to do to deploy our app is to `git push` our code.

In your shell, at the root of your project, go ahead and commit your code to Heroku as if it was another regular git repository. 
You may need to add your Heroku remote using your own git URL

```bash
 ❯ git remote add heroku [https://git.heroku.com/simple-app-elixir.git](https://git.heroku.com/simple-app-elixir.git)
```

Yours was printed when creating the app, or it can also be found in the settings of your Heroku app. 

```bash
# If you haven't initialised the repository yet
❯ git init
Initialized empty Git repository

# Commit your code
❯ git add .
❯ git commit -m "SimpleApp initial commit. Coinbase API"
❯ git push heroku master
```

This should've deployed your app if your setup is correct, SSH keys are set and there aren't any additional configuration issues.

Once your code is deployed, Heroku will assign a subdomain to your app, something like 

```bash
https://simple-app-elixir.herokuapp.com/
```

Depending on the time you're reading this article, mine may or may not be active. Either way, once yours is deployed you should be able to check it out.

You can go ahead and test our `/bpi` endpoint in the browser or via curl.

```bash
❯ curl https://simple-app-elixir.herokuapp.com/bpi
```

This endpoint should return a json object with the Coinbase Bitcoin exchange rates and your deployment would be complete. Congrats! 👏

## Conclusion

As you can see, it's fairly simple to deploy Elixir apps in Heroku. The platform allows for scaling as you go and it's able to handle Elixir's performance requirements very well.

Another advantage of Heroku is that you can have apps in multiple languages running all under the same umbrella platform, which lets you have complex microservices architectures servicing different needs without any additional platform knowledge required.

I've put a full working version of the code on this [Github repository](https://github.com/ronald05arias/simple-app-elixir), in case you want to compare with it or simply clone it and test it.

For more topics, check my [profile](https://dev.to/ronald_arias) or visit [rarias.dev](rarias.dev).
Happy coding!