This article was published on Thursday, April 6, 2023 by Denis Badurina @ The Guild Blog
What Is GraphQL?
Some of you have heard this question many times over... But for the beginners out there: GraphQL is
a data language. And that's it, nothing more and nothing less. It a system of communication that
defines the grammar and the vocabulary. It helps you request the exact data you want, and also helps
the server fulfill that request. Like in the real-world — those that know the language can
communicate. Please keep in mind, GraphQL is not a database, nor is it a server - it is simply -
just a language.
GraphQL Is like a Pizza Order
Imagine ordering a pizza - you call and ask for a pizza with special toppings, and then the person
on the other end takes the order and tells the pizza chef what to make. Well, GraphQL is kinda' like
that phone call, except that instead of ordering pizza, you ask a server for some data; instead of
speaking to a person, you're sending a specially formatted message to a server called a GraphQL
query. Just like you ask for specific toppings, you ask for specific data from a server; and just
like the pizza chef makes your pizza with the exact toppings, the server responds to the GraphQL
query with the exact data you queried.
Immediately we can see that there are two agreements on both sides:
- You know you're ordering pizza (and therefore you know which toppings you can choose from)
- You also know that both you and the waiter speak the same language
Subsequently you know exactly what to expect when picking up your order: a pizza with the toppings
you communicated over the phone.
However, neither of you knows, or cares, about how a telephone works!
Synonymously, GraphQL itself deliberately does not care about the transport - it doesn't care about
how you send the request, how you receive the response, and what is the delivery path — all of this
is out of the GraphQL's scope. It is quite literally just a data language.
GraphQL over HTTP
HTTP is the most common choice when transporting GraphQL because of its ubiquity. With this in mind,
a group of superheroes formed in 2019 to solve exactly this problem. A working group of avid GraphQL
lovers all with the same goal - standardize the process of transporting GraphQL over HTTP. The group
mostly works asynchronously, but there are Zoom meetings, where progress is shared, and important
topics are discussed.
This is a joint effort, so everyone's opinion matters, and everyone is welcome to contribute!
There is already a
specification being standardized named "GraphQL over HTTP".
At the time of writing, it is in the proposal stage. It currently describes exclusively single
response operations, this is the main focus; so, no file uploads, no incremental deliveries, yet...
Reference Implementation
To accompany the spec, the
reference implementation graphql-http
is developed. It
is a zero-dependency server, client and audit suite.
The client is very simple to use and offers some great niceties - one of them being silent retries
of failed requests.
Beware that the library exclusively implements the
current official "GraphQL over HTTP" spec. It does
so in the clearest and most explicit manner so that others implementing their own server can quite
literally use graphql-http
as a reference of the spec.
The library is server agnostic and, as such, can run in any JavaScript environment. Additionally, it
maintains the list of compliant servers and their reports. You're more than welcome to send PRs with
your own implementation!
graphql-http.com
Checking for compliance was never easier! graphql-http.com is a website
for swiftly auditing servers - even those that are running locally! It is always up-to-date with the
current spec.
Of course, security is very important - the website audits run exclusively in the browser, and there
is no tracking, data sharing, or external dependencies! Meaning you can download the website on your
computer and have a portable offline checker.
GraphQL over WebSocket
Before we talk about the current state of GraphQL WebSocket support, let's start with a brief
overview of the history. Initially, there was a
subscriptions-transport-ws
library
that was built and maintained by Apollo. It also had an
accompanying protocol. But as time passed, they haven't been able to spend as
much time on it — probably due to increased demand in other areas. The repo grew in issues, and PRs
were never reviewed. Eventually, it became deprecated. However, this library actually kick-started
and pushed the evolution. It was a big inspiration and a great influence for an upcoming refresh!
The WebSocket Protocol provides a persistent, full-duplex
communication channel between the client and server, allowing data to be transmitted in real-time.
Full-duplex is just a fancy word for bidirectional communication which means that the server and the
client can exchange messages through the connection channel whenever they want. When used in
conjunction with GraphQL, it allows clients to subscribe and receive events from the server in real
time. This makes it ideal for applications that require real-time updates and push notifications.
The GraphQL over WebSocket protocol leverages all pros of the WebSocket and
gives shine to real-time GraphQL. Please note that it is not backwards compatible with
Apollo's protocol behind subscriptions-transport-ws
.
⚠️ Beware that the specification is currently an RFC and is open to changes!
Reference Implementation
A specification goes best with a reference implementation. Say hello to
graphql-ws
. It is a zero-dependency server and client,
very simple and bare-bone. But don't be mistaken, it's very versatile and extendable!
A common struggle when working with WebSockets is re-connecting. graphql-ws
is all about stable
connections. Silent retries with custom strategies are easy to use and extend.
Oh and, it is completely server agnostic - run it on any JavaScript environment!
GraphQL over SSE
Traditionally, a web page has to send a request to the server to receive new data; that is, the
page requests data from the server. With server-sent events, it's possible for a server to send
new data to a web page at any time, by pushing messages to the web page. These incoming messages
can be treated as Events + data inside
the web page.
Why Not Just EventSource
?
Before we proceed, you might ask why not just use the
browser native EventSource? Do we
even need a whole specification? Are we overthinking it?
That's an excellent question, actually! Here are a few gotchas' about the browser native
EventSource
:
- Headers cannot be customized
- If the HTTP response is erroneous and not a 200 OK,
EventSource
raises an ultra vague error - The requests cannot have an accompanying body
- The retry mechanism is too simple, it will simply retry following a fixed interval
- Server might limit the length of the URL
- EventSource will keep reconnecting indefinitely if the server is the one that closes the
- Meaning, if a subscription ends and the server closes the connection as a result - the browser will automatically reconnect
The Specification
Keeping everything from the previous section in mind, the GraphQL over SSE spec
is created. It basically lifts all usual limitations of SSE while keeping the browser native
EventSource
in mind - so you're not locked to a library, you're still free to use it!
It supports two working modes:
- "distinct connections mode" - basically the regular way you'd use SSEs, each request is a connection on its own
- "single connection mode" - a single SSE connection is maintained for streaming, but other accompanying requests dictate the behavior
⚠️ Beware that the specification is currently an RFC and is open to changes!
Distinct Connections Mode
The first mode I want to talk about is "distinct connection mode". It's pretty much what you'd
usually do with SSE - each connection is a subscription on its own. The operation requests conform
the the requests in the GraphQL over HTTP spec, but
with two differences:
-
Content-Type
header must have thetext/event-stream
value - All GraphQL errors must be reported through the SSE connection
- So you first accept the connection and then through it you stream the error in form of a
message. This allows
EventSource
s to have proper error reporting with descriptive errors •
- So you first accept the connection and then through it you stream the error in form of a
message. This allows
- An additional
complete
event message that is used to indicate to the client that the subscription was completed from the sever- This makes
EventSource
s aware when the subscription has ended server-side so that it can be closed and with that avoid unexpected reconnects
- This makes
Single Connection Mode
The other mode of operating is called "single connection mode". A often talked about limitation of
SSE is HTTP/1. HTTP/1 powered servers are limited to
only 6 active connections per domain,
meaning you can only have 6 concurrently active subscriptions (compared to HTTP/2 powered servers
that are by default limited to 100+ active connection per domain).
The "singe connection mode" creates a single connection to the server which is used exclusively for
streaming events and messages. Then you issue separate HTTP requests to execute operations. This
property makes it safe for HTTP/1 environments, but also for subscription heavy apps even in HTTP/2
environments (simply because subscriptions are typically best when really granular and this can
sometimes exceed even the 100+ limit of HTTP/2).
These HTTP operation requests conform to the
GraphQL over HTTP spec with just one difference:
successful operation requests are responded with 202: Accepted
and the results are streamed to the
single established connection.
Reference Implementation
Of course, a reference implementation goes along with the spec.
graphql-sse
is a zero-dependency implementation of
both the server and the client.
One cool thing is the fact that the server automatically detects the operation mode (single
connection vs distinct connection) and behaves accordingly.
The client is pretty advanced too, it offers a few interesting elements like:
- Custom retry strategies
- You can decide exactly when you want to retry connecting (like you might have a health check, and only after the server becomes health do you want to retry)
-
Since
graphql-sse
is packing a custom implementation of SSE and is not using the browser native
EventSource, you're able to:- Set custom headers
- Add a body to the request
- Experience very descriptive errors
Like all of the libraries mentioned in the presentation - graphql-sse
too is server agnostic - you
can run it in any JavaScript environment!
WebSockets vs. SSEs
There's an abundance of articles, tables and benchmarks on the internet comparing WebSockets to
SSEs, so I'll just briefly touch the obvious.
WebSockets | SSEs | |
---|---|---|
Communication | Real-time and bi-directional | Transported over simple HTTP instead |
Firewalls | Has troubles with corporate and outdated firewalls | No trouble with any firewall doing packet inspection |
Latency | Lower because of persisted TCP connection | Can be higher sometimes because of multiplexing |
Retries | Needs to be developed in user-land | Browsers have built-in retry mechanisms |
Support | Browser native through WebSocket . No custom implementations for the browser |
Browser native through EventSource . But, can be custom implemented on fetch()
|
Presentation
Check out my presentation from GraphQL Zurich Meetup
sponsored by SwissLife.
Top comments (0)