GraphQL interface types are a powerful construct. At its core, an interface type exposes a certain set of fields that a type must respond to if it implements the interface. This lets us create fields of interface types, meaning that the objects returned by the fields could be any object type that implements the interface.
This sounds great, but the spec doesn’t (and shouldn’t 🙃) tell us when to use interface types. I’ve been noticing a trend in schemas I see recently, where interfaces are used for what seems purely field sharing / DRY’ing code up. This seems especially true for schemas built using libraries that auto-implement the interface fields when implementing an interface. If the schema is written using the SDL, this is probably less tempting.
Using interfaces purely because two or more object types share similar fields might be tempting, but we must really think of what it means for these object types to implement a common interface before doing so. I’m a huge proponent for modeling our schema by looking at the behavior of our concepts instead of looking directly at their attributes or data. (I wrote something on Anemic Mutations that talks about this concept with regard to GraphQL mutations).
A good interface should mean something to the API consumers. It describes and provides a common way to do or behave like something instead of being or having something. To be perfectly fair, it’s a bit hard sometimes to tell the difference with a GraphQL Schema. To know if our interface truly serves a purpose other than just sharing a bunch of fields, we must look at the whole schema and try to see if the interface fits in the possible interactions with the types, for example looking at the mutations that could mutate these concepts.
So how do we know if our interfaces are too focused on “categorizing” objects, grouping similar attributes, and not focused enough on interactions and behaviors? One quick smell is naming. When we use interfaces and they don’t actually have strong meaning in the schema, the naming will usually be awkward and meaningless. A common example of this include having the word Interface in the type name. For example, ItemInterface which would share common fields to an item in an e-commerce domain. As our Cart items, a Checkout items, an Order items start drifting apart, this interface might become a pain to maintain since it sounds like it was designed purely in terms of what common data they had. If we had looked at all sort of items and determined they have very different interactions and behaviors inside our d domain, maybe we would’ve picked a different abstraction. I’ve seen other namings such as ItemFields or ItemInfo being used in the same way.
Related to naming, with interfaces like these, you’ll find that fields of those interface types are sometimes pretty confusing. For example:
interface ItemInfo {
price: Money!
taxes: Money!
}
type Query {
items: [ItemInfo!]!
}
Our items field here returns ItemInfo types 🤔 it’s really hard to know what we can do with these types, and our resulting schema is fairly hard to reason about.
Last thing: If the GraphQL library you are using implements the interface fields for you in all object types, be careful not to use this mechanism to share code 😨. Sharing code is an internal concern that your API consumers don’t need to care about. Use composition, inheritance or just *write everything twice *to share similar code between object types instead of piggy backing GraphQL concepts to achieve the same!
Next time you want to add an interface type to your GraphQL schema, ask yourself if it brings any value to the design of your schema. Is it describing common interactions and behaviors, or describing internal state / common fields only?
Thanks for reading! 💚 When do you usually use interfaces? If you’ve enjoyed this post, I’m working on a book that will cover a bunch of other GraphQL schema design tips: https://book.graphqlschemadesign.com/