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
userJoined
event. - Steps:
- Define the
userJoined
subscription type in the schema. - Implement the resolver for the
userJoined
subscription. - 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.