mcrumm

mcrumm

Phoenix Core Team

AbsintheClient – GraphQL client with Subscriptions

Announcing AbsintheClient - A GraphQL client designed for Absinthe.

What is AbsintheClient?

AbsintheClient is a Req plugin to perform GraphQL operations, including subscriptions.

  • Performs query and mutation operations via JSON POST requests.
  • Performs subscription operations over WebSockets (Absinthe Phoenix).
  • Automatically re-establishes subscriptions on socket disconnect/reconnect.
  • Supports virtually all Req.request/1 options, notably:
    • Bearer authentication (via the auth step).
    • Retries on errors (via the retry step).

Here is an example straight from the docs using rickandmortyapi.com to fetch all the Cronenbergs:

req = Req.new(base_url: "https://rickandmortyapi.com") |> AbsintheClient.attach()

Req.post!(req,
  graphql: {
    """
    query ($name: String!) {
      characters(filter: {name: $name}) {
        results {
          name
        }
      }
    } 
    """,
    %{name: "Cronenberg"}
  }
).body["data"]
%{
  "characters" => %{
    "results" => [
      %{"name" => "Cronenberg Rick"},
      %{"name" => "Cronenberg Morty"}
    ]
  }
}

Subscriptions

AbsintheClient includes a custom adapter for performing operations over WebSockets, which is mostly useful for managing subscriptions.

Speaking of subscriptions, one killer feature in AbsintheClient is the ability to automatically re-establish subscriptions on disconnect/reconnect. When your WebSocket disconnects, whether due to network failure, rolling deployment, or an elusive third thing, when the socket reconnects AbsintheClient will automatically re-subscribe to any active subscriptions.

Let’s review a subscriptions example. In this scenario an Absinthe server has defined a subscription for leaving comments on repositories. We can create a subscription to listen for new comments:

client = Req.new(base_url: "http://localhost:4000") |> AbsintheClient.attach()
websocket = AbsintheClient.WebSocket.connect!(client, url: "/subscriptions/websocket")

gql =
  """
  subscription RepoCommentSubscription($repository: Repository!){
    repoCommentSubscribe(repository: $repository){
      id
      commentary
    }
  }
  """

variables = %{"repository" => "absinthe-graphql/absinthe"}

Req.request!(client, web_socket: web_socket, graphql: {gql, variables}).body
#=> %AbsintheClient.Subscription{}

When a change occurs, the server will publish a message with the subscription data:

IEx.Helpers.flush()
#=> %AbsintheClient.WebSocket.Message{event: "subscription:data", payload: %{"data" => %{"repoCommentSubscribe" => %{"id" => 1, "commentary" => "Absinthe is great!"}}}

The Future

The initial release of AbsintheClient was just a necessary first step. We are embarking on an exciting new journey around GraphQL state management in Elixir, specifically in Phoenix LiveView. Over the next few weeks and months, expect to see more releases with tools for managing Absinthe socket state in the LiveView process, operation bindings with loading states, pagination, and much, much more!

For the GraphQL (not Absinthe) users– we are not leaving you out in the cold! Query and mutation operations work today for all GraphQL servers that support JSON POST requests. Support for other formats is planned. Subscriptions support is underway, too– we have made solid initial progress on a graphql-transport-ws adapter, too. It won’t provide all of the same guarantees as the Phoenix Channels implementation, but it will definitely support automatic re-subscription so stay tuned for those updates.

Getting involved

First and foremost, if you’re an Absinthe user we would love to get your feedback– do you see a use case for AbsintheClient? What kind of tools would you like to see in a client library? What should we focus on next?

Finally if you are remotely interested in improving the state of GraphQL client operations in Elixir, we would love to have your help! Several members of the CargoSense team have tackled this problem from different angles at different times and we would like to begin coordinating this effort around the AbsintheClient library.

Thanks for reading and happy gardening!

Most Liked

arathunku

arathunku

Hello Michael! Nice to see you :slight_smile:

