DEV Community

Cover image for GraphQL over WebSockets
TheGuildBot for The Guild

Posted on • Updated on • Originally published at the-guild.dev

GraphQL over WebSockets

This article was published on Tuesday, September 15, 2020 by Denis Badurina @ The Guild Blog

GraphQL over WebSockets

A couple of years ago, while I was still working at Apollo,
Lee Byron, Rob Zhu,
Dotan Simha and I worked on the
GraphQL Subscriptions spec and the reference implementation.

During that work, we created and merged the
reference implementation into graphql-js and
created two supporting libraries:
graphql-subscriptions and
subscriptions-transport-ws. Here is
a talk with deep dive into all the details.

Since leaving Apollo,
not a lot of work has been gone into those libraries.
That's why I was so thrilled that Denis 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:

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.

WebSockets ❤️ GraphQL

Okay, so, how do I use WebSockets to add support for the
GraphQL subscription operation? Doing a
basic Google search, you'd be faced with a single solution, namely
subscriptions-transport-ws. 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.

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. A
coherent, feature-full, zero-dependency, plug-n-play, lazy, simple, server and client implementation
of the
new, security first GraphQL over WebSocket Protocol
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.

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
Enter fullscreen mode Exit fullscreen mode

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 }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Start the Server

With ws
import { useServer } from 'graphql-ws/lib/use/ws'
// yarn add ws
import ws from 'ws'

const server = new ws.Server({
  port: 4000,
  path: '/graphql'
})

useServer(
  // from the previous step
  { schema, roots },
  server
)

console.log('Listening to port 4000')
Enter fullscreen mode Exit fullscreen mode
With uWebSockets.js
import { makeBehavior } from 'graphql-ws/lib/use/uWebSockets'
// yarn add uWebSockets.js@uNetworking/uWebSockets.js#<tag>
import uWS from 'uWebSockets.js'

uWS
  .App()
  .ws(
    '/graphql',
    makeBehavior(
      // from the previous step
      { schema, roots }
    )
  )
  .listen(4000, listenSocket => {
    if (listenSocket) {
      console.log('Listening to port 4000')
    }
  })
Enter fullscreen mode Exit fullscreen mode
With fastify-websocket
// yarn add fastify
import Fastify from 'fastify'
// yarn add fastify-websocket
import fastifyWebsocket from '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')
})
Enter fullscreen mode Exit fullscreen mode

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
})()
Enter fullscreen mode Exit fullscreen mode

Want to Find Out More?

Check the repo out to for Getting Started
quickly with some Recepies for vanilla usage, or
with Relay and Apollo Client. Opening
issues, contributing with code or simply improving the documentation is always welcome!

I am @enisdenjo and you can chat with me about this topic on the
GraphQL Slack workspace anytime.

Thanks for reading and happy coding! 👋

Oldest comments (0)