Learn GraphQL - Building Collaborative Real-Time Apps
Episode 38 of 51

Learn GraphQL - Building Collaborative Real-Time Apps

Episode 38 builds collaborative real-time apps: live queries with @live, presence features like online status and cursor positions, conflict resolution with operational transformation and CRDT, and concrete implementations of collaborative editing, real-time chat, and live dashboards.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Collaborative features — documents edited together, chat, and live dashboards — are what set modern apps apart. Episode 38 combines subscriptions (episode 16) with live query concepts and conflict handling to build real collaborative apps.

We'll cover live queries, presence features, conflict resolution with OT and CRDT, then close with concrete implementation examples.

Live Queries

Concept and the @live Directive

A live query differs from a subscription: subscriptions need a special operation definition, while live queries make an ordinary query real-time — when data changes, the query is re-run automatically. The @live directive marks a query that should stay alive:

Live query with @live
query DashboardStats @live {
  activeUsers: Int!
  ordersToday: Int!
}

Implementing live queries requires synchronization with data changes — usually through database integration (for example Hasura live queries) or a change-monitoring library. Use cases: metric dashboards, collaborative editing, and always-fresh lists.

Performance Considerations

Live queries run continuously; without care, they can load the server. Limit update frequency, limit the number of live queries per client, and combine several queries into one for efficiency.

Presence Features

Online Status and Cursor Positions

Presence tells users who is currently active. Presence data is highly real-time and per-client:

Presence schema
type Subscription {
  presenceUpdated(roomId: ID!): [Presence!]!
}
 
type Presence {
  userId: ID!
  status: PresenceStatus!
  cursorPosition: CursorPosition
}
 
type CursorPosition {
  x: Float!
  y: Float!
}

Implementation: each client publishes its status (via a mutation or an activity-monitoring directive), and the presenceUpdated subscription sends the list of active users. Cursor positions for collaborative editing are sent at high frequency — throttle the sends (for example 10 times per second) and interpolate on the client.

Real-Time Indicators

Combine presence with active typing indicators: users who are typing are flagged through an event published to a specific topic. This gives the familiar "typing..." feel of chat apps.

Conflict Resolution

Operational Transformation (OT)

When two users edit the same document simultaneously, conflicts arise. Operational Transformation (OT) resolves them by transforming operations so the edit result is consistent regardless of arrival order:

OT concept
user A: insert("a", 0) -> transform -> apply
user B: insert("b", 2) -> transform -> apply

OT is the technique behind Google Docs and many collaborative editors. Its implementation is complex, and in practice you'll more often use ready-made libraries like Yjs.

CRDT and Merge Strategies

CRDT (Conflict-free Replicated Data Type) is the modern approach: every node computes the same result without central coordination, so conflicts resolve automatically. The Yjs library is a popular implementation; install with npm install yjs y-websocket:

JSCollaborative document with Yjs
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
 
const doc = new Y.Doc();
const provider = new WebsocketProvider("ws://localhost:1234", "room-1", doc);
const text = doc.getText("content");
 
text.insert(0, "Halo bersama!");

For simple cases (configuration, status), last-write-wins is enough: the latest value wins. Choose a strategy based on the nature of the data: collaborative text needs CRDT, non-conflicting data only needs last-write-wins.

Implementation Examples

Collaborative Editing and Real-Time Chat

Collaborative document editing: use Yjs for the text, and GraphQL subscriptions for presence and the document list. Each document has its own subscription topic (documentUpdated(id)), and text changes sync via Yjs separately from GraphQL — the two run side by side.

Real-time chat: you already built this in episode 16. Add collaborative features: read receipts, typing indicators (presence), and real-time moderation that filters messages before they're published.

Multiplayer and Live Dashboards

For multiplayer, apply the same pattern: one subscription per room, game state in Redis (stateless servers), and per-topic pubsub. For a live dashboard, combining live queries for aggregate metrics with subscriptions for granular changes gives an always-fresh view without polling.

Conclusion

Key takeaways:

  • Live queries make ordinary queries real-time via the @live directive.
  • Presence tracks online status and cursor positions through subscriptions.
  • OT and CRDT resolve simultaneous-edit conflicts; Yjs is the practical implementation.
  • Last-write-wins is enough for non-conflicting data.
  • Collaborative editing separates text sync (Yjs) from metadata (GraphQL).
  • Limit real-time data frequency so the server isn't overloaded.

In the next episode, episode 39, you'll learn about GraphQL with Blockchain and Web3 — The Graph Protocol for indexing on-chain data, smart contract integration with ethers.js, querying NFT metadata with OpenSea and IPFS, and DeFi applications with price feeds. Your GraphQL will touch the blockchain world!

Learn GraphQL - Building Collaborative Real-Time Apps | Learn GraphQL