Introducing Tesla — the flexible HTTP client for Elixir

Tymon Tobolski
Sep 7, 2016 · 2 min read

After a year and a half year since the first commit it is time to finally write something about tesla — the flexible HTTP client library for Elixir.

The story behind it is dead simple - coming from ruby world I was missing the fantastic faraday equivalent, so there was no other option than filling the gap. (You probably figured out origins of the name by now.)

Why another HTTP client library?

There are already some great elixir HTTP clients like httpotion or httpoison. Each of them is based on different erlang library, ibrowse or hackney. They both allow writing custom API clients providing specific callback to manipulate requests and responses. Having said that, the most important missing trait for me was the lack of support for composing API clients from ready-made blocks — middlewares. Having a rich, well-tested toolset right at hand is invaluable. Since besides middleware tesla also supports multiple different adapters we (the elixir community) can implement, share and reuse common patterns without worrying about underlying low-level details.


You can use tesla directly like this:

Tesla.get(“”, query: [q: “tesla”])

Let’s build a GitHub API client

Take a look at the following code snippet:

defmodule GitHub do
use Tesla

plug Tesla.Middleware.BaseUrl, ""
plug Tesla.Middleware.Headers, %{"User-Agent" => "tesla"}
plug Tesla.Middleware.JSON

def user_repos(login) do
get("/user/" <> login <> "/repos")

As you can probably already guess this GitHub API client will hit with “tesla” User-Agent header. The request body will be encoded as JSON and response body will be decoded as JSON (if given supported Content-Type).

Now you can use this client as follows to get list of repos for a user:


Dynamic API clients

I’m sure you are thinking about dynamic properties like authorization that you will need to pass around at function arguments. Worry not, tesla got you covered:

defmodule Gitalyzer.GitHub do
use Tesla.Builder
with Tesla.Middleware.BaseUrl, ""
with Tesla.Middleware.Headers, %{"User-Agent" => "Tesla"}
with Tesla.Middleware.JSON
def new(token) do
{Tesla.Middleware.Headers, %{"Authorization" => token}}
def user_repos(client, login) do
get(client, "/user/" <> login <> "/repos")

Tesla.build_client/1 allows you to create a local client by dynamically inserting middleware (with arguments). Using that client is then as simple as this:

client =
repos = client |> GitHub.user_repos("teamon")

How about middleware?

Every middleware is either a local function/3 or a module with function call/3. It takes Tesla.Env struct with request parameters, the next middlewares and some options.

defmodule MyMiddleware do
def call(env, next, options // []) do
|> do_something_with_request
|> do_something_with_response

You can choose to do whatever you want. See built-in middlewares for some examples.

There is more

At the moment of writing tesla supports three different erlang HTTP adapters: hackney, ibrowse and the built-in httpc. It has some basic middlewares for JSON, logging etc. If the adapter supports it you can pass a Stream as a request body.

I encourage you to play with it, write some middleware and submit a pull request — let’s build the best HTTP toolkit one can imagine!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store