Skip to main content

Subscriptions

Understand real-time data delivery with GraphQL subscriptions.


What is a Subscription?

A subscription is a long-lived operation that delivers data to the client whenever a specific event occurs on the server. Unlike queries and mutations (request-response), subscriptions maintain an open connection.

┌─────────────────────────────────────────────────────────────────────┐
│ QUERY vs MUTATION vs SUBSCRIPTION │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ QUERY MUTATION SUBSCRIPTION │
│ ───────────────────────────────────────────────────────────────── │
│ Request → Response Request → Response Open connection │
│ One-time fetch One-time write Continuous delivery │
│ Client initiates Client initiates Server pushes │
│ Stateless Stateless Stateful │
│ │
│ "Give me data" "Change data" "Tell me when X happens" │
│ │
└─────────────────────────────────────────────────────────────────────┘

Why Subscriptions?

Traditional approaches to real-time data have limitations:

Polling

Client: "Any new messages?"    (every 1 second)
Server: "No"
Client: "Any new messages?"
Server: "No"
Client: "Any new messages?"
Server: "Yes, here's one"

Problems: Wasted requests, latency (up to polling interval), server load

Long Polling

Client holds connection open until data is available, then reconnects. Problems: Complex, connection overhead, not true real-time

Subscriptions

Client: "Tell me when there are new messages"
Server: (connection stays open)
... time passes ...
Server: "New message: Hello!"
Server: "New message: How are you?"

Benefits: Instant delivery, efficient, single connection


Subscription Syntax

Subscriptions look like queries but use the subscription keyword:

subscription OnMovieAdded {
movieAdded {
id
title
releaseYear
genre
}
}

When a new movie is created, the server pushes:

{
"data": {
"movieAdded": {
"id": "99",
"title": "Dune: Part Two",
"releaseYear": 2024,
"genre": "SCIFI"
}
}
}

Subscriptions in the Schema

Define subscriptions in the Subscription type:

type Subscription {
"""
Triggered when a new movie is added to the database.
"""
movieAdded: Movie!

"""
Triggered when any movie is updated.
"""
movieUpdated: Movie!

"""
Triggered when a movie is deleted.
"""
movieDeleted: MovieDeletedPayload!

"""
Triggered when a new review is posted for a specific movie.
"""
reviewAdded(movieId: ID!): Review!

"""
Triggered when the Oscar ceremony announces an award.
"""
awardAnnounced(category: String): Award!
}

type MovieDeletedPayload {
id: ID!
title: String!
deletedAt: DateTime!
}

Subscription Arguments

Subscriptions can accept arguments to filter events:

# Subscribe to reviews for a specific movie
subscription OnReviewAdded($movieId: ID!) {
reviewAdded(movieId: $movieId) {
id
rating
comment
user {
displayName
}
}
}

Variables:

{
"movieId": "42"
}

Only reviews for movie 42 will be delivered. Reviews for other movies are filtered out.

Common Filtering Patterns

type Subscription {
# Filter by ID
movieUpdated(movieId: ID): Movie!

# Filter by type/category
awardAnnounced(category: String): Award!

# Filter by user
notificationReceived(userId: ID!): Notification!

# Multiple filters
messageReceived(channelId: ID!, priority: Priority): Message!
}

Transport Protocols

Unlike queries and mutations (HTTP), subscriptions require a persistent connection:

WebSocket (Most Common)

┌─────────────────────────────────────────────────────────────────────┐
│ WEBSOCKET FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CLIENT SERVER │
│ │ │ │
│ │──── WebSocket Handshake ─────────────▶│ │
│ │◀─── Connection Established ──────────│ │
│ │ │ │
│ │──── Subscribe (movieAdded) ──────────▶│ │
│ │◀─── Acknowledgment ──────────────────│ │
│ │ │ │
│ │ (waiting for events...) │ │
│ │ │ │
│ │◀─── Data: New movie added ───────────│ │
│ │◀─── Data: Another movie added ───────│ │
│ │ │ │
│ │──── Unsubscribe ─────────────────────▶│ │
│ │◀─── Complete ────────────────────────│ │
│ │ │ │
└─────────────────────────────────────────────────────────────────────┘

