Skip to content
AITroveRead. Build. Understand.
Make this comfortable

GraphQL Mutation Errors and Pagination Contracts

Last updated: 5 Oct 20266 min read
tutorial
IntermediateBy AITrove Editorial

A GraphQL mutation expresses a write in a typed schema, but it still needs the same server transaction, authorization, idempotency, and version checks as any other API. Expected outcomes such as a stale revision or validation failure can be represented as typed result variants; unexpected execution faults belong in the response error channel with safe messages. GraphQL responses may contain both data and errors, so clients must not equate a successful HTTP status with a successful domain action. List reads need stable pagination rules, usually a cursor tied to a deterministic sort and filters, rather than an unbounded array hidden behind a field.

Working case

Reviewer 29 edits case 47 from revision 6 to 7 through updateCase. The mutation takes an idempotency key and expected revision, checks current case permission, then commits one change and outbox event. A repeated request with the same key returns the original result. Reviewer 62 attempts the same case and gets a safe denied outcome. A stale reviewer request receives a typed conflict with current revision metadata allowed by policy. The case activity connection returns 23 rows sorted by immutable event time and ID, plus an opaque end cursor. A later page continues from that position without repeating rows even when new activity arrives.

Implementation boundary

javascript
function mutationOutcome(expectedRevision, currentRevision) {
  return expectedRevision === currentRevision ? "applied" : "conflict";
}
console.log(mutationOutcome(6, 7));
// Output: conflict

Validate input shape and business rules before mutation, then execute authorization, optimistic concurrency, write, and side-effect intent in one transaction. Scope idempotency keys to actor and operation. Model expected validation and conflict results explicitly if clients need field-level handling; use execution errors for faults and avoid private internal messages. A client should inspect both the returned result and errors, and should not blindly retry every mutation after a timeout. For a connection, fix sort order and filters, cap page size, and encode the position in an opaque cursor with integrity protection when its contents need trust. Recheck authorization on each page. Document what happens when items are inserted or deleted during pagination.

Cost and boundaries

A versioned mutation is O(1) indexed record work plus validation and any outbox writes; idempotency storage adds one keyed result. Keyset pagination can keep page lookup near O(log n plus k) for k returned rows with a suitable index, whereas deep offset pagination can grow with skipped rows. An opaque cursor adds encode and verification work but prevents clients from relying on internal IDs. Typed result variants add schema surface yet make expected recovery explicit. Measure conflict rate, duplicate mutation attempts, page gaps or repeats, and client behavior when a response contains data plus errors.

Failure trace

The client sees HTTP 200 and clears its edit form even though updateCase returned a stale-revision result. Handle the typed result and preserve user input for comparison. Another resolver writes the case, then fails before queuing its notice; a retry writes again because no idempotency key was recorded. Commit intent and result together. A paginated activity field sorts only by timestamp, so simultaneous events share a cursor position and one disappears. Add an immutable tie-breaker. Test duplicated requests, timeout after commit, revoked access, stale revision, partial errors, equal timestamps, inserted rows, and changed filters between pages.

Verification

  • HTTP success does not hide a domain conflict.
  • A repeated idempotent mutation has one effective write.
  • Cursor pages use a stable sort and bounded size.

Practice drill

Update case 47 from revision 6 using one idempotency key, repeat the same mutation, and verify one write and one outbox item. Submit a stale revision and inspect the typed conflict without losing the client draft. Run an activity query with 23 records, including equal timestamps, across several bounded pages. Insert a newer event between pages and confirm the continuation rule. Change the filter while reusing a cursor and reject or restart according to the documented cursor scope.

Decision note

Treat GraphQL writes and pages as domain contracts with explicit retries, errors, authorization, and stable continuation.

Common Mistakes

  • Treating every GraphQL response with data as full success.
  • Retrying a timed-out write with a new identity.
  • Using a timestamp-only cursor when values can tie.

Connected lessons

GraphQL Query and Resolver Boundaries; GraphQL Schema Nullability and Evolution; GraphQL Resolver Batching and Tenant Scope; GraphQL Operation Cost and Admission; Idempotent Write Requests and Lost Responses; Optimistic Mutations and Rollback; Cursor Pagination for Changing Collections.

Apply and check

Build Project: tenant-safe GraphQL case API and review Web Development: release and GraphQL decisions quiz.

web-tech
web-development
Storage details