What are GraphQL subscriptions

GraphQL subscriptions enable publishing updates to data in real time to subscribed clients. They are invoked in response to a mutation or change in data. In other words, when data is modified via a GraphQL mutation operation, subscribed clients are automatically notified accordingly. In short, a mutation publishes data that is sent to clients subscribed to it following a publish-subscribe (pub/sub) pattern.

Why build real-time, pub/sub APIs with GraphQL

GraphQL subscriptions are simple statements in a GraphQL schema that tell your GraphQL server which portions of your data should provide real-time updates when mutations occur. This capability to build real-time APIs makes it easy to deliver interactive, real-time app experiences like chat, geo-tracking for food delivery status, virtual ed-tech solutions, telehealth solutions, social gaming, score and price updates, and many more.

What subscription implementation paths are available

On AWS, there are two paths to implement GraphQL subscriptions: 

  1. Self-hosted options like Apollo Server, Express GraphQL, GraphQL Java, and Hot Chocolate .NET, which often require additional WebSockets infrastructure to manage client connections, message delivery, and fanout
  2. AWS AppSync, a fully managed GraphQL service.

How to implement subscriptions with a managed GraphQL service

Unlike self-hosted open-source GraphQL options such as Apollo Server, managed GraphQL solutions provide a way to shift many operational concerns and reorient efforts toward organizational use cases. AWS AppSync, a managed serverless GraphQL API service, handles the creation and maintenance of GraphQL connections, message broadcast, and fanout to millions of subscribed clients, automatically scaling according to demand. It makes it easy to create real-time APIs by automatically publishing data updates to subscribed clients via GraphQL subscriptions.

Adding subscriptions to a GraphQL API powered by AWS AppSync requires two steps. First, add a subscription type to the schema and identify which mutation updates should be made available as a subscription. In this example, the mutations “addPost,” “updatedPost,” and “deletedPost” are made available as subscriptions.
 

schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}

// Other GraphQL types and related elements.

type Subscription {
    addedPost: Post
    @aws_subscribe(mutations: ["addPost"])
    updatedPost: Post
    @aws_subscribe(mutations: ["updatePost"])
    deletedPost: Post
    @aws_subscribe(mutations: ["deletePost"])
}

Once the GraphQL subscriptions are added to the schema, the next step is to configure an application to subscribe and receive real-time updates as change sets. The following is an example of client code required to subscribe to the addedPost mutation:

subscription NewPostSub {
    addedPost {
        __typename
        version
        title
        content
        author
        url
    }
}

As long as the client remains subscribed, it will continue to receive data updates from the underlying mutations as they occur. AWS AppSync handles the creation and scaling of the underlying WebSockets infrastructure and connections, message broadcast, and fanout to up to millions of connected clients.

Advanced configurations options with AWS AppSync

Authorization – To control authorization at connection time to a GraphQL subscription, use AWS Identity and Access Management (IAM), AWS Lambda, Amazon Cognito identity pools, Amazon Cognito user pools, or API keys for field-level authorization. For fine-grained access control on subscriptions, attach resolvers to the subscription fields and define business logic using the identity of the caller and AWS AppSync data sources. For more information, see docs on Authorization and Authentication.

Filtering – Many real-time use cases require restricting or filtering the data specific groups of subscribed clients receive. Basic filtering can be achieved in GraphQL using arguments defined on the subscription query itself when invoking a subscription operation from an API client. For example, with arguments, clients can subscribe and listen for data related to just a particular identifier (i.e., orderID, userID, groupId, roomId, deviceId, eventId, gameId, etc.) or a combination of fields with AND logic (title X AND location Y). In addition to these client-side filtering capabilities, AWS AppSync supports the configuration of more complex filtering or authorization logic with additional logical operators, controlled and enforced centrally by filters defined in the GraphQL API backend itself. Other than providing enhanced control over what data is sent to subscribed clients, backend-defined filters simplify application code and reduce the amount of data sent to clients. Visit the AWS blog for more details of enhanced GraphQL subscription filtering.

Invalidation – Some use cases require forcibly unsubscribing connected clients when certain events occur, for instance, when a user is removed from a group chat by an owner or admin or when a user is unfollowed, and their events should be removed from a social feed. AWS AppSync can automatically unsubscribe clients from the service side based on an event triggered by a GraphQL mutation and a specific invalidation filter, effectively disconnecting a subscription on the client side and providing more control over WebSockets connections from the GraphQL API backend.

