DEV Community

Olivia Craft
Olivia Craft

Posted on

CLAUDE.md for GraphQL: 13 Rules That Stop AI from Writing REST Patterns in Your Schema

GraphQL has its own mental model: a schema defines the contract, resolvers handle the data, clients ask for exactly what they need. AI tools that treat GraphQL like REST produce schemas that fight the framework at every level.

The most common failure modes: REST-style mutations that return nothing useful, overfetching queries that ignore fields, schemas that duplicate database column names instead of expressing domain concepts, and missing type safety at the resolver layer.

A CLAUDE.md file that declares the GraphQL model prevents these patterns from appearing. Here are 13 rules.


Rule 1: Schema is the source of truth — not the database

The GraphQL schema defines the API contract independently
of the database schema. Field names should reflect domain
concepts, not database column names.
Do NOT mirror the database tables 1:1 in the GraphQL schema.
Types should represent business entities, not storage shapes.
Enter fullscreen mode Exit fullscreen mode

Rule 2: Mutations return meaningful types

Every mutation must return a meaningful result type, not Boolean
or a simple success flag:
  type CreateUserPayload { user: User! errors: [UserError!]! }
Do NOT return Boolean or null from mutations.
Return union types for error handling at the type level.
Enter fullscreen mode Exit fullscreen mode

Rule 3: Queries are fields, not endpoints

Think in fields, not endpoints.
Bad: getUserById(id: ID!): User  REST thinking
Good: user(id: ID!): User on the Query type
Each query field resolves to a type; the type resolves its fields.
Do NOT name query fields like REST endpoints.
Enter fullscreen mode Exit fullscreen mode

Rule 4: Use fragments for reusable field sets

Repeated field selections should be extracted into fragments.
  fragment UserBasic on User { id name email }
Use fragments on the client; do NOT inline the same fields
on every query that needs the same data shape.
Enter fullscreen mode Exit fullscreen mode

Rule 5: Connections for paginated lists

For paginated collections, use the Relay connection pattern:
  type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! }
  type UserEdge { node: User! cursor: String! }
Do NOT return [User!]! directly for lists that will be paginated.
Include totalCount in connections when the UI needs it.
Enter fullscreen mode Exit fullscreen mode

Rule 6: DataLoader for N+1 prevention

All resolver functions that load related entities MUST use
DataLoader for batching. Do NOT call the database directly
inside a resolver that may run per-item in a list.
Every field that resolves a relationship needs a DataLoader.
Enter fullscreen mode Exit fullscreen mode

Rule 7: Input types for mutations

All mutation arguments use input types:
  input CreateUserInput { name: String! email: String! }
  createUser(input: CreateUserInput!): CreateUserPayload!
Do NOT use individual scalar arguments for mutations with
more than 2 fields. Input types are required for nullable
and optional fields on mutations.
Enter fullscreen mode Exit fullscreen mode

Rule 8: Enums for fixed value sets

Status fields, category fields, and any finite value set
use GraphQL enums:
  enum UserStatus { ACTIVE SUSPENDED DELETED }
Do NOT use String for fields that have a fixed set of values.
Enum values use SCREAMING_SNAKE_CASE.
Enter fullscreen mode Exit fullscreen mode

Rule 9: Non-null discipline

Apply non-null (!) based on business rules, not optimism.
- IDs are always non-null
- Required fields that can never be absent: non-null
- Optional fields that may not be set: nullable
Do NOT make everything non-null to silence TypeScript warnings.
Nullable means "this field may not exist in valid business states."
Enter fullscreen mode Exit fullscreen mode

Rule 10: Error handling at the type level

Use union types for operations that can fail:
  union CreateUserResult = User | ValidationError | AuthError
Or use the payload pattern with an errors array (see Rule 2).
Do NOT use exceptions as the primary error communication path.
The schema should express failure modes as valid types.
Enter fullscreen mode Exit fullscreen mode

Rule 11: Subscriptions for real-time data

Real-time data uses GraphQL subscriptions, not polling.
  subscription OnUserUpdated($id: ID!) { userUpdated(id: $id) { ...UserBasic } }
Do NOT implement polling queries when subscriptions can serve
the use case with a subscription-capable transport.
Enter fullscreen mode Exit fullscreen mode

Rule 12: Resolver structure follows the schema

Resolvers are organized to mirror the schema structure:
  Query resolvers → query field resolvers
  Mutation resolvers → mutation field resolvers
  Type resolvers → field-level resolvers for each type
Do NOT put all business logic in Query resolvers.
Each type should have its own resolver map.
Enter fullscreen mode Exit fullscreen mode

Rule 13: Type generation is mandatory

All resolver types, query variables, and response shapes
are generated from the schema (via GraphQL Code Generator
or equivalent). Do NOT write resolver type signatures
by hand — generate them from the schema.
Types: generated; Business logic: handwritten.
Enter fullscreen mode Exit fullscreen mode

The full CLAUDE.md block

# GraphQL Project Rules

## Schema Design
- Schema expresses domain concepts, not database columns
- Mutations return meaningful payload types (never Boolean)
- Query fields named as domain concepts, not REST endpoints
- Enums for all fixed value sets (SCREAMING_SNAKE_CASE)
- Non-null (!) only where business rules require it

## Pagination
- Relay connection pattern for paginated lists
- [Type!]! only for guaranteed-complete non-paginated lists
- Include totalCount in connections when needed

## Resolvers
- DataLoader required for all relationship resolvers (N+1)
- Resolver map mirrors schema structure (Query/Mutation/Types)
- Business logic in services, not in resolvers

## Mutations
- Input types required for mutations with >2 fields
- Return union types or payload with errors array
- Error handling at the type level, not via exceptions

## Types
- Generated from schema (GraphQL Code Generator or equivalent)
- Never write resolver type signatures by hand

## Real-time
- Use subscriptions, not polling queries
Enter fullscreen mode Exit fullscreen mode

Why GraphQL AI drift is predictable

REST is the default mental model for API design. Every tutorial, every framework, every Stack Overflow answer about "how do I build an API" reaches for REST. GraphQL is a fundamentally different model — declare what you want, not what endpoint to call.

Without explicit instruction, AI produces REST-shaped GraphQL: mutations that return Boolean, queries named like endpoints, no DataLoader, manual type signatures. The schema becomes a thin wrapper over REST instead of a typed graph.

The CLAUDE.md file declares the GraphQL model before any schema is generated.


These 13 rules are part of a framework-specific CLAUDE.md series. The Cursor Rules Pack equivalent (50 production-tested rules across all stacks) is available at oliviacraftlat.gumroad.com/l/wyaeil — search "Olivia Craft Cursor Rules" on Gumroad.

Top comments (0)