This document outlines an approach to designing REST API’s to optimise requests made from a GraphQL server in combination with DataLoader.
Entities should always include their own id
.
One-to-one relationships should be exposed through a field on the entity and never through denormalisation.
One/many-to-many relationships should not be exposed through an array on the entity, but instead through a List endpoint.
As the name suggests, these endpoints are for loading entities. The key component is their ability to batch up and load multiple entities in a single request; avoiding the n+1 problem. For any entity that exists in a system that should be exposed, a corresponding entity endpoint should be made available.
These endpoints must accept one or more id
query parameters, e.g., /book?id=1&id=2&id=3
.
Any entity that cannot be accessed, (e.g., permissions), should be filtered from the response, rather than resulting in an error.
List endpoints produce lists of entities. Any relationships that exist should be exposed through an id
and not through denormalisation. For example, a list of books (/books
):
[
{
id: "7c14883b",
authorId: "a9bf4d69",
title: "Book One"
},
{
id: "e11cc805",
authorId: "681c56ba",
title: "Book Two"
},
...
]
The relationships from books to authors can then be connected through a single batched request to the /author
entity endpoint, e.g., /author?id=1&id=2
.
Another important consideration of a List endpoint is that it should always return the exact same set of fields as its corresponding Entity endpoint. That way, the related Entity DataLoader cache can be primed.
Ideally all List endpoints should support filtering and pagination to maintain consistency across the REST API as well as the GraphQL schema.
List and Entity endpoints should not be combined into a single endpoint as pagination and batching parameters are incompatible.