GraphQL Subscriptions and Real-Time Data Updates in Express.js

Real-time applications are increasingly popular, providing instant data updates and creating interactive experiences for users. Express.js can support real-time updates through GraphQL subscriptions, allowing clients to receive live data changes from the server automatically.

Introduction to GraphQL Subscriptions

GraphQL subscriptions allow clients to listen to specific events and receive real-time updates from the server. Unlike queries and mutations, which follow a request-response pattern, subscriptions use WebSockets for a continuous two-way communication channel.

Key Concepts:

  • Subscription: A GraphQL operation that sets up a real-time connection, allowing the server to push data to clients when specified events occur.
  • WebSocket: A protocol that enables two-way communication, essential for real-time updates.
  • Publisher: The component that emits events (e.g., database changes) that trigger subscriptions.

Understanding Real-Time Data with WebSockets

WebSockets are a protocol enabling interactive communication sessions between a client and a server. Unlike HTTP, which requires constant requests to maintain an open channel, WebSockets allow both parties to send messages at any time, making them ideal for real-time applications.

  • Full-Duplex Communication: Both client and server can send messages independently.
  • Low Latency: No need to open and close connections for each update.

Express.js, combined with libraries like graphql-ws or subscriptions-transport-ws, enables WebSocket support, bridging the gap between traditional request-response cycles and real-time data flow.

Setting Up GraphQL Subscriptions with Express.js

To use GraphQL subscriptions in Express, you’ll set up a WebSocket server alongside your HTTP server, configure the schema to include subscriptions, and use a pub-sub mechanism to emit events.

Step 1: Install Required Packages

Install the following packages:

  • apollo-server-express for handling GraphQL requests.
  • graphql-subscriptions for handling pub-sub events.
  • graphql-ws for WebSocket support in GraphQL subscriptions.
				
					npm install apollo-server-express graphql graphql-subscriptions graphql-ws

				
			

Step 2: Set Up Express with Apollo Server and WebSockets

Here’s an example of setting up a basic server with WebSocket support:

				
					const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { useServer } = require('graphql-ws/lib/use/ws');
const { WebSocketServer } = require('ws');
const { PubSub } = require('graphql-subscriptions');

// Create a pub-sub instance
const pubsub = new PubSub();
const MESSAGE_ADDED = 'MESSAGE_ADDED';

const typeDefs = `
  type Message {
    id: ID!
    content: String!
  }

  type Query {
    messages: [Message!]
  }

  type Subscription {
    messageAdded: Message!
  }

  type Mutation {
    addMessage(content: String!): Message!
  }
`;

const messages = []; // In-memory storage for simplicity

const resolvers = {
  Query: {
    messages: () => messages,
  },
  Mutation: {
    addMessage: (_, { content }) => {
      const message = { id: messages.length + 1, content };
      messages.push(message);
      pubsub.publish(MESSAGE_ADDED, { messageAdded: message });
      return message;
    },
  },
  Subscription: {
    messageAdded: {
      subscribe: () => pubsub.asyncIterator(MESSAGE_ADDED),
    },
  },
};

// Create schema
const schema = makeExecutableSchema({ typeDefs, resolvers });

async function startServer() {
  const app = express();

  const httpServer = app.listen(4000, () => {
    console.log(`Server is running at http://localhost:4000/graphql`);
  });

  // Set up WebSocket server
  const wsServer = new WebSocketServer({
    server: httpServer,
    path: '/graphql',
  });

  useServer({ schema }, wsServer);

  const server = new ApolloServer({ schema });
  await server.start();
  server.applyMiddleware({ app });
}

startServer();

				
			

In this setup:

  • The Subscription type defines a messageAdded subscription.
  • The pubsub object, an instance of PubSub, is used to publish messages.
  • useServer is used to configure the WebSocket server with graphql-ws.

Building Real-Time Features with Apollo Server and Subscriptions

Example: Adding Messages in Real-Time

In the example above, we define a subscription called messageAdded that notifies clients of new messages.

  1. Add Message Mutation: The mutation addMessage takes content as input, creates a new message, and publishes it to all subscribers.
  2. Message Subscription: The subscription messageAdded listens for MESSAGE_ADDED events. Each time a new message is added, clients receive an update.

Client-Side Subscription

To receive real-time updates on the client, you’ll use a GraphQL client like Apollo Client or any WebSocket GraphQL client to subscribe.

Here’s how a client might subscribe to messageAdded

				
					subscription {
  messageAdded {
    id
    content
  }
}

				
			

Each time addMessage is called, the client subscribed to messageAdded will receive an instant update with the new message.

Handling Events and Broadcasting Data

The PubSub system in GraphQL Subscriptions allows events to be triggered and data to be broadcasted. When a user adds a message, the event MESSAGE_ADDED is triggered, causing any clients subscribed to messageAdded to be notified.

Example Workflow:

  1. User Adds Message: A user calls addMessage.
  2. Event Published: pubsub.publish(MESSAGE_ADDED, { messageAdded: message }) is called.
  3. Subscription Triggered: The messageAdded subscription pushes the new message to all subscribed clients.

This flow creates a seamless, real-time experience where users see messages appear instantly.

Advanced Subscription Techniques

  • Filtering Subscriptions: You can filter subscriptions to provide more control over the data each client receives.
  • Authorization: Ensure users are authorized to access certain subscriptions.
  • Optimizing Performance: Using Redis or another distributed pub-sub for scalable event handling in production environments.

Example: Filtering Messages by User

To limit the data each client receives, you can add arguments to the subscription:

				
					subscription($userId: ID!) {
  messageAdded(userId: $userId) {
    id
    content
  }
}

				
			

On the server side, handle the filtering logic:

				
					const resolvers = {
  Subscription: {
    messageAdded: {
      subscribe: (_, { userId }) =>
        pubsub.asyncIterator(MESSAGE_ADDED).filter(
          (payload) => payload.messageAdded.userId === userId
        ),
    },
  },
};

				
			

Best Practices and Security for Subscriptions

When working with subscriptions, it’s important to keep security and performance in mind:

  • Secure WebSocket Connections: Use wss (WebSocket Secure) for encrypted connections.
  • Rate Limiting: Prevent misuse by limiting the frequency of subscription updates.
  • Data Privacy: Ensure users can only subscribe to data they’re authorized to view.

GraphQL subscriptions allow you to implement real-time features in Express.js applications, enhancing user interactivity and data immediacy. By combining Express.js with GraphQL and WebSockets, you can set up efficient, scalable systems that broadcast updates instantly to subscribed clients. Happy Coding!❤️

Table of Contents