graphql

Verified·Scanned 2/18/2026

Design GraphQL schemas and resolvers with proper performance, security, and error handling.

from clawhub.ai·v25002bc·4.1 KB·0 installs
Scanned from 1.0.0 at 25002bc · Transparency log ↗
$ vett add clawhub.ai/ivangdavila/graphql

N+1 Problem (Always Forget)

  • Each resolver runs independently—fetching user for each of 100 posts = 100 queries
  • DataLoader required: batches requests within single tick—100 posts = 1 user query
  • DataLoader per-request: create new instance per request—prevents cross-request caching
  • Even with DataLoader, watch for nested N+1—posts → comments → authors chains

Schema Design

  • Fields nullable by default—make non-null explicit: name: String!
  • Input types separate from output—CreateUserInput vs User; allows different validation
  • Connections for pagination: users(first: 10, after: "cursor") returns edges + pageInfo
  • Avoid deeply nested types—flatten where possible; 5+ levels = resolver complexity

Pagination

  • Cursor-based (Relay style): first/after, last/before—stable across insertions
  • Offset-based: limit/offset—simpler but skips or duplicates on concurrent writes
  • Return pageInfo { hasNextPage, endCursor }—client knows when to stop
  • totalCount expensive on large datasets—make optional or estimate

Security (Often Ignored)

  • Query depth limiting—prevent { user { friends { friends { friends... } } } }
  • Query complexity scoring—count fields, multiply by list sizes; reject above threshold
  • Disable introspection in production—or protect with auth; schema is attack surface
  • Timeout per query—malicious queries can be slow without being deep
  • Rate limit by complexity, not just requests—one complex query = many simple ones

Error Handling

  • Partial success normal—query returns data AND errors; check both
  • Errors array with path—shows which field failed: "path": ["user", "posts", 0]
  • Error extensions for codes—"extensions": {"code": "FORBIDDEN"}; don't parse message
  • Throw in resolver = null + error—parent nullable = partial data; parent non-null = error propagates up

Resolver Patterns

  • Return object with ID, let sub-resolvers fetch details—avoids over-fetching at top level
  • __resolveType for unions/interfaces—required to determine concrete type
  • Context for auth, DataLoaders, DB connection—pass through resolver chain
  • Field-level auth in resolvers—check permissions per field, not just per query

Mutations

  • Return modified object—client updates cache without re-fetch
  • Input validation before DB—return user-friendly error, not DB constraint violation
  • Idempotency for critical mutations—accept client-generated ID or idempotency key
  • One mutation per operation typically—batch mutations exist but complicate error handling

Performance

  • Persisted queries: hash → query mapping—smaller payloads, prevents arbitrary queries
  • @defer for slow fields—returns fast fields first, streams slow ones (if supported)
  • Fragment colocation: components define data needs—reduces over-fetching
  • Query allowlisting: only registered queries in production—blocks exploratory attacks

Subscriptions

  • WebSocket-based—graphql-ws protocol; separate from HTTP
  • Scaling: pub/sub needed—Redis or similar for multi-server broadcast
  • Filter at subscription level—don't push everything and filter client-side
  • Unsubscribe on disconnect—clean up resources; connection tracking required

Client-Side

  • Normalized cache (Apollo, Relay)—deduplicate by ID; updates propagate automatically
  • Optimistic UI: predict mutation result—rollback if server differs
  • Error policies: decide per-query—ignore errors, return partial, or treat as failure
  • Fragment reuse—define once, use in multiple queries; keeps fields in sync

Common Mistakes

  • No DataLoader—N+1 kills performance; one query becomes hundreds
  • Exposing internal errors—stack traces leak implementation details
  • No query limits—attackers craft expensive queries; DoS with single request
  • Over-fetching in resolvers—fetching full object when query only needs ID + name
  • Treating like REST—GraphQL is a graph; design for traversal, not resources