GraphQL in Production · GraphQL Fundamentals for API Designers
Designing a GraphQL Schema
Types, nullability, IDs, naming and modeling the domain for clients.
Design the schema around client use cases and domain language, not database tables. Use globally unique IDs (ID!, often base64 Type:id or prefixed IDs) to enable refetching (node(id:)) and client caches. Be deliberate with nullability: non-null (!) is a promise — if a non-null field fails, the null bubbles up to the nearest nullable parent. Prefer nullable fields for data from unreliable dependencies, non-null for guaranteed data. Use enums for closed sets, custom scalars (DateTime, Money) for formats, and descriptions everywhere (they become docs).
Nullability guidance
| Choice | Use non-null (!) when | Use nullable when |
|---|---|---|
| Scalar fields | Always present in your own DB | Optional data or computed from other services |
| Object references | Same service, guaranteed | Cross-service / may fail / permission-dependent |
| Lists | [Item!]! — list and items always exist | Items may be individually unresolvable |
| Arguments | Required inputs | Optional filters with defaults |
"""A customer order."""
type Order implements Node {
id: ID!
number: String!
status: OrderStatus!
total: Money!
createdAt: DateTime!
customer: Customer # nullable: customer service may be unavailable
items(first: Int = 20, after: String): OrderItemConnection!
}
enum OrderStatus { PENDING PAID SHIPPED CANCELLED }
type Money { amount: Int! currency: String! } # minor units
interface Node { id: ID! }
type Query { node(id: ID!): Node order(id: ID!): Order viewer: User }
Common pitfalls
- Everything non-null: one failing resolver nulls out the whole response.
- Exposing database IDs and join tables directly.
- Generic
JSONscalars that defeat typing.
Key points
- Model for clients, not tables
- Global IDs + Node interface
- Nullability is a contract
Further reading: GraphQL: Schemas and types
Practice questions
What happens when a non-null field's resolver throws?
Answer
The field can't be null, so null propagates to the nearest nullable parent field (possibly the entire data), and an error is added to errors.
Why do client caches like Apollo and Relay want globally unique IDs?
Answer
They normalize objects by type+id so the same entity fetched by different queries is stored once and updates propagate everywhere.
Open in the app to mark lessons complete, take notes and track your progress.