Overview
GraphQL subscriptions enable real-time communication in RealtimeChat. When messages are sent, edited, or deleted, all subscribed clients receive instant updates via WebSocket.
Subscriptions require a WebSocket connection to the GraphQL endpoint. Standard HTTP requests will not work.
How Subscriptions Work
Client connects to the GraphQL WebSocket endpoint
Client subscribes to specific events (e.g., OnMessageUpdated)
Server pushes updates whenever mutations trigger events
Client receives real-time notifications without polling
Event-Driven Receive updates only when changes occur
Low Latency Near-instant message delivery
Efficient No need for polling or repeated queries
Bi-directional Full-duplex WebSocket communication
WebSocket Endpoint
Connect to the GraphQL subscription endpoint:
ws://localhost:5000/graphql
For production (with TLS):
wss://your-domain.com/graphql
OnMessageUpdated
Subscribe to all message events (added, updated, deleted) across the chat system.
Event Types
The subscription emits events with three possible types:
A new message was sent to a chat room
An existing message was edited
A message was deleted from a chat room
GraphQL Schema
type Subscription {
onMessageUpdated : MessageUpdatedEvent !
}
type MessageUpdatedEvent {
eventType : String !
message : MessageGraph
}
type MessageGraph {
id : Int !
sentAt : DateTime !
senderId : String !
chatRoomId : Int !
content : MessageContent !
}
Example Subscription
subscription OnMessageUpdated {
onMessageUpdated {
eventType
message {
id
senderId
sentAt
chatRoomId
content {
... on TextMessageContentGraph {
text
}
}
}
}
}
Event Payloads
ADDED Event
Received when a new message is sent via sendMessage mutation:
{
"data" : {
"onMessageUpdated" : {
"eventType" : "ADDED" ,
"message" : {
"id" : 105 ,
"senderId" : "user456" ,
"sentAt" : "2026-03-05T11:00:00Z" ,
"chatRoomId" : 1 ,
"content" : {
"text" : "New message just arrived!"
}
}
}
}
}
UPDATED Event
Received when a message is edited via editMessage mutation:
{
"data" : {
"onMessageUpdated" : {
"eventType" : "UPDATED" ,
"message" : {
"id" : 105 ,
"senderId" : "user456" ,
"sentAt" : "2026-03-05T11:00:00Z" ,
"chatRoomId" : 1 ,
"content" : {
"text" : "New message just arrived! (edited)"
}
}
}
}
}
DELETED Event
Received when a message is deleted via deleteMessage mutation:
{
"data" : {
"onMessageUpdated" : {
"eventType" : "DELETED" ,
"message" : {
"id" : 105 ,
"senderId" : "user456" ,
"sentAt" : "2026-03-05T11:00:00Z" ,
"chatRoomId" : 1 ,
"content" : {
"text" : "New message just arrived! (edited)"
}
}
}
}
}
Implementation Details
From ChatRoomSubscription.cs:3-8:
public class MessageSubscription
{
[ Subscribe ]
[ Topic ( "MessageUpdated" )]
public MessageUpdatedEvent OnMessageUpdated (
[ EventMessage ] MessageUpdatedEvent @event
) => @event ;
}
The subscription listens to the "MessageUpdated" topic, which is published by all message mutations.
Client Implementation
JavaScript/TypeScript Client
Using graphql-ws library:
import { createClient } from 'graphql-ws' ;
const client = createClient ({
url: 'ws://localhost:5000/graphql' ,
});
const subscription = client . subscribe (
{
query: `
subscription {
onMessageUpdated {
eventType
message {
id
senderId
sentAt
chatRoomId
content {
... on TextMessageContentGraph {
text
}
}
}
}
}
` ,
},
{
next : ( data ) => {
const { eventType , message } = data . data . onMessageUpdated ;
switch ( eventType ) {
case 'ADDED' :
console . log ( 'New message:' , message );
// Add message to UI
break ;
case 'UPDATED' :
console . log ( 'Message updated:' , message );
// Update message in UI
break ;
case 'DELETED' :
console . log ( 'Message deleted:' , message );
// Remove message from UI
break ;
}
},
error : ( error ) => {
console . error ( 'Subscription error:' , error );
},
complete : () => {
console . log ( 'Subscription completed' );
},
}
);
// Unsubscribe when done
subscription . unsubscribe ();
React Hook Example
import { useEffect , useState } from 'react' ;
import { createClient } from 'graphql-ws' ;
function useMessageSubscription ( chatRoomId : number ) {
const [ messages , setMessages ] = useState < Message []>([]);
useEffect (() => {
const client = createClient ({
url: 'ws://localhost:5000/graphql' ,
});
const subscription = client . subscribe (
{
query: `
subscription {
onMessageUpdated {
eventType
message {
id
senderId
sentAt
chatRoomId
content {
... on TextMessageContentGraph {
text
}
}
}
}
}
` ,
},
{
next : ( data ) => {
const { eventType , message } = data . data . onMessageUpdated ;
// Only process messages from this chat room
if ( message . chatRoomId !== chatRoomId ) return ;
setMessages (( prev ) => {
switch ( eventType ) {
case 'ADDED' :
return [ ... prev , message ];
case 'UPDATED' :
return prev . map (( m ) =>
m . id === message . id ? message : m
);
case 'DELETED' :
return prev . filter (( m ) => m . id !== message . id );
default :
return prev ;
}
});
},
error : ( error ) => console . error ( error ),
}
);
return () => subscription . unsubscribe ();
}, [ chatRoomId ]);
return messages ;
}
Apollo Client Example
import { useSubscription , gql } from '@apollo/client' ;
const MESSAGE_UPDATED_SUBSCRIPTION = gql `
subscription OnMessageUpdated {
onMessageUpdated {
eventType
message {
id
senderId
sentAt
chatRoomId
content {
... on TextMessageContentGraph {
text
}
}
}
}
}
` ;
function ChatRoom ({ roomId }) {
const { data , loading , error } = useSubscription (
MESSAGE_UPDATED_SUBSCRIPTION
);
useEffect (() => {
if ( data ?. onMessageUpdated ) {
const { eventType , message } = data . onMessageUpdated ;
if ( message . chatRoomId === roomId ) {
handleMessageEvent ( eventType , message );
}
}
}, [ data , roomId ]);
if ( loading ) return < p > Connecting... </ p > ;
if ( error ) return < p > Error: { error . message } </ p > ;
return < div > { /* Your chat UI */ } </ div > ;
}
Connection Lifecycle
Connection Flow
Initialize WebSocket connection
Send connection init message
Subscribe to events
Receive real-time updates
Unsubscribe when done
Close connection
Reconnection Strategy
import { createClient } from 'graphql-ws' ;
const client = createClient ({
url: 'ws://localhost:5000/graphql' ,
retryAttempts: 5 ,
retryWait : async ( retries ) => {
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
await new Promise (( resolve ) =>
setTimeout ( resolve , Math . min ( 1000 * Math . pow ( 2 , retries ), 30000 ))
);
},
on: {
connected : () => console . log ( 'Connected to GraphQL subscriptions' ),
closed : () => console . log ( 'Connection closed' ),
error : ( error ) => console . error ( 'WebSocket error:' , error ),
},
});
Real-Time Chat Example
Complete example combining queries, mutations, and subscriptions:
import { createClient } from 'graphql-ws' ;
class ChatClient {
private client : Client ;
private subscription : any ;
constructor ( private url : string ) {
this . client = createClient ({ url });
}
// Subscribe to message updates
subscribeToMessages ( onUpdate : ( event : MessageUpdatedEvent ) => void ) {
this . subscription = this . client . subscribe (
{
query: `
subscription {
onMessageUpdated {
eventType
message {
id
senderId
sentAt
chatRoomId
content {
... on TextMessageContentGraph {
text
}
}
}
}
}
` ,
},
{
next : ( data ) => onUpdate ( data . data . onMessageUpdated ),
error : ( err ) => console . error ( 'Subscription error:' , err ),
}
);
}
// Send a message (mutation)
async sendMessage ( chatRoomId : number , senderId : string , text : string ) {
const response = await fetch ( 'http://localhost:5000/graphql' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
query: `
mutation {
sendMessage(
chatRoomId: ${ chatRoomId }
senderId: " ${ senderId } "
text: " ${ text } "
) {
id
sentAt
}
}
` ,
}),
});
return response . json ();
}
// Fetch message history (query)
async getMessages ( chatRoomId : number ) {
const response = await fetch ( 'http://localhost:5000/graphql' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
query: `
query {
getMessages(chatRoomId: ${ chatRoomId } ) {
id
senderId
sentAt
content {
... on TextMessageContentGraph {
text
}
}
}
}
` ,
}),
});
return response . json ();
}
disconnect () {
this . subscription ?. unsubscribe ();
this . client . dispose ();
}
}
// Usage
const chat = new ChatClient ( 'ws://localhost:5000/graphql' );
// Subscribe to updates
chat . subscribeToMessages (( event ) => {
console . log ( `Event: ${ event . eventType } ` , event . message );
});
// Load message history
const history = await chat . getMessages ( 1 );
// Send a message
await chat . sendMessage ( 1 , 'user123' , 'Hello from the client!' );
// Clean up
chat . disconnect ();
Error Handling
Connection Errors
const client = createClient ({
url: 'ws://localhost:5000/graphql' ,
on: {
error : ( error ) => {
console . error ( 'WebSocket error:' , error );
// Handle connection errors
},
closed : ( event ) => {
console . log ( 'Connection closed:' , event );
// Handle disconnection
},
},
});
Subscription Errors
const subscription = client . subscribe (
{ query: '...' },
{
next : ( data ) => console . log ( data ),
error : ( error ) => {
console . error ( 'Subscription error:' , error );
// Handle subscription-specific errors
},
complete : () => {
console . log ( 'Subscription completed' );
// Handle completion
},
}
);
Best Practices
Filter on Client Since subscriptions receive all message events, filter by chatRoomId on the client side to display only relevant messages.
Handle All Event Types Always handle ADDED, UPDATED, and DELETED events to keep your UI in sync.
Implement Reconnection Use exponential backoff for reconnection attempts to handle network issues gracefully.
Unsubscribe on Unmount Always clean up subscriptions when components unmount to prevent memory leaks.
Combine with Queries Load initial data with queries, then use subscriptions for real-time updates.
Source Code Reference
The subscription is implemented in:
Infrastructure/RealtimeChat.Infrastructure.GraphQL/Subscriptions/ChatRoomSubscription.cs
Related types:
Infrastructure/RealtimeChat.Infrastructure.GraphQL/Events/MessageUpdatedEvent.cs
Infrastructure/RealtimeChat.Infrastructure.GraphQL/Models/MessageGraph.cs
Mutations that trigger events:
Infrastructure/RealtimeChat.Infrastructure.GraphQL/Mutations/MessageMutation.cs
Next Steps
Queries Fetch initial message history
Mutations Learn about mutations that trigger events