Introduction
GraphQL Subscriptions are a powerful feature that allows clients to receive real-time updates from the server. Unlike queries and mutations, which follow a request-response model, subscriptions enable a persistent connection between the client and the server, allowing the server to push updates to the client as they happen.
Key Concepts
- What are Subscriptions?
- Definition: Subscriptions are a way to maintain a real-time connection between the client and the server.
- Use Cases: Ideal for applications that require real-time updates, such as chat applications, live sports scores, or stock market updates.
- How Subscriptions Work
- Persistent Connection: Subscriptions use WebSockets to maintain a persistent connection.
- Event-Driven: The server pushes updates to the client when specific events occur.
- Setting Up Subscriptions
- Server-Side: Define subscription types and resolvers.
- Client-Side: Use a client library to handle the subscription connection.
Practical Example
Step 1: Define Subscription Type in Schema
First, you need to define a subscription type in your GraphQL schema.
type Subscription {
messageAdded: Message
}
type Message {
id: ID!
content: String!
author: String!
}Step 2: Implement Subscription Resolver
Next, implement the resolver for the subscription. This example uses Apollo Server.
const { ApolloServer, gql, PubSub } = require('apollo-server');
const pubsub = new PubSub();
const typeDefs = gql`
type Subscription {
messageAdded: Message
}
type Message {
id: ID!
content: String!
author: String!
}
type Query {
messages: [Message]
}
type Mutation {
addMessage(content: String!, author: String!): Message
}
`;
const messages = [];
const MESSAGE_ADDED = 'MESSAGE_ADDED';
const resolvers = {
Query: {
messages: () => messages,
},
Mutation: {
addMessage: (parent, { content, author }) => {
const message = { id: messages.length + 1, content, author };
messages.push(message);
pubsub.publish(MESSAGE_ADDED, { messageAdded: message });
return message;
},
},
Subscription: {
messageAdded: {
subscribe: () => pubsub.asyncIterator([MESSAGE_ADDED]),
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
path: '/subscriptions',
},
});
server.listen().then(({ url, subscriptionsUrl }) => {
console.log(`Server ready at ${url}`);
console.log(`Subscriptions ready at ${subscriptionsUrl}`);
});Step 3: Client-Side Subscription
On the client side, use Apollo Client to subscribe to the messageAdded event.
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { split } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { HttpLink } from '@apollo/client';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/',
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000/subscriptions`,
options: {
reconnect: true,
},
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
client
.subscribe({
query: gql`
subscription {
messageAdded {
id
content
author
}
}
`,
})
.subscribe({
next(data) {
console.log(data);
},
});Exercises
Exercise 1: Basic Subscription
- Task: Implement a basic subscription for a
userJoinedevent. - Steps:
- Define the
userJoinedsubscription type in the schema. - Implement the resolver for the
userJoinedsubscription. - Test the subscription using Apollo Client.
- Define the
Solution
const USER_JOINED = 'USER_JOINED';
const resolvers = {
Mutation: {
addUser: (parent, { name }) => {
const user = { id: users.length + 1, name };
users.push(user);
pubsub.publish(USER_JOINED, { userJoined: user });
return user;
},
},
Subscription: {
userJoined: {
subscribe: () => pubsub.asyncIterator([USER_JOINED]),
},
},
};client
.subscribe({
query: gql`
subscription {
userJoined {
id
name
}
}
`,
})
.subscribe({
next(data) {
console.log(data);
},
});Common Mistakes and Tips
- WebSocket Connection Issues: Ensure the WebSocket server is correctly configured and running.
- Subscription Path: Verify the subscription path matches between the server and client.
- Event Naming: Use consistent and descriptive event names to avoid confusion.
Conclusion
GraphQL Subscriptions provide a robust way to implement real-time features in your applications. By understanding how to define subscription types, implement resolvers, and set up client-side subscriptions, you can create dynamic and responsive applications that keep users updated in real-time.