To learn more about GraphQL subscriptions and AWS AppSync, read the article Simple serverless WebSocket real-time API, AWS AppSync documentation on real-time APIs, and documentation on the AWS AppSync real-time protocol.

Tips for creating GraphQL subscriptions with AWS AppSync

  • It is possible to leverage AWS AppSync and GraphQL subscriptions to create simple pub/sub-only APIs. Follow these steps to create generic pub/sub APIs powered by serverless WebSockets in less than five minutes.
  • Any interactions with the subscription must take into account the ephemeral nature of the subscription change sets themselves. They are sent once into the response stream, and if not received, for any reason, they are gone. To mitigate this, appropriate data structures need to be added for tracking, like time stamps, sent, and receipt responses, and need to be included with the responses in the data stream.
  • To mitigate offline disconnects, clients can use GraphQL queries after reconnecting and coming back online to retrieve data that was pushed while they were offline based on specific time stamps or receipts. Alternatively, use Amplify DataStore to manage offline sync. For more details on offline sync, refer to this post on a reference architecture and this post on adding offline and sync features to SQL-based applications.
  • Plan what data should be published in a mutation and what subset of the published data subscribers should receive. A subscription message contains only the fields requested to be returned by the mutation that originated a specific publishing call; fields that are not in the mutation-selection set will be returned as null.
  • For quick starts, prototyping, and editing on the fly, use the AWS AppSync console to generate subscription-enabled GraphQL APIs. This is a great way to also learn about the reference implementations of chat apps, real-time updating, and other examples.

How to implement subscriptions with self-hosted GraphQL servers

In some situations, the need to code against, subscribe to, or interact with subscriptions at the WebSockets level, or provide some other type of real-time streaming connectivity to a service, may require more fine-grained controls than what a fully managed solution like AWS AppSync provides. If this level of control over GraphQL subscriptions is required, it can be accomplished with a mix of AWS services and self-hosted open-source solutions. Deploying solutions built with Express GraphQL or GraphQL Java are examples of solutions that provide more options to directly program the real-time stream or server.

The trade-off here is that configuring these open-source solutions to support GraphQL subscriptions and manage connections, message delivery, and fanout at scale often requires additional configuration steps and infrastructure. As an example, configuring an Apollo Server to support GraphQL subscriptions requires:

  • Swapping out the regular Apollo Server library with the subscription-enabled server library called apollo-server-express.
  • Including the newer graphql-ws library as well as the older subscription-transport-ws library since they recursively reference each other for functionality, and ensuring that circular reference problems between these two libraries do not arise from the way the server is implemented from these imports.
  • Additional infrastructure, like Amazon API Gateway, Application Load Balancer, Amazon SNS, and Amazon SQS, may be required to support load balancing, message delivery, and fanout.
  • Subscriptions aren’t available if federation is used.

To read up more on what it takes to get an Apollo Server up and running with subscriptions and build real-time APIs, refer to the Apollo Server documentation. It includes much of the aforementioned details about limitations, library usage, and related information, as well as material to help get started.

Tips for creating GraphQL subscriptions with self-hosted servers

  • When building a self-hosted solution, be sure to map the capabilities of the underlying services intended for hosting to that of the libraries. If an underlying service doesn't allow a connection to a data source or transport option, it may not work with a particular library. For example, WebSockets need particular transport requirements to operate.
  • Take a performance baseline for subscriptions and their matching mutation calls. If a mutation is performing fine under standard request loads, having a subscription providing change sets can dramatically increase demand and load.
  • Test under load for forced unsubscribes, connectivity loss, and network-related concerns when building GraphQL subscriptions. Many situations can arise that require additional handling of disconnections or unsubscribes, missed messages, and related issues. For example, in a chat app, a way to verify receipt of messages is important to assure that sent messages are received and can be read.

Looking for a fully managed GraphQL service?

Explore AWS AppSync

Explore AWS AppSync

AWS AppSync is an enterprise level, fully managed serverless GraphQL service with real-time data synchronization and offline programming features. AppSync makes it easy to build data driven mobile and web applications by securely handling all the application data management tasks such as real-time and offline data access, data synchronization, and data manipulation across multiple data sources.