GraphQL Subscriptions with JavaScript and Apollo Server
GraphQL is a data query and manipulation language for APIs which was developed by Facebook. Before reading this article, it will be better if you have some basic knowledge of GraphQL. So if you’re new to this, you can refer to this link and have some idea about GraphQL.
Now let’s focus on GraphQL Subscriptions. Subscription is one of the key operations provided by GraphQL. They are mainly used in real-time applications to notify users when a particular event triggers. To familiarize yourself with the topic, think about a message group in a messaging application you use. Here, if anyone messages into the group, every member will immediately receive that message. Though it may seem tricky, this functionality can be easily implemented using a GraphQL subscription. What we need is to set up an event to trigger as if anyone messages to the group. And then, subscribe all the members of the group to that event. After that, whenever the event is triggered, data will be sent to all the members. In addition to subscriptions, GraphQL has Live Queries, which also can be used in the same manner.
As mentioned above, subscriptions usually need an event to listen to. Whenever that event happens, data must be sent to the clients who are subscribed to that event. This can be done by a pub sub system, where a publisher can send messages to a topic and the subscribers of that topic can receive these messages.
Let’s move on to implement GraphQL subscriptions. For that, we use JavaScript and Apollo Server. ( Apollo server will be used as the GraphQL server)
Let’s Start
We are going to build a simple messaging application using GraphQL subscriptions where someone messages, you will receive all that messages.
Step 1: Set up a new Node project and Install Dependencies
First, you need to set up a node project. If you have not installed node yet, follow the instructions in this link. Create a new directory for the node project and run this code.
npm init -y
Install these dependencies to the project.
npm i graphql
npm i apollo-server-express
npm i graphql-subscriptions
npm i subscriptions-transport-ws
npm i @graphql-tools/schema
Create an index.js file in the directory. Then go to the package.json file and modify the scripts field as below.
“scripts”: {“test”: “echo \”Error: no test specified\” && exit 1",“start”: “node index.js”},
Step 2: Set up the server
Import the following modules into the project at the top of the index.js file.
const { createServer } = require(“http”);
const express = require(“express”);
const { execute, subscribe } = require(“graphql”);
Add an async function and set up a new express app and a HTTP server inside it. It will be used to manage all the HTTP requests.
(async () => { const app = express(); const httpServer = createServer(app);})();
Now, the index.js file will be displayed like this.
The rest of the code also must be added inside the Async function.
Step 3: Set up a Pub Sub Instance
To track the events, we need to implement a pub sub system. There is a default in-memory pub sub system provided by the graphql-subscriptions library and we are going to use it here. Since this is an in-memory system it is better not to use this for production environments. For that, the Apollo community suggests to use their community-created pub sub libraries. I am hoping to discuss more about the pub sub systems in my next article.
You need to import the module on the top of the code.
const { PubSub } = require(“graphql-subscriptions”);
Create a new pub sub instance inside the async function.
const pubsub = new PubSub();
Step 4: Define the GraphQL Schema
Next, you need to define the structure of your data in GraphQL. For that, we need to add the following schema.
const typeDefs = gql` type Query {
viewMessages: [Message!]
}
type Mutation {
sendMessage(name: String, content: String): Message!
}
type Subscription {
receiveMessage: Message!
}
type Message {
id: ID!
name: String!
content: String
}`;
Step 5: Add Resolvers
Resolvers are used to populate data to the fields which are defined in the schema. Therefore you need to pay attention to the data types of the fields in the schema and return the exact type of data from the resolvers. You can use the following code as the resolvers for our example.
let messages = []const resolvers = {
Query: {
viewMessages() {
return messages;
},
},
Mutation: {
sendMessage: (parent, { name, content }) => {
const id = messages.length;
var new_message = {
id,
name,
content
}
messages.push(new_message);
pubsub.publish(“MessageService”, {receiveMessage: new_message});
return new_message;
},
},
Subscription: {
receiveMessage: {
subscribe:()=> pubsub.asyncIterator([“MessageService”]),
},
},
};
Here you can see how the pub sub model is used for the subscription. As discussed earlier, there must be an event to trigger a subscription. In the above code, the name of that event is ‘MessageService’. (You can choose any name for that. But remember to use the same event name in the subscription resolver as well). On one end, data has to be published to that event and on the other end, data must return to all the subscribers of that event. For that inside sendMessage mutation resolver, a message is published to the pub sub event. And in the receiveMessage subscription resolver, it returns an AsyncIterator from the pub sub (clients get subscribed to the event inside this function). Using that, the subscription server in JavaScript retrieves all the published data using a loop and return data as GraphQL responses to all the subscribed clients.
AsyncIterator
AsyncIterator can run as a normal iterator but it has a next() method that returns promises. Because of that when a next() method is invoked in the internal loop mentioned above, it has to waits until the data has arrived in AsyncIterator. Therefore only when the data is published, AsyncIterator can return data.
Step 6: Enable the Subscriptions
To enable subscriptions for the server, first, you need to import the following modules on top of your code.
const { ApolloServer, gql } = require(“apollo-server-express”);
const { SubscriptionServer } =require(“subscriptions-transport-ws”);
const { makeExecutableSchema } = require(“@graphql-tools/schema”);
Inside the async function, you need to create a GraphQL schema instance and pass it to the Apollo Server. You need a Subscription Server to create a WebSockets endpoint over the current HTTP endpoint. It will be used to communicate with the clients when they are requesting subscriptions.
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({schema,});
await server.start();
server.applyMiddleware({ app });
SubscriptionServer.create({ schema, execute, subscribe },{ server: httpServer, path: '/graphql'});
Step 7: Listen for the Requests
const PORT = 4000;httpServer.listen(PORT, () => {console.log(`Query endpoint ready at http://localhost:${PORT}${server.graphqlPath}`);console.log(`Subscription endpoint ready at ws://localhost:${PORT}${server.graphqlPath}`);});
Configuring the server to send/receive messages asynchronously is completed now. The final version of the index.js file is displayed like this.
Step 8: Testing the Subscriptions
Since we have set up the server now, we need to test its functionality. Apollo also provides client-side support but only the server-side will be discussed here. Instead, we can use Apollo Studio Explorer (GraphQL IDE provided by Apollo) to test our server.
First, start the node project
npm start
Then you will see two links in the console
Query endpoint ready at http://localhost:4000/graphql
Subscription endpoint ready at ws://localhost:4000/graphql
To enter Apollo Studio Explorer, you can either go to the query endpoint link or you can just go to this link. When you enter the GraphQL IDE, there will be a link in the top left corner and check if that link is your HTTP endpoint.
After that, you need to add a subscription request in the IDE as in the image below. You can use this query for that.
subscription receive {
receiveMessage{
id
name
content
}
}
Now click the button with the operation name of your request. Then at the bottom right corner, you will see the WebSockets connection is established and it is listening to the events now.
You have to send messages to trigger the event so that you can receive data from subscriptions. For that, you create a new tab in the Operation box. And in the new tab add the following mutation and run it.
mutation send {
sendMessage(name: “User”, content: “Hello”){
id
name
content
}
}
Then you can see the response for the mutation and also the subscription responses at the bottom. You can run the mutation a couple of times and see how the subscriptions work when events trigger. You can view the GitHub repository with full code here. Contact me if you have got any issues in configuring the server.