59 Subscription Schema and Resolver
59 Subscription Schema and Resolver: Understanding the Concepts, Practice, and Implementation in GraphQL
When we talk about GraphQL, we cannot separate it from its three main pillars: schema, resolver, and subscription. In this article specifically, I’ll dig deep into subscription schemas and resolvers and why they matter in modern API development.
I’ll assume the reader is already familiar with the basic concepts of queries and mutations in GraphQL. This time we’ll focus on the subscription mechanism, which serves as the bridge between the backend and a real-time frontend. You’ll find explanations, simulations, comparison tables, and even real code examples covering subscriptions in GraphQL.
1. Subscriptions in GraphQL: What Are They and Why Do They Matter?
Before diving into schemas and resolvers, let’s refresh our understanding of subscriptions a bit.
A subscription allows a client to subscribe to specific events and receive data in real time, without having to manually poll the server. This is extremely useful for chat applications, analytics dashboards, notifications, and a wide range of applications built around real-time concepts.
A comparison of the data communication models:
| Query | Mutation | Subscription | |
|---|---|---|---|
| Nature | Request/response | Write | Stream/push |
| Response | Once | Once | Periodic (realtime) |
| Example use case | Fetch a table | Delete data | Incoming chat |
2. Subscription Flow Diagram
To make it easier to picture, here is the subscription flow diagram that we’ll implement later, using Mermaid syntax:
sequenceDiagram
participant Client
participant Server
participant PubSub
Client->>Server: Kirim request subscription
Server-->>Client: Mendengar event (connection ack)
PubSub-->>Server: New event published
Server-->>Client: Kirim data baru via websocket
Here, the Server listens for events from PubSub (the Publish/Subscribe mechanism) and then forwards them to any Client that has an open connection.
3. Declaring a Subscription Schema in GraphQL
The schema in GraphQL defines the shape and types of events that a client can subscribe to.
1type Subscription {
2 messageSent(roomId: ID!): Message
3}
4
5type Message {
6 id: ID!
7 content: String!
8 sender: String!
9 sentAt: String!
10}Explanation:
- The subscription is named
messageSentand returns aMessagetype. - The
roomIdparameter is of type ID — it will only receive messages from a specific room. - The
Messagetype describes the shape of the object sent to the client.
4. Resolver: The Bridge Between Schema and Backend
A resolver is the logic code that connects the schema to a data source (whether that’s a database, an event emitter, and so on).
Implementing a subscription differs from a query or mutation. A subscription resolver must return an async iterator so the server can stream data to the client.
We’ll use the popular graphql-subscriptions
package, which provides a simple PubSub backed by an internal EventEmitter.
Step 1: Set up PubSub and the Resolver
1const { PubSub } = require('graphql-subscriptions');
2const pubsub = new PubSub();
3
4const SUBSCRIPTION_TOPIC = 'MESSAGE_SENT';
5
6const resolvers = {
7 Subscription: {
8 messageSent: {
9 subscribe: (_, { roomId }) =>
10 pubsub.asyncIterator(`${SUBSCRIPTION_TOPIC}_${roomId}`),
11 },
12 },
13};Explanation:
- When a client subscribes to
messageSent, the resolver returns an async iterator for a specific topic (in this case, based on the roomId).
Step 2: Publish an Event When a New Message Arrives
Simulate publishing an event when a new message is sent (for example, via the sendMessage mutation):
1const resolvers = {
2 Mutation: {
3 sendMessage: async (_, { roomId, content, sender }) => {
4 const message = { id: Date.now(), content, sender, sentAt: new Date().toISOString() };
5 // Simulate saving to the DB
6 // db.saveMessage(message);
7
8 // Publish the event to subscribers of the matching topic
9 await pubsub.publish(`${SUBSCRIPTION_TOPIC}_${roomId}`, {
10 messageSent: message,
11 });
12 return message;
13 },
14 },
15};5. Simulating the Subscription Flow
Let’s test the process:
Simulation Steps
Client A subscribes to the following subscription:
1subscription { 2 messageSent(roomId: "room-59") { 3 id 4 content 5 sender 6 sentAt 7 } 8}Client B sends a message via a mutation:
1mutation { 2 sendMessage(roomId: "room-59", content: "Halo, GraphQL!", sender: "Budi") { 3 id 4 content 5 } 6}Client A receives the new message without any polling. The server automatically pushes the data:
1{ 2 "data": { 3 "messageSent": { 4 "id": "1718717500000", 5 "content": "Halo, GraphQL!", 6 "sender": "Budi", 7 "sentAt": "2024-06-15T19:38:20Z" 8 } 9 } 10}
In practice, you’ll often use WebSocket (for example subscriptions-transport-ws or a package within Apollo Server) as the underlying protocol to achieve true real-time behavior.
6. Case Study: Comparison Table (Query vs Subscription)
| Feature | Query | Subscription |
|---|---|---|
| Mechanism | HTTP, stateless | WebSocket, stateful connection |
| When to use | Data snapshot | Real-time event streaming |
| Scalability | Easy (caching, CDN) | Fairly hard, needs proper infra |
| Popular use cases | Reports, statistics | Chat, notifications, realtime prices |
7. Best Practices & Tips
- Use Filtering: Always limit the events being sent. Use parameters on the subscription (for example, roomId).
- Authentication: Make sure only authorized clients can subscribe; filter at the resolver layer.
- Scalability: Implement tools like Redis PubSub or Kafka once your application reaches a larger scale.
- Handle Disconnects: Make sure clients can reconnect/re-subscribe if the WebSocket connection drops.
8. End-to-End Implementation Example (Mini Project)
Here is a snippet of a GraphQL server (Node.js + Apollo Server) that runs subscriptions with a local PubSub:
1const { ApolloServer, gql } = require('apollo-server');
2const { PubSub } = require('graphql-subscriptions');
3const pubsub = new PubSub();
4
5const typeDefs = gql`
6 type Message {
7 id: ID!
8 content: String!
9 sender: String!
10 sentAt: String!
11 }
12
13 type Query {
14 _empty: String
15 }
16
17 type Mutation {
18 sendMessage(roomId: ID!, content: String!, sender: String!): Message!
19 }
20
21 type Subscription {
22 messageSent(roomId: ID!): Message!
23 }
24`;
25
26const SUBSCRIPTION_TOPIC = 'MESSAGE_SENT';
27
28const resolvers = {
29 Mutation: {
30 sendMessage: async (_, { roomId, content, sender }) => {
31 const message = { id: Date.now(), content, sender, sentAt: new Date().toISOString() };
32 await pubsub.publish(`${SUBSCRIPTION_TOPIC}_${roomId}`, {
33 messageSent: message,
34 });
35 return message;
36 },
37 },
38 Subscription: {
39 messageSent: {
40 subscribe: (_, { roomId }) =>
41 pubsub.asyncIterator(`${SUBSCRIPTION_TOPIC}_${roomId}`),
42 },
43 },
44};
45
46const server = new ApolloServer({ typeDefs, resolvers });
47
48server.listen().then(({ url, subscriptionsUrl }) => {
49 console.log(`🚀 Server ready at ${url}`);
50 console.log(`🚀 Subscriptions ready at ${subscriptionsUrl}`);
51});9. Conclusion: Subscription = The Future of the Real-Time Web
By understanding subscription schemas and resolvers, we can build real-time features that are efficient, clean, and maintainable. GraphQL subscriptions offer an ideal solution in an era of increasingly interactive and responsive applications.
The main challenge lies in scalability and connection management, but with the right architecture and best practices, subscriptions can become a go-to weapon for the modern backend.
Interested in trying it in your own project? Let’s subscribe to the future! 🚀