GraphQL over WebSockets

Explore our services and get in touch.
GraphQL over WebSockets
A couple of years ago, while I was still working at Apollo, Lee Byron (opens in a new tab), Rob Zhu (opens in a new tab), Dotan Simha (opens in a new tab) and I (opens in a new tab) worked on the GraphQL Subscriptions spec and the reference implementation.
During that work, we created and merged the reference implementation into graphql-js (opens in a new tab) and created two supporting libraries: graphql-subscriptions (opens in a new tab) and subscriptions-transport-ws (opens in a new tab). Here is a talk with deep dive into all the details (opens in a new tab).
Since leaving Apollo, not a lot of work has been gone into those libraries (opens in a new tab). That's why I was so thrilled that Denis (opens in a new tab) decided to pick up that important work and create a new library for the WebSocket Protocol.
We will support his work of maintaining the library and standardizing it with the GraphQL over HTTP working group. This is a part of a lot of exciting work coming up about GraphQL and real-time, so follow us for new things in this space!
Denis will take it away from here!
Introduction
At some point in time during your web development journeys, you may've been faced with the challenge to add a real-time component. The synonym for realtime in the browser is the WebSocket API. This is how MDN's describes it (opens in a new tab):
The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.
Google Chrome was the first browser to support the WebSocket Protocol, adding support in 2009 - two years before it was officially standardised in 2011. As the need for full-duplex server-client communications rose, other browsers followed suit.
As of today, all major browsers support the WebSocket Protocol - see the Can I Use table (opens in a new tab).
WebSockets ❤️ GraphQL
Okay, so, how do I use WebSockets to add support for the
GraphQL subscription
operation (opens in a new tab)? Doing a
basic Google search, you'd be faced with a single solution, namely
subscriptions-transport-ws
(opens in a new tab). Looking
through the repository, checking recent comments, reading through the issues and open PRs - might
have you notice the abundance of bugs and their security implications.
A summary can be found here (opens in a new tab).
The authors have done a brilliant job, but unfortunately other projects and needs have reduced their disposable time for maintaining and nurturing this well accomplished, yet needy, idea. But, it did indeed inspire me to take up their legacy and revitalised it! Having faced various WebSocket challenges with GraphQL myself - I jumped in writing a new library from scratch.
With no further ado - I humbly introduce graphql-ws
(opens in a new tab). A
coherent, feature-full, zero-dependency, plug-n-play, lazy, simple, server and client implementation
of the
new, security first GraphQL over WebSocket Protocol (opens in a new tab)
with full support for all 3 GraphQL operations: Queries
, Mutations
and Subscriptions
. The
protocol aims to be standardised and become a part of GraphQL with the help of the foundation's
GraphQL over HTTP work group (opens in a new tab).
The library is written in TypeScript and the full implementation totals merely ~1000 lines of well formatted, commented, code. It leverages modern JavaScript primitives and the well established presence of WebSockets.
How Do I Get Started?
I am glad you asked! This is how:
Install
yarn add graphql-ws
Create a GraphQL Schema
import { buildSchema } from 'graphql'
// Construct a schema, using GraphQL schema language
const schema = buildSchema(`
type Query {
hello: String
}
type Subscription {
greetings: String
}
`)
// The roots provide resolvers for each GraphQL operation
const roots = {
query: {
hello: () => 'Hello World!'
},
subscription: {
greetings: async function* sayHiIn5Languages() {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
yield { greetings: hi }
}
}
}
}
Start the Server
With ws
(opens in a new tab)
import ws from 'ws' // yarn add ws
import { useServer } from 'graphql-ws/lib/use/ws'
const server = new ws.Server({
port: 4000,
path: '/graphql'
})
useServer(
// from the previous step
{ schema, roots },
server
)
console.log('Listening to port 4000')
With uWebSockets.js
(opens in a new tab)
import uWS from 'uWebSockets.js' // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<tag>
import { makeBehavior } from 'graphql-ws/lib/use/uWebSockets'
uWS
.App()
.ws(
'/graphql',
makeBehavior(
// from the previous step
{ schema, roots }
)
)
.listen(4000, listenSocket => {
if (listenSocket) {
console.log('Listening to port 4000')
}
})
With fastify-websocket
(opens in a new tab)
import Fastify from 'fastify' // yarn add fastify
import fastifyWebsocket from 'fastify-websocket' // yarn add fastify-websocket
import { makeHandler } from 'graphql-ws/lib/use/fastify-websocket'
const fastify = Fastify()
fastify.register(fastifyWebsocket)
fastify.get(
'/graphql',
{ websocket: true },
makeHandler(
// from the previous step
{ schema, roots }
)
)
fastify.listen(4000, err => {
if (err) {
fastify.log.error(err)
return process.exit(1)
}
console.log('Listening to port 4000')
})
Use the Client
import { createClient } from 'graphql-ws'
const client = createClient({
url: 'ws://welcomer.com:4000/graphql'
})
// query
;(async () => {
const result = await new Promise((resolve, reject) => {
let result
client.subscribe(
{
query: '{ hello }'
},
{
next: data => (result = data),
error: reject,
complete: () => resolve(result)
}
)
})
expect(result).toEqual({ hello: 'Hello World!' })
})()
// subscription
;(async () => {
const onNext = () => {
/* handle incoming values */
}
let unsubscribe = () => {
/* complete the subscription */
}
await new Promise((resolve, reject) => {
unsubscribe = client.subscribe(
{
query: 'subscription { greetings }'
},
{
next: onNext,
error: reject,
complete: resolve
}
)
})
expect(onNext).toBeCalledTimes(5) // we say "Hi" in 5 languages
})()
Want to Find Out More?
Check the repo out to for Getting Started (opens in a new tab) quickly with some Recepies (opens in a new tab) for vanilla usage, or with Relay (opens in a new tab) and Apollo Client (opens in a new tab). Opening issues, contributing with code or simply improving the documentation is always welcome!
I am @enisdenjo (opens in a new tab) and you can chat with me about this topic on the GraphQL Slack workspace (opens in a new tab) anytime.
Thanks for reading and happy coding! 👋
Join our newsletter
Want to hear from us when there's something new? Sign up and stay up to date!
By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.
Recent issues of our newsletterSimilar articles

GraphQXL - The Missing GraphQL Language Extension?
GraphQXL, a new language for building big and scalable GraphQL server-side schemas

Announcing GraphQL Network Inspector
A better way to debug network traffic with GraphQL

Build realtime GraphQL backends with Grafbase
Build collaborative, multiplayer apps faster and easier than ever with GraphQL Live Queries using Grafbase.

Using @defer Directive with GraphQL Code Generator
Learn how to boost GraphQL performance using the @defer directive and GraphQL Code Generator for deferred fragment field resolution.