Implementing GraphQL Subscriptions with Express.js

GraphQL Subscriptions enable real-time communication between a client and a server using WebSockets. They allow a client to subscribe to specific events and get notified in real time when the server emits updates. This chapter will cover the implementation of GraphQL Subscriptions with Express.js from basics to advanced concepts.

Introduction to GraphQL Subscriptions

GraphQL Subscriptions extend the capabilities of GraphQL APIs by supporting real-time updates. They are often used in scenarios like:

  • Live chats
  • Notifications
  • Real-time analytics
  • Collaborative tools

How GraphQL Subscriptions Work

  1. The client sends a subscription query to the server.
  2. The server establishes a WebSocket connection with the client.
  3. When a specified event occurs on the server (e.g., new data is added), the server sends an update to all connected clients that have subscribed to that event.

Key Components:

  • Subscription Type: Defines the events clients can subscribe to.
  • Resolvers: Functions that handle event publishing.
  • PubSub: A mechanism to publish and subscribe to events.

Setting Up the Project

We’ll build a real-time chat application to demonstrate subscriptions.

Install Dependencies

Create a new project and install the required dependencies:

				
					mkdir graphql-subscriptions-example
cd graphql-subscriptions-example
npm init -y
npm install express express-graphql graphql graphql-subscriptions ws

				
			

Project Structure

				
					graphql-subscriptions-example/
├── index.js
├── schema.js
└── package.json

				
			

Creating the Schema

Define the GraphQL schema with a Subscription type for real-time updates and a Mutation type for adding data.

schema.js

				
					const { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLList } = require("graphql");
const { PubSub } = require("graphql-subscriptions");

const pubsub = new PubSub(); // Initialize PubSub

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

// Define Message Type
const MessageType = new GraphQLObjectType({
  name: "Message",
  fields: {
    content: { type: GraphQLString },
    sender: { type: GraphQLString },
  },
});

// Define Query Type
const QueryType = new GraphQLObjectType({
  name: "Query",
  fields: {
    messages: {
      type: new GraphQLList(MessageType),
      resolve: () => messages,
    },
  },
});

// Define Mutation Type
const MutationType = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    sendMessage: {
      type: MessageType,
      args: {
        content: { type: GraphQLString },
        sender: { type: GraphQLString },
      },
      resolve: (_, { content, sender }) => {
        const newMessage = { content, sender };
        messages.push(newMessage);

        // Publish the event to subscribers
        pubsub.publish("NEW_MESSAGE", { messageReceived: newMessage });

        return newMessage;
      },
    },
  },
});

// Define Subscription Type
const SubscriptionType = new GraphQLObjectType({
  name: "Subscription",
  fields: {
    messageReceived: {
      type: MessageType,
      subscribe: () => pubsub.asyncIterator("NEW_MESSAGE"), // Subscribe to the "NEW_MESSAGE" topic
    },
  },
});

// Define Schema
const schema = new GraphQLSchema({
  query: QueryType,
  mutation: MutationType,
  subscription: SubscriptionType,
});

module.exports = schema;

				
			

Setting Up the Server

Configure the Express server to handle GraphQL requests and enable WebSocket support for subscriptions.

index.js

				
					const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { execute, subscribe } = require("graphql");
const { createServer } = require("http");
const { WebSocketServer } = require("ws");
const { useServer } = require("graphql-ws/lib/use/ws");
const schema = require("./schema");

const app = express();

// GraphQL Endpoint
app.use(
  "/graphql",
  graphqlHTTP({
    schema,
    graphiql: {
      subscriptionEndpoint: "ws://localhost:4000/subscriptions", // WebSocket endpoint
    },
  })
);

// Create an HTTP server
const server = createServer(app);

// Create WebSocket server for GraphQL Subscriptions
const wsServer = new WebSocketServer({
  server,
  path: "/subscriptions",
});

// Use GraphQL Subscriptions with WebSocket
useServer({ schema, execute, subscribe }, wsServer);

// Start the server
server.listen(4000, () => {
  console.log("Server is running on http://localhost:4000/graphql");
  console.log("Subscriptions are running on ws://localhost:4000/subscriptions");
});

				
			

Testing the Application

You can test the application using GraphiQL (GraphQL IDE) or a GraphQL client like Apollo.

Queries

Fetch all messages:

				
					query {
  messages {
    content
    sender
  }
}

				
			

Mutations

Send a new message:

				
					mutation {
  sendMessage(content: "Hello, world!", sender: "Alice") {
    content
    sender
  }
}

				
			

Subscriptions

Listen for new messages:

				
					subscription {
  messageReceived {
    content
    sender
  }
}

				
			

Whenever you execute the sendMessage mutation, clients subscribed to messageReceived will receive real-time updates.

Advanced Topics

Authentication

To secure subscriptions, add middleware to validate WebSocket connections.

Example:

				
					useServer({
  schema,
  execute,
  subscribe,
  onConnect: (ctx) => {
    const token = ctx.connectionParams?.authorization;
    if (!validateToken(token)) {
      throw new Error("Unauthorized");
    }
  },
}, wsServer);

				
			

Scaling with Redis

For distributed systems, use Redis to share events across multiple instances of your app.

Install Redis and the required library:

				
					npm install ioredis graphql-redis-subscriptions

				
			

Update PubSub initialization

				
					const { RedisPubSub } = require("graphql-redis-subscriptions");
const pubsub = new RedisPubSub({
  connection: {
    host: "localhost",
    port: 6379,
  },
});

				
			

Optimizing Performance

  • Batch Events: Group events before publishing to reduce the number of WebSocket messages.
  • Keep Connections Alive: Monitor and reconnect WebSocket connections if needed.

In this chapter, you learned how to implement GraphQL Subscriptions with Express.js: Configured a real-time subscription server using WebSockets. Built a chat application to demonstrate the Subscription type. Secured the implementation with advanced features like authentication and scaling. This implementation showcases how Express.js can handle real-time updates efficiently using GraphQL Subscriptions, making it a powerful choice for modern web applications. Happy coding !❤️

Table of Contents