Using GraphQL gives simpler and lighter ways to manage APIs from the backend service if using correctly. One of the ways to use GraphQL correctly is to use GraphQL Dataloader.
What is Dataloader?
DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.
To understand the advantage of using Dataloader, let’s look at the simple example.
Example
Let's say that we have the following GraphQL type definitions.
Schema
type Query {
book(id: String!): Book!
authors: [Author]!
}
type Book {
id: ID!
title: String!
author: Author!
}
type Author {
id: ID!
name: String!
books: [Book!]!
}
Resolver
FYI
root — Result from the previous/parent type
args — Arguments provided to the field
context — a Mutable object that is provided to all resolvers
info — Field-specific information relevant to the query (used rarely)
Query: {
book: (root, args, context, info) => {
// using args.id to query data from db
},
authors: (root, args, context, info) => {
// query authors data from db
}
},
Book: {
author: (root, args, context, info) => {
// using args.id to query author data from db
}
},
Author: {
books: (root, args, context, info) => {
//using args.id to query books from db
}
}
As you see in the example above, Book and Author types pass their id to their related children node. In this case, Book’s child node is Author, and Author’s child node is multiple Book nodes.
By doing so, you don’t always have to access the author table even if the client doesn’t query the author in the book.
N+1 Problem and DataLoader
The n+1 problem means that the server executes multiple unnecessary round trips to datastores for nested data.
N+1 Problem Example
query {
books { # fetches books (1 query)
title
author { # fetches author for each book (N queries for N book)
name
}
}
} # N+1 round trips
In the above case, the server makes 1 round trip to a datastore to fetch the books, then makes N round trips to a datastore to fetch the author for N authors.
Using DataLoader
DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.
Setup Apollo Server with DataLoader
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => {
return {
authorLoader: new DataLoader(async keys => {
const authors = // query authors from database
const authorMap = {};
authors.forEach(author => {
authorMap[author.id] = author;
});
return keys.map(key => authorMap[key]);
})
};
}
});
Using defined Data Loader: You can grab defined DataLoader from the context
Book: {
author: async (root, args, context, info) => {
return context.authorLoader.load(root.id); // root of Book is Author
}
},
The basic idea is to create a batch to combine all fetching processes as one process and use the cached data for duplicated data.
Caching
Once .load()
is called, the result is cached and will be used to avoid the duplicated call for the value with the keys that the .load()
used.
Clearing Cache
Rarely there is a case when we need to clear the cache manually.
For example, when some values are updated with the same key within the same request, the cached value may be outdated and need to be cleared. In this case, we can simply use .clear()
or .clearAll()
.
For more information about Caching, please visit the GraphQL DataLoader GitHub page.
Using DataLoader with Authorization and Authentication
The request may be separated by the different tenant or the different credential, and their request may be mixed and produced the wrong response. Thus, we recommend creating a Data Loader instance per request.
For example, when using with express:
function createLoaders(authToken) {
return {
users: new DataLoader(ids => genUsers(authToken, ids)),
}
}
const app = express()
app.get('/', function(req, res) {
const authToken = authenticateUser(req)
const loaders = createLoaders(authToken)
res.send(renderPage(req, loaders))
})
app.listen()
Top comments (0)