How do you deal with a lack of third party SDK's for Elixir?

Ohhhhh, I LOVE this question because I got a really hot take on it…

I don’t use "SDK"s anymore… especially if they wrap a simple JSON over HTTP API. Not in Elixir, nor any other language.

JSON over HTTP is simple

defmodule Github.Api do
  use Kestrel, finch: Finch, url: "https://api.github.com"

  @impl true
  def process_request_headers(headers) do
    token = System.get_env("GITHUB_TOKEN")
    [{"Authorization", "Bearer #{token}"} | headers]
  end
end

iex> Github.Api.get("/repository/MyThing")

There. That’s it. It never goes out of date; it doesn’t matter if GitHub expands or changes their API. It will always work; I never have to wait for a library update or anything.

In a statically typed language, there is some benefit to using a 3rd party SDK so you get compile time guarantees… and arguably there is a benefit to doing the same with Elixir so typespecs + Dialyzer give you some feedback.

But for simple HTTP+JSON in a dynamically typed language… this has served me very well.

5 Likes

And that’s even more true for GraphQL APIs, since SDKs can often be out of date or incomplete (or in Github’s case many SDKs use the old JSON API which doesn’t have as much capability r flexibility as the GraphQL one). Here’s a function to illustrate (taken from a script we use to generate a changelog from all recently closed issues):


  def fetch_issues(opts \\ []) do
    org = Keyword.get(opts, :org, "my-org")
    closed_after = Keyword.get(opts, :closed_after) || Date.add(Date.utc_today, -Keyword.get(opts, :closed_in_last_days, 30))
    
    with token when is_binary(token) and token !="" <- Keyword.get(opts, :github_token) || System.get_env("GITHUB_TOKEN"),
    {:ok, %{body: body}} <- Neuron.query("""
    query {
      search(first: 100, type: ISSUE, query: "org:#{org} state:closed closed:>#{closed_after}") {
        issueCount
        pageInfo {
          hasNextPage
          endCursor
        }
        edges {
          node {
            ... on Issue {
              number
              # createdAt
              # closedAt
              title
              url
              # bodyText
              # repository {
              #   name
              # }
              labels(first: 100) {
                edges {
                  node {
                    name
                    color
                  }
                }
              }
              assignees(first: 5) {
                edges {
                  node {
                    login
                  }
                }
              }
            }
          }
        }
      }
    }
    """,
    nil,
    url: "https://api.github.com/graphql",
    headers: [authorization: "Bearer #{token}"]) do
      body["data"]["search"]["edges"]
      |> Enum.map(&Map.get(&1, "node", &1))
    else e ->
      Logger.error(e)
      []
    end
  end

If you use Stripe Checkout then your app doesn’t need to handle sensitive payment info. It redirects the user to stripe for checkout and then handles the results of checkout from Stripe webhooks.

I just published a tutorial with the basics of setting it up.