Back to docs
Recipe

GraphQL Schema Design

A practical primer on designing clean, performant, and maintainable GraphQL schemas for production APIs.

Start with the domain, not the database

Resist the urge to mirror your PostgreSQL tables one-to-one. GraphQL is a product-facing API layer. Model your types around how clients consume data — not how you store it. A single UserProfile type may hydrate from three different tables behind the scenes.

Prefer thin resolvers

Resolvers should delegate to a service layer immediately. Keep authentication, authorization, and business logic out of the resolver itself. A resolver's job is to extract arguments, call a function, and return the result — nothing more.

Connections over arrays

Any list that could grow unbounded — posts, comments, audit entries — should use the Relay Connection spec: edges, node, pageInfo. This gives you cursor-based pagination from day one and prevents painful migrations later.

Enums, not magic strings

Every status field, role, or category should be an enum type. GraphQL enums are self-documenting, introspectable, and give clients compile-time safety. Avoid string unions masquerading as flexibility — they rot silently.

Nullability is a contract

Mark a field non-null only when you can guarantee it will never be null for the lifetime of the API. Overusing ! creates brittle clients. When in doubt, leave it nullable and let the client decide how to handle absence.

Versioning through evolution

GraphQL does not need /v2 endpoints. Add fields freely. Deprecate old fields with the @deprecated directive and a reason string. Monitor usage, then remove after a communicated sunset window.

Ready to implement? Continue to the GraphQL implementation recipe for code-level patterns and DataLoader integration.