Design APIs Using GraphQL
Learn how to design API using GraphQL API.
Making a good API design is a challenging task that rewards iteration, experimentation, and a thorough understanding of the business domain. For this lesson, let’s imagine that we work at a next-generation pizza company. We have an existing GraphQL API for an existing feature for the current business model. We now want to introduce a new feature. On the backend, the new feature requires the following implementations:
- All pizzas must have essential attributes like
pizza
anddescription
that indicate whichtopping
orsauce
they will be served with. - Some types of pizza must have customizable unique attributes. For example, we should be able to customize the
sauce
in a Neapolitan pizza and thecrust
in a St. Louis pizza.
With these requirement fulfilled, we can start thinking about the API design we’ll use to introduce the new feature.
Helicopter view
A naive version of the schema based on the listed requirements might look something like this:
type Pizza {id: Int!pizza: String!description: String!}type NeapolitanPizza {id: Int!pizza: String!description: String!sauce: String!}type StLouisPizza{id: Int!pizza: String!description: String!crust: String!}
It’s already complicated even though it only has five objects. The disadvantage of using this schema is that if the business offers more pizza types in the future, we would need additional objects even though there are only one or two attributes that change.
A GraphQL API can contain many objects with dozens of fields and can relate with other objects. Building an API this way is a recipe for mistakes. Instead, it’s much better to start with a higher-level view. We should focus exclusively on the types and their relations without concerning ourselves with specific fields or mutations. We can think of it as a GraphQL-specific entity-relationship model . If we shrink the scope of our schema, we end up with the following:
interface IPizza {id: Int!pizza: String!description: String!crust: [Crust!]sauce: [Sauce!]}type Sauce {id: Int!name: String!description: String!}type Crust {id: Int!name: String!description: String!}type Pizza implements IPizza{id: Int!pizza: String!description: String!crust: [Crust!]sauce: [Sauce!]}
To get this simplified representation, there are a few details we need to watch out for.
-
We need to look for common attributes in each pizza type and try to create a suitable interface. With an interface, we can be sure that every type of pizza we potentially create in the future is consistent and predictable.
-
Most pizzas either have a thin or a thick crust and a sauce, usually tomato. So, it’s a good idea to separate crust and sauce data into their own objects (
Crust
andSauce
type) and then connect them to the pizza object. ...