Dataloader plugin
This plugin makes it easy to add fields and types that are loaded through a dataloader.
Usage
Install
To use the dataloader plugin you will need to install both the dataloader
package and the Pothos
dataloader plugin:
Setup
loadable objects
To create an object type that can be loaded with a dataloader use the new builder.loadableObject
method:
It is VERY IMPORTANT to return values from load
in an order that exactly matches the order of
the requested IDs. The order is used to map results to their IDs, and if the results are returned in
a different order, your GraphQL requests will end up with the wrong data. Correctly sorting results
returned from a database or other data source can be tricky, so there this plugin has a sort
option (described below) to simplify the sorting process. For more details on how the load function
works, see the dataloader docs.
When defining fields that return User
s, you will now be able to return either a string
(based in
ids param of load
), or a User object (type based on the return type of loadUsersById
).
Pothos will detect when a resolver returns string
, number
, or bigint
(typescript will
constrain the allowed types to whatever is expected by the load function). If a resolver returns an
object instead, Pothos knows it can skip the dataloader for that object.
loadable fields
In some cases you may need more granular dataloaders. To handle these cases there is a new
t.loadable
method for defining fields with their own dataloaders.
loadableList fields for one-to-many relations
loadable
fields can return lists, but do not work for loading a list of records from a single id.
The loadableList
method can be used to define loadable fields that represent this kind of
relationship.
loadableGroup fields for one-to-many relations
In many cases, it's easier to load a flat list in a dataloader rather than loading a list of lists.
the loadableGroup
method simplifies this.
Accessing args on loadable fields
By default the load
method for fields does not have access to the fields arguments. This is
because the dataloader will aggregate the calls across different selections and aliases that may not
have the same arguments. To access the arguments, you can pass byPath: true
in the fields options.
This will cause the dataloader to only aggregate calls for the same "path" in the query, meaning all
calls share the same arguments. This will allow you to access a 3rd args
argument on the load
method.
dataloader options
You can provide additional options for your dataloaders using loaderOptions
.
See dataloader docs for all available options.
Manually using dataloader
Dataloaders for "loadable" objects can be accessed via their ref by passing in the context object for the current request. dataloaders are not shared across requests, so we need the context to get the correct dataloader for the current request:
Errors
Calling dataloader.loadMany will resolve to a value like (Type | Error)[]
. Your load
function
may also return results in that format if your loader can have parital failures. GraphQL does not
have special handling for Error objects. Instead Pothos will map these results to something like
(Type | Promise<Type>)[]
where Errors are replaced with promises that will be rejected. This
allows the normal graphql resolver flow to correctly handle these errors.
If you are using the loadMany
method from a dataloader manually, you can apply the same mapping
using the rejectErrors
helper:
(Optional) Adding loaders to context
If you want to make dataloaders accessible via the context object directly, there is some additional setup required. Below are a few options for different ways you can load data from the context object. You can determine which of these options works best for you or add you own helpers.
First you'll need to update the types for your context type:
next you'll need to update your context factory function. The exact format of this depends on what graphql server implementation you are using.
Now you can use these helpers from your context object:
Using with the Relay plugin
If you are using the Relay plugin, there is an additional method loadableNode
that gets added to
the builder. You can use this method to create node
objects that work like other loadeble objects.
Loadable connections
To data-load a connection, you can use a combination of helpers:
builder.connectionObject
To create the connection and edge typesbuilder.loadable
with thebyPath
option to create a loadable field with access to argumentst.arg.connectionArgs
to add the standard connection arguments to the field
Loadable Refs and Circular references
You may run into type errors if you define 2 loadable objects that circularly reference each other in their definitions.
There are a some general strategies to avoid this outlined in the circular-references guide.
This plug also has methods for creating refs (similar to builder.objectRef
) that can be used to
split the definition and implementation of your types to avoid any issues with circular references.
All the plugin specific options should be passed when defining the ref. This allows the ref to be used by any method that accepts a ref to implement an object:
The above example is not useful on its own, but this pattern will allow these refs to be used with other that also allow you to define object types with additional behaviors.
Caching resources loaded manually in a resolver
When manually loading a resource in a resolver it is not automatically added to the dataloader
cache. If you want any resolved value to be stored in the cache in case it is used somewhere else in
the query you can use the cacheResolved
option.
The cacheResolved
option takes a function that converts the loaded object into it's cache Key:
Whenever a resolver returns a User or list or Users, those objects will automatically be added the dataloaders cache, so they can be re-used in other parts of the query.
Sorting results from your load
function
As mentioned above, the load
function must return results in the same order as the provided array
of IDs. Doing this correctly can be a little complicated, so this plugin includes an alternative.
For any type or field that creates a dataloader, you can also provide a sort
option which will
correctly map your results into the correct order based on their ids. To do this, you will need to
provide a function that accepts a result object, and returns its id.
This will also work with loadable nodes, interfaces, unions, or fields.
When sorting, if the list of results contains an Error the error is thrown because it can not be
mapped to the correct location. This sort
option should NOT be used for cases where the result
list is expected to contain errors.
Shared toKey
method.
Defining multiple functions to extract the key from a loaded object can become redundant. In cases
when you are using both cacheResolved
and sort
you can use a toKey
function instead: