GraphQL Design Patterns
A practitioner's guide to designing GraphQL schemas that scale with your product. This recipe walks through three patterns we use across Meridian: relay-style connections, mutation payload shape, and federated entity ownership. Each pattern is paired with a runnable snippet you can drop into your gateway today.
1. Relay-style connections
Pagination is the first decision that locks your schema in. Use cursor-based connections from day one. Offset pagination breaks under concurrent writes and forces clients to refetch when items shift. Relay connections give you a stable cursor, edge metadata, and a path to bidirectional traversal without a breaking change.
type RecipeConnection {
edges: [RecipeEdge!]!
pageInfo: PageInfo!
}
type RecipeEdge {
cursor: String!
node: Recipe!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}2. Mutation payload shape
Return a payload object from every mutation, never the bare entity. The payload wraps the entity plus a list of user-facing errors. This lets you surface validation failures without throwing a GraphQL error, which keeps your transport clean and your UI logic local. Add a clientMutationId field when you need optimistic UI reconciliation.
The payload pattern also makes future fields cheap. When you later want to return a side-effect entity, a metric, or a feature flag, add it to the payload without touching existing clients.
3. Federated entity ownership
One service owns each entity. Other services extend it. This rule sounds obvious and is violated every sprint. Codify ownership in your federation manifest and reject PRs that add resolvers to non-owned entities. Use the @key directive to pin the primary key, and @external for fields you only read.
When ownership is unclear, the entity is too coarse. Split it. A Recipe owned by the catalog service and a RecipeRating owned by the social service is cleaner than a single Recipe with mixed ownership.