How to pass DB connection to a sub-resolver when the root resolver returns an iterable

821 views Asked by At

In short, this resolver getAllArticles() returns an array of Articles, and each article has an Author Field and a Tags Field, so each article can fire the sub-resolver to get that data, but I was having trouble seeing and finding the best solution.

You have to know some backstory:

app.js

I am passing the DB connections into the top-level resolvers as a map in the root value.

const db = new Map()
db.set('Neo4J', Neo4J.getDriver())
db.set('MongoDB', MongoDB.getDB())

// GraphQL Endpoint
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
    // ...
    return {
        schema,
        context,
        rootValue: {
            db
        }
    }
}))

getArticle.js

I am passing the db connections to the sub-resolvers by assigning them onto the response object.

const getArticle = async (root, args, context) => {
    const db = root.db
    const Neo4J = db.get('Neo4J')
    const MongoDB = db.get('MongoDB')
    // ...
    const article = { /* ... */ }
    return Object.assign({}, article , { db })
}

This worked excellent (code has become extremely clean) until I moved to the getAllArticles() resolver that returns an array of articles. I could not see how to attach the db Map.

getAllArticles.js

Here's what was immediately intuitive to add:

const getAllArticles = async (root, args, context) => {
    const db = root.db
    const Neo4J = db.get('Neo4J')
    const MongoDB = db.get('MongoDB')
    // ...
    const articles = [{ /* ... */ }, { /* ... */ }, { /* ... */ }]
    return Object.assign({}, articles, { db })
}

That didn't work, and after looking at it, why would it? Sub-resolvers take the data from the parent object, which is each Article in this case.

1

There are 1 answers

4
agm1984 On BEST ANSWER

After some iterations, here is the viable solution:

app.js

import Neo4J from './connectors/neo4j'
import MongoDB from './connectors/mongodb'
const db = new Map([
    ['Neo4J', Neo4J.getDriver()],
    ['MongoDB', MongoDB.getDB()]
])

app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
    const context = {
        settings: { SECRET },
        person: req.person,
        db
    }
    return {
        schema,
        context,
        rootValue: null
    }
}))

everyResolver.js

const getSomething = async (root, args, context, info) => {
    const db = context.db
    const Neo4J = db.get('Neo4J')
    const MongoDB = db.get('MongoDB')

    const session = Neo4J.session()
    session.run(query) // etc

    const users = MongoDB.collection('users')
    users.findOne(ObjectID(id)) // etc

    return objectOrIterable
}

Hopefully, this can help someone else in the future. I really like the approach of passing the DB driver connections into the resolvers. It tightened up the overall architecture and allows me to spin up additional resolvers easily because they come with batteries included.

If you pass DB connections into the GraphQL context parameter, just make sure you pass in a Map containing the DB connections, not an Object. Some values in the DB connections are functions. Maps are able to handle that. Objects are not. You may see horribly ambiguous detonations related to the DB connections in your sub-resolvers unless you pass around a Map.