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.