Protocols built on WebSocket:

  • graphql-ws (modern, recommended)
  • subscriptions-transport-ws (legacy, deprecated)

Server-Sent Events (SSE)

One-way communication from server to client. Simpler than WebSocket but less flexible. Works over HTTP.

HTTP Streaming

Some implementations use HTTP streaming for subscriptions. Useful when WebSocket isn't available.


Subscription Events

Subscriptions are triggered by events on the server. Common patterns:

Database Changes

User creates movie → Trigger "movieAdded" subscription
User updates movie → Trigger "movieUpdated" subscription
User deletes movie → Trigger "movieDeleted" subscription

External Events

Payment processed → Trigger "paymentReceived" subscription
File uploaded → Trigger "uploadCompleted" subscription
API webhook → Trigger relevant subscription

Scheduled Events

Every minute → Trigger "stockPriceUpdated" subscription
Every hour → Trigger "weatherUpdated" subscription

Use Cases

Real-Time Notifications

subscription {
notificationReceived {
id
type
message
createdAt
}
}

Live Chat

subscription OnMessage($channelId: ID!) {
messageReceived(channelId: $channelId) {
id
content
sender {
name
avatar
}
sentAt
}
}

Live Updates (Dashboards, Feeds)

subscription {
stockPriceUpdated(symbols: ["AAPL", "GOOGL"]) {
symbol
price
change
updatedAt
}
}

Collaborative Editing

subscription OnDocumentChanged($documentId: ID!) {
documentChanged(documentId: $documentId) {
changeType
path
value
editor {
name
}
}
}

Live Scores

subscription {
scoreUpdated(gameId: "superbowl-2024") {
homeTeam
homeScore
awayTeam
awayScore
quarter
timeRemaining
}
}

Subscription Lifecycle

┌─────────────────────────────────────────────────────────────────────┐
│ SUBSCRIPTION LIFECYCLE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. SUBSCRIBE │
│ Client sends subscription query │
│ Server validates and registers subscription │
│ │
│ 2. LISTEN │
│ Server listens for relevant events │
│ Connection remains open │
│ │
│ 3. PUBLISH (repeats) │
│ Event occurs → Server filters subscriptions │
│ Matching subscriptions receive data │
│ │
│ 4. UNSUBSCRIBE │
│ Client closes connection or sends unsubscribe │
│ Server cleans up subscription │
│ │
│ Triggers for unsubscribe: │
│ - Client explicitly unsubscribes │
│ - Client disconnects (network, tab close) │
│ - Server terminates (error, shutdown) │
│ - Subscription completes (if finite) │
│ │
└─────────────────────────────────────────────────────────────────────┘

Design Considerations

1. Keep Payloads Small

# ✅ Good - minimal payload
subscription {
movieUpdated {
id
title
rating
}
}

# ❌ Avoid - huge payload on every update
subscription {
movieUpdated {
id
title
fullPlot
allReviews { ... }
allActors { ... }
allAwards { ... }
}
}

2. Filter on the Server

# ✅ Good - server filters
subscription {
reviewAdded(movieId: "42") { ... }
}

# ❌ Avoid - client filters from all events
subscription {
reviewAdded {
movieId # Client checks if movieId === "42"
...
}
}

3. Handle Reconnection

Clients should handle:

  • Connection drops
  • Server restarts
  • Network changes

Most client libraries handle reconnection automatically.

4. Consider Scale

Subscriptions are stateful. At scale, consider:

  • How many concurrent connections?
  • How to distribute across servers? (pub/sub systems like Redis)
  • Memory usage per subscription

When NOT to Use Subscriptions

Subscriptions aren't always the best choice:

ScenarioBetter Alternative
Data changes rarely (< 1/min)Polling
User can tolerate delayPolling with reasonable interval
Simple read-after-writeReturn updated data from mutation
One-time data fetchQuery
High volume, low priorityPolling or batch updates

Summary

ConceptDescription
SubscriptionLong-lived operation for real-time data
EventServer-side trigger that sends data to subscribers
WebSocketCommon transport for subscriptions
FilteringArguments to receive only relevant events
LifecycleSubscribe → Listen → Publish → Unsubscribe

What's Next?

In the next chapter, we'll explore Introspection - how GraphQL APIs describe themselves and enable powerful tooling.