Congratulations on the release! This is a much needed piece in the Elixir ecosystem, particularly now with how good LiveView is! It’s great to finally see a proper client for GraphQL APIs in Elixir system. I see a future where SPAs are replaced with LiveView+GraphQL based front-ends :smiley:

I still remember @benwilson512’s post from 2019 Anyone using a Phoenix LiveView Client to access a Phoenix Absinthe API? - #3 by benwilson512 where he had mentioned Absinthe.Client, although I presume this library only takes an inspiration from what you have at work given that Req 0.3 wasn’t released that long ago… :slight_smile:

This topic is also close to my heart, I cannot wait to dig deeper into it
and I’m happy to help :slight_smile:

At work, we’ve got an Elixir service that has to make few calls to GraphQL API
and I’ve dearly missed the pros of GraphQL! Our SPAs use Apollo and its tooling to
maximum extent(TS types, compilation, caching, etc.) but there wasn’t anything out there for Elixir apps. Given our Elixir service limited scope, we’ve just used Req with json:. And there’s another reason why I’m delighted something is moving in this area.

Last year I’ve started making some steps into a proper GraphQL Client. I wanted something easy to use in LiveBook to create some demos and to try replacing React+Apollo SPA with LiveView, keeping GraphQL API from Rails app unchanged.

I really wanted support for compile time validation of queries, subscriptions and caching (similar to Apollo), and on the last piece I struggled the most and then got life in a way before I had something to release.

My approach was to basically reuse as much as possible from Absinthe. Based on SDLC (or Introspection query result) I’ve created Absinthe schema, hijacked the pipeline for running queries against the schema and added additional steps to
make the query against remote API, all resolvers were no-op. :smile:

All queries were in ~g sigil and queries inside it would be validated against the schema.

https://twitter.com/arathunku/status/1408841659882876930
https://twitter.com/arathunku/status/1423315395310669827

When I got to subscriptions, I’ve followed graphql-ws too!
With some glue on top to make it work both with Apollo based GraphQL APIs and
Absinthe Elixir API in case the remote service is in Elixir too via slipstream.

I’ve to say, the approach with hijacking Absinthe’s pipeline worked nicely and saved me a lot of time,
but when I got to caching… this when things very interesting.
It was had for me to get nice DX with websocket based queries, subscriptions and LiveView.
That was many months ago, since then life got in a way and I put the project on hold :see_no_evil:

Congratulations again and I’ll probably come back in issue tracker :slight_smile:

Where Next?

Popular in Announcing Top

josevalim
Hi everyone, We would like to announce that Plataformatec is working on a new MySQL driver called MyXQL. Our goal is to eventually integ...
New
josevalim
Yes, yet another parser combinator library! Most of the parser combinators in the ecosystem are either compile-time, often using AST tra...
159 19228 141
New
mspanc
I am pleased to announce an initial release of the Membrane Framework - an Elixir-based framework with special focus on processing multim...
New
brainlid
LangChain is short for Language Chain. An LLM, or Large Language Model, is the “Language” part. This library makes it easier for Elixir a...
New
Crowdhailer
Experimenting with this code. OK.try do user <- fetch_user(1) cart <- fetch_cart(1) order = checkout(cart, user) save_orde...
New
ahamez
Hi everyone, I’ve been working on this protobuf library for 3 years. We use it in the company I work for, EasyMile, to communicate with ...
New
versilov
Could not wait for the missing Elixir ML libraries to appear, so, I wrote one myself, taking https://github.com/sdwolfz/exlearn as a foun...
New
Hal9000
Here is my first stab at this. README pasted below. https://github.com/Hal9000/elixir_random Comments and critiques are welcome. Thank...
New
michalmuskala
Hello everybody. I have just released Jason - a new JSON library. You might be wondering, why do we need a new library? The primary foc...
New
anshuman23
Hello all, I have been working on my proposed project called Tensorflex as part of Google Summer of Code 2018.. Tensorflex can be used f...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New

We're in Beta

About us Mission Statement