GraphQL
by @ivangdavila
Design GraphQL schemas and resolvers with proper performance, security, and error handling.
clawhub install graphql
📖 About This Skill
name: GraphQL
slug: graphql
version: 1.0.1
description: Design GraphQL schemas and resolvers with proper performance, security, and error handling.
metadata: {"clawdbot":{"emoji":"◈","os":["linux","darwin","win32"]}}
Quick Reference
| Topic | File |
|-------|------|
| Schema design patterns | schema.md |
| Security and limits | security.md |
| Performance optimization | performance.md |
| Client-side patterns | client.md |
N+1 Problem (Critical)
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 chainsSchema 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 complexityPagination
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 estimateSecurity Traps
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 onesError 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 upResolver 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 queryMutations
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 handlingPerformance
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 attacksSubscriptions
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 requiredClient-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 syncCommon 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