At Bearer, we use GraphQL to allow our Dashboard application to communicate with our database. Recently we gave this GraphQL API a re-design. Here are a couple of lessons we learned during the process.
Lesson one: Serve the user, not your database
“[GraphQL] is a query language for your API”.
This bears repeating: GraphQL is a query language for your API. When consumers want to talk to your API, they’ll be doing so in GraphQL.
The primary focus of your GraphQL API is the needs of your API consumers. Unlike other query languages such as SQL, GraphQL does not care that much about your database structure. This may sound trivial in theory, but it is surprisingly tempting in practice to take a database-first approach when designing a GraphQL API.
To give a simplistic example, let’s imagine we are building a profile website for dogs. Our UI contains the dog’s name, date of birth, and favorite toy.
![Example Dog UI component](https://cdn.prod.website-files.com/61570a290fb0e042e260602f/61818965e61b04ad44135892_dog-ui-example.png)
Our database looks something like this:
dogs name: String birthdate: Date toys dog_id: Int description: String preference: Int
A dog has a name, a birthday, and can have any number of toys. Each toy has a preference, meaning that each dog with one or more toys has a “favorite toy”.
In Rails, this would look something like:
class dog < ApplicationRecord has_many :toys def favourite_toy toys.order(preference: :desc).first end end class toy < ApplicationRecord belongs_to :dog end
Modeling our GraphQL API directly against this database would give us a Dog type that looks something like this:
Lesson two: Say no to null! Embrace interfaces
Let’s imagine we want to show additional information on our dog profile:
For rescue dogs, we want to show their adoption date and adoption shelter.
For show dogs, we want to show their full, registered name.
We can expand our GraphQL API to accommodate this. Since the new data does not apply to all dogs, we add the additional fields as nullable fields:
type Dog { id: ID! name: String! birthDate: DateTime! favoriteToy: Toy! adoptionDate: DateTime adoptionShelter: String registeredName: String }
These nullable fields allow us to represent different kinds of dogs within a single Dog type. A schema like this keeps our API simple, but this simplicity can come at a cost. Because we are representing all dogs under one Dog type, information about the different kinds of dogs becomes implicit.
Our API, for example, does not tell us about the two subsets of dogs available, rescue and show, nor does it tell us that only show dogs would have registered names, and that rescue dogs have adoption dates and shelters. It expects its consumers to know what kinds of dogs (rescue dogs, show dogs) are represented and what fields are associated with which kinds of dogs.
Moreover, we cannot express which fields are mandatory for which kinds of dogs. Can we expect that every rescue dog has an adoption date and an adoption shelter, or do only some rescue dogs have their shelter name recorded?
Lastly, in its current form, our schema makes no guarantee against returning nonsensical data like the following:
Conclusion
These are only a couple of insights we gained from re-designing our GraphQL API, and some things to keep in mind when building your own. We’d love to hear your thoughts on GraphQL, and any lessons you’ve got to share from your own experience building and working with GraphQL APIs.