Skip to content

Commit daa2496

Browse files
authored
docs: add schema design best practices part 1 blog (#7043)
1 parent fde24ee commit daa2496

File tree

13 files changed

+208
-0
lines changed

13 files changed

+208
-0
lines changed
281 KB
Loading
228 KB
Loading
212 KB
Loading
199 KB
Loading
275 KB
Loading
227 KB
Loading
206 KB
Loading
223 KB
Loading
268 KB
Loading
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
title: Proven Schema Designs and Best Practices - Part 1
3+
description: From a GraphQL Conf 2025 talk -- proven schema design approaches and patterns.
4+
date: 2025-10-06
5+
authors: [jdolle]
6+
tags: [graphql]
7+
---
8+
9+
GraphQL provides many benefits over other query languages. Federation builds on top of this
10+
foundation to provide even more flexibility and power. But even with all that GraphQL has to offer,
11+
API design remains difficult.
12+
13+
Over time, we've developed some proven design philosophies and patterns for easier schema
14+
migrations, exposing errors, and avoiding ambiguous or misleading type names.
15+
16+
## Schema Design Goals and Solutions
17+
18+
As you design your schema, keep in mind that:
19+
20+
> You can and should future proof your schemas as much as possible, but it’s okay for the schema not
21+
> to be perfect. Products change, requirements change. There will be chaos. But if we can tend to
22+
> our schemas, we can encourage them to grow and evolve organically -- much like a garden.
23+
24+
To better tend to our schemas, we can design with some goals in mind, follow some battle tested
25+
patterns, and approach type creation methodically.
26+
27+
### Flexible
28+
29+
The first goal is for your schema to be easy to change over time. As I mentioned before, entropy is
30+
a constant.
31+
32+
And if you anticipate and prepare for change then you can more easily adapt your schema to fit the
33+
needs of your consumers.
34+
35+
---
36+
37+
![Flexible](./flexible-01.jpg)
38+
39+
It may be unintuitive, but keeping your field arguments concise and inflexible is one way to help
40+
keep your schema as a whole more flexible.
41+
42+
So don’t overload your arguments or an input type with a bunch of optional ways of doing things. By
43+
keeping fields more focused, we limit the impact that any one change can have. And if we do need to
44+
break things, the blast radius to your API consumers is much smaller.
45+
46+
---
47+
48+
![Flexible](./flexible-02.jpg)
49+
50+
Overloading arguments can also hurt performance by requiring your resolver to do many more things
51+
before returning.
52+
53+
So it’s best to keep your mutations focused on a specific task.
54+
55+
---
56+
57+
![Flexible](./flexible-03.jpg)
58+
59+
Here we have two examples of User types, but on the left is only what your consumer NEEDS, and on
60+
the right maybe is everything you know about a user. It should be obvious that the left is much
61+
easier to maintain.
62+
63+
Unnecessary fields often go unused or worse, they may cause technical debt because the format of the
64+
data ends up needing changes because it doesn't fit with the new requirements.
65+
66+
---
67+
68+
![Flexible](./flexible-04.jpg)
69+
70+
Tooling such as Hive Console can also help. We offer a feature called “Conditional Breaking Changes”
71+
that checks usage data on breaking changes. And if the portion of the schema that broke isn’t used,
72+
then the change is allowed. Otherwise the change is still flagged as breaking.
73+
74+
This lets you safely migrate your schema and eliminates the need to manually check usage when making
75+
breaking changes.
76+
77+
### Accessible & Efficient
78+
79+
Another goal is that traversing the graph should be quick – with as few hops as reasonably possible.
80+
81+
This makes the API more client friendly and reduces strain on your system from too many lookups or
82+
connections.
83+
84+
---
85+
86+
![Accessible](./accessible-01.jpg)
87+
88+
A proven way of doing this is by using a common “Node” interface for every entity type. But so long
89+
as you expose a way to access related types, then that's what's important.
90+
91+
---
92+
93+
![Accessible](./accessible-02.jpg)
94+
95+
Another way to make your API more accessible is to better support partial results.
96+
97+
Returning partial data can dramatically improve a user’s experience for example if your system has a
98+
partial outage. Keeping root query fields nullable ensures any one field can’t break the entire
99+
client operation.
100+
101+
---
102+
103+
### Safe & Secure
104+
105+
To have a secure schema means to avoid leaking sensitive information, but also not being vulnerable
106+
to attacks. One major vulnerability of vanilla GraphQL is Denial of Service attacks.
107+
108+
There’s no substitution for setting reasonable request timeouts. But tooling that restricts the size
109+
of requests or that rate limits requests are also necessary for preventing DoS attacks
110+
111+
---
112+
113+
![Secure](./secure-01.jpg)
114+
115+
GraphQL Armor is a popular tool that has been integrated into most server implementations. It allows
116+
you to restrict operations with a number of configuration options.
117+
118+
---
119+
120+
![Secure](./secure-02.jpg)
121+
122+
There’s also a few ways of designing your schema to limit attacks by limiting response sizes.
123+
124+
This schema shows the Relay Pagination Spec. Using pagination limits the amount load from any one
125+
field. There are also other pagination specs that are equality suited.
126+
127+
Pagination is important to reduce the potential for DDOS attacks and to increase responsiveness of
128+
your API, regardless of rate limiting or other protections.
129+
130+
However, if you know your data is guaranteed to have a small upper bound, then you can get away with
131+
a simple array. Pagination adds a lot of complexity which is nice to avoid it if you can.
132+
133+
---
134+
135+
![Secure](./secure-03.jpg)
136+
137+
Also don’t place Binary Files or other large response fields in the Schema. Leverage CDNs and other
138+
purpose built solutions meant for dealing with these files.
139+
140+
---
141+
142+
### Interactive
143+
144+
Another goal is for the schema to expose clear ways to interact with it. This may seem obvious, but
145+
many times edge cases such as errors are forgotten or ignored.
146+
147+
---
148+
149+
![Interactive](./interactive-01.jpg)
150+
151+
Placing your expected errors in the schema is incredibly valuable for consumers of your API. This
152+
lets your API’s consumers know what can go wrong and potentially how to fix the issues.
153+
154+
This is sometimes called “typed errors” or “errors as data” or sometimes “expected errors”. But
155+
whatever you call it, be sure to add meaningful fields to your errors so that clients can show
156+
helpful information to users instead of a generic “Something went wrong” message.
157+
158+
However, don't move every possible intermittent failure into your schema. These errors are
159+
potentially numerous and are meaningless to the API consumer. Those should be thrown and masked in
160+
production to avoid leaking potentially sensitive information and to avoid forcing clients to handle
161+
these cases.
162+
163+
---
164+
165+
![Interactive](./interactive-02.jpg)
166+
167+
There are a few convenient ways to expose an error type in you schema. The first is to return a
168+
union of your success results and error results. This adds the minimal number of types to your
169+
schema, but relies on convention to separate the types. The other is to create a response type that
170+
has fields indicating whether or not the request was successful or errored.
171+
172+
One is convention and the other is codified, but otherwise there isn't much difference. Both are
173+
good.
174+
175+
---
176+
177+
### Consistent
178+
179+
Consistency is promoted by using patterns. And patterns make it easier for your team to develop
180+
because they know “how things are done”. This applies to both developing and consuming the schema.
181+
If everything is slightly different then it’s much harder to navigate because nothing is intuitive.
182+
183+
---
184+
185+
![Consistent](./consistent-01.jpg)
186+
187+
Consistency comes from implementing all of these things all the time. Tools like linters can help --
188+
particularly when implementing linting rules in a Schema Registry so that every schema is consistent
189+
with one another.
190+
191+
---
192+
193+
### Descriptive & Unambiguous
194+
195+
The last goal is to be descriptive and unambiguous. This means that our entities are accurately
196+
reflected in the schema using a shared language. Shared language is the common set of terms that are
197+
used to describe a thing such that there's no confusion about its identity. Simply put, shared
198+
language is what everybody calls something.
199+
200+
There are a number of approaches that can be used to group fields and determine your shared
201+
language. In part 2 of this article, we'll look at a few approaches and how they can be used to
202+
create a descriptive and unambiguous schema.
203+
204+
---
205+
206+
_This had been adapted from a talk given at GraphQL Conf 2025. A link will be added when the video
207+
is made available to the public on the
208+
[@GraphQLFoundation](https://www.youtube.com/@GraphQLFoundation) Youtube channel._

0 commit comments

Comments
 (0)