Writes and Real-Time
So far the graph has been read-only. We have selected fields, traversed links, and resolved values — but never changed anything. A real API has to let clients do things: post a review, archive a track, follow an artist. And once data can change, clients often want to know the moment it does. Those two needs map onto the two remaining root types: Mutation for writes, and Subscription for the stream of changes that follow.
Two new doorways
Section titled “Two new doorways”You already met all three root types in the foundations module. Here we put the second and third to work:
Mutation— the write entry point. A field here represents one intentional change: create something, update it, delete it. It runs the change and then returns data describing the result.Subscription— the real-time entry point. A field here opens a long-lived stream: the client subscribes once, and the server pushes a new payload every time the relevant event fires.
The mental model is a request that acts (mutation) versus a connection that listens (subscription). A mutation is one round trip — send the change, get the result back. A subscription is an open channel — one request, many results delivered over time.
flowchart LR
subgraph Read
Q["Query"] --> R1["one result"]
end
subgraph Write
M["Mutation"] --> S["mutable store"]
S --> R2["result of the change"]
end
subgraph RealTime
Sub["Subscription"] --> Ev["event stream"]
Ev --> P1["payload"]
Ev --> P2["payload"]
Ev --> P3["payload …"]
end What a mutation looks like
Section titled “What a mutation looks like”A mutation field is declared on the Mutation type. It usually takes arguments describing the change and returns the affected object so the client can update its view without a second read:
type Mutation { addReview(trackId: ID!, rating: Int!, body: String): Review!}Calling it looks almost like a query — the difference is that it starts at mutation instead of { (the implicit query), and the server is allowed to change state:
mutation { addReview(trackId: "t1", rating: 5, body: "On repeat all week.") { id rating body }}What this module covers
Section titled “What this module covers”Across the next four lessons we go from “it works” to “it scales”:
- Mutation design — naming writes by intent, keeping one mutation per logical change, and returning the affected data.
- The input / payload pattern — wrapping arguments in a single
inputobject and returning a structuredpayloadso mutations can evolve and carry per-field errors. - Subscriptions — modelling real-time as a stream of events with
subscribeandresolve, the transports that carry them, and when not to reach for them. - Mutation best practices — idempotency, validation, user errors versus protocol errors, chattiness, and optimistic UI.
A mutation, run for real
Section titled “A mutation, run for real”The example below builds a tiny schema with a Mutation type, a mutable in-memory store of reviews, and a resolver that appends to it. Press Run to add a review and watch the store grow — the second query reads back exactly what the mutation wrote.
Notice the shape of the result. The mutation returns the single Review it created, complete with its server-assigned id — so the client never has to guess. The follow-up read proves the write landed: the store that was empty now contains exactly one review. That round trip — act, then receive the result — is the rhythm of every mutation in this module.