Facebook's dataloader library is a great way to reduce the chattiness of your GraphQL server. Under normal circumstances you would create a DataLoader by instantiating a new DataLoader object passing in the batch function as the first argument to the constructor. The suggested pattern for creating reusable DataLoaders, for example when constructing your GraphQL context is to return it from a function like so:

const createUserLoader = () => {
    return new DataLoader<number, User>(batch);
}

This is a decent enough approach but what if we wanted to take a more object oriented approach to constructing our loaders and maybe even injecting them into our services? Instead of creating a function to return a new instance of DataLoader let's extend the DataLoader.

class UserLoader extends DataLoader<number, User> {
    constructor(options?: DataLoader.Options<number, User>) {
        const batch = async (userIds: number) => {
            return User.findAll({ where: { id: userIds }});
        }
        super(batch, options);
    }
}

In the example above we've extended DataLoader to a specific class of UserLoader which is much more descriptive. In the constructor it creates the batch function and passes it into super along with the options. Now, wherever we need to ensure we have a new instance of the UserLoader we can just instantiate it.

const userLoader = new UserLoader();
userLoader.load(userId);
userLoader.load(otherUserId);

Keep in mind, you generally want to use the same instance of your DataLoader across a graphql query or in a single request scope, so make sure you're not recreating the loader each place you want to call .load.

In a future post, I will demonstrate how we can inject a GraphQL query-scoped instance of our dataloaders into the service layer.