https://github.com/smithg09/lookahead
Look Ahead GraphQL fields with recursive joins (`$lookup`) using Mongo aggregation pipelines for Apollo queries
https://github.com/smithg09/lookahead
Last synced: 2 months ago
JSON representation
Look Ahead GraphQL fields with recursive joins (`$lookup`) using Mongo aggregation pipelines for Apollo queries
- Host: GitHub
- URL: https://github.com/smithg09/lookahead
- Owner: smithg09
- Created: 2023-07-31T13:14:48.000Z (almost 2 years ago)
- Default Branch: master
- Last Pushed: 2023-08-10T15:46:21.000Z (almost 2 years ago)
- Last Synced: 2025-03-08T09:02:48.377Z (3 months ago)
- Language: JavaScript
- Size: 110 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Lookahead
Look Ahead GraphQL fields with recursive joins (`$lookup`) using Mongo aggregation pipelines for Apollo queries, thus eliminating N+1 problem in GraphQL completely.
## Future
I am building Version 2 of this library which would allow you to recursively convert Nested GraphQL request into N Level Nested Lookups Queries, Along with support for different stages such as Match, Limit & Sort.
You may wanna look into [mongo-aggregation-builder](https://github.com/smithg09/mongo-aggregation-builder), an easier and readable way of building mongodb aggregation pipelines.## Overview
A Apollo/MongoDB based project uses GraphQL resolvers to recursively fetch fields from Mongo collections. This approach is often sufficient in most of the cases, however it suffers from a major issue.
- GraphQL attempts to fetch fields recursively due to which single request can lead to many database requests. It's easy to see how we can quickly reach hundreds of lookups for a single Apollo query.
This issue can be solved by performing a _single_ Mongo aggregation that fetches all the data in one go, performing lookups on the related collections, so that we can then sort or filter on any field in the result._lookahead_ does all the heavy lifting for you:
1. It analyses the `resolveInfo` data passed to the top-level resolver in order to extract the hierarchy of
fields that have been requested. It does this to ensure that it only performs the joins required for the
actual query.2. From this information it builds a **single** Mongo aggregation pipeline that recursively performs lookups
for the other collections used in the request.You can then include the pipeline as part of a larger aggregation pipeline that sorts and filters the result.
## Installation
```
npm install lookahead
```You'll also need to include lookahead's type definition and directive when calling Apollo's `makeExecutableSchema`:
```
import { mergeTypes } from 'merge-graphql-schemas';
import { lookAheadDirective } from 'lookahead';...
const { lookAheadDirectiveTypeDefs, lookAheadDirectiveTransformer } = lookAheadDirective('lookahead');
let schema = makeExecutableSchema({
typeDefs: mergeTypes([lookAheadDirectiveTypeDefs, ...yourTypes]),
resolvers,
});schema = lookAheadDirectiveTransformer(schema)
```## Specifying the Joins
lookahead needs to know which fields are joins, and how to join them. In order to make this both easy to specify and declarative,
a custom GraphQL directive, `@lookahead`, is used to specify this information directly in the types declaration. Here's an example:```
type Company {
...
user: User @lookahead(lookup: { collection: "users", localField: "userId", foreignField: "_id" })
}type Query {
...
companies: [Company!]!
}
```## Writing the Resolvers
In your resolvers you'll call `createPipeline` to create the aggregation pipeline:
```
import { createPipeline } from 'lookahead';...
const companies = (_, { limit = 20 }, context, resolveInfo) => {
// Create a pipeline to first perform any initial matching, then do the lookups and finally fetch the results
const pipeline = [
// Perform any initial matching that you need.
// This would typically depend on the parameters passed to the query.
{ $match: { type: 'b2b' } }// Include all the pipeline stages generated by lookahead to do the lookups
// We pass `null` since the `users` query is mapped directly to the result
// of an aggregation on the Users collection.
...createPipeline(null, resolveInfo, context),// Filter, sort or limit the result.
{ $limit: limit },
];// How you call Mongo will depend on your code base. You'll need to pass your pipeline to Mongo's aggregate.
// This is how you'd do it using `mongoose`
return CompanyCollection.aggregate(pipeline);
});```