Recently I’ve been writing an wrapper for the TD Ameritrade API. I’ve been learning Elixir as an alternative to Python recently, and I thought that this would be a good use case. I was right.

I’ve written a few API wrappers, mostly in Python, Golang, or JavaScript. Every time it was pretty painful. Generally, authentication is the most annoying part, followed closely by JSON parsing/processing, especially in static languages. Elixir handles both of these super ergonomically.

Keeping authentication state is super simple using a GenServer. Just stick the data in a map, set an interval to renew everything, and add some calls and casts. Another benefit of this is that testing specific endpoints is easy using iex -S mix, since it gets the auth token on startup.

The code looks roughly like this:

defmodule TDAPI.TDStorage do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @impl true
  def init(:ok) do
    # get client_id, refresh_token, and auth_token

    store = %{
      client_id: client_id,
      refresh_token: refresh_token,
      auth_token: auth_token
    }

    :timer.send_interval(25 * 60 * 1000, :renew_auth_token)

    {:ok, store}
  end

  @impl true
  def handle_info(:renew_auth_token, store) do
    new_token = get_new_auth_token(store.client_id, store.refresh_token)
    {:noreply, %{store | auth_token: new_token}}
  end

  @impl true
  def handle_call(:get_auth_token, _from, store), do: {:reply, store.auth_token, store}

  defp get_new_auth_token(client_id, refresh_token) do
    url = "..."

    form = [
      ...
      {"refresh_token", refresh_token},
      {"client_id", client_id}
    ]

    headers = [{"Content-Type", "application/x-www-form-urlencoded"}]

    %{body: body} = HTTPoison.post!(url, {:form, form}, headers)
    Jason.decode!(body)["access_token"]
  end

  def get_auth_token(), do: GenServer.call(__MODULE__, :get_auth_token)
end

JSON processing is made super ergonomic in Elixir through pattern matching and guards. Being able to write a new function clause for each possible response type is great for cleanly dividing logic. Coupled with Elixir’s optional and default argument handling through keyword lists, it’s possible to write super flexible function clauses easily.

All in all, It’s been a great experience using Elixir for this script. It’s not all positives, but my biggest issues are just to do with syntax, and I don’t think that that’s worth writing about.