GraphQL Subscriptions

Courselore’s API is REST. But suppose it were a GraphQL API, like the shop API. How would the server get a new message to Marcus’s client?

Recall that in GraphQL, a client sends a query to read data, such as cart(id: 73), or a mutation to change it, such as placeOrder. Both are ordinary HTTP requests: the client sends POST /graphql, and the server sends one response. So GraphQL has the same problem we had with REST. The server cannot send Marcus’s client anything unless Marcus’s client has sent a request.

We could solve it the same way we did with REST. Marcus’s client could send a query for new messages every few seconds, which is polling. Or the server could keep the query until a message is posted, which is long polling. Both work with GraphQL as they did with REST.

GraphQL also has a third operation type, made for this case. A subscription asks the server to send data every time something happens, such as a new message in a room.

The schema and the request

Subscriptions are declared in the schema, in a type named Subscription. For chat, one field is enough:

type Subscription {
  messagePosted(roomId: ID!): Message!
}

Message is a type with the fields of our message representation, and its author field holds a type with an id and a name.

Marcus’s client sends this subscription for Team 4’s room, room 12:

subscription {
  messagePosted(roomId: 12) {
    id
    body
    author { name }
  }
}

The text begins with the word subscription, as a mutation’s text begins with mutation. After the argument, the client selects fields, as in a query.

The server does not send any data right away. Each time a message is posted in the room, it sends Marcus’s client one result with the fields the client selected. When Priya posts the link to the design doc, the result is:

{
  "data": {
    "messagePosted": {
      "id": "61843",
      "body": "Design doc: https://docs.example/team-4-design",
      "author": {
        "name": "Priya Shah"
      }
    }
  }
}

The result looks like a query’s response. Under data, there is one key, the field’s name, and its value has the shape of the selection.

Subscriptions in history

In October 2015, the month after it released GraphQL publicly, Facebook described subscriptions in a blog post. Facebook already used them to show new likes and comments as they happened. Subscriptions entered the GraphQL specification in its June 2018 edition. The example in the specification is a chat: a subscription to newMessage(roomId: 123) that selects the sender and the text.

How the results reach the client

A subscription needs a result every time a message is posted, for as long as Marcus has Courselore open. One POST /graphql and its one response cannot do that.

The GraphQL specification defines the subscription and the results the server sends. It leaves open how the results travel from the server to the client. In practice, they travel over a WebSocket, and a common library for this is graphql-ws. The client opens a WebSocket, as Marcus’s client opened one at /courses/7/live, and sends the subscription on it as a message. The server sends each result on the same WebSocket.

What the server needs

When Priya posts, the server saves the message as before. Then it has to find every subscription to the room and send the result on its connection. That needs what we built for WebSockets:

  • A set for each room, of the connections that sent a subscription for that room.
  • Code that sends each new message on the connections in the room’s set, as sendToRoom does.
  • The authorization checks. The server checks that Marcus may read the room when the subscription arrives, and removes Marcus’s subscriptions to the room when an instructor removes Marcus from it.

What changes is the format of the messages on the WebSocket: they are GraphQL results in place of our message.created messages.