0

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.

agm1984
  • 15,500
  • 6
  • 89
  • 113

1 Answers1

0

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.

agm1984
  • 15,500
  • 6
  • 89
  • 113
  • 1
    You could just pass the db connection into your context rather than utilizing the root value. This will make it available to all your resolvers without any additional work. – Daniel Rearden Sep 04 '17 at 21:31
  • You know, now that I hear you say it, I think I recall some documentation from Apollo Server saying that doing so is recommended over passing it in the root value. I'll try and find it. Thanks. – agm1984 Sep 04 '17 at 23:08
  • I don't see anything, but this page is where I was thinking: http://dev.apollodata.com/tools/apollo-server/setup.html . I'm pretty sure you are spot on because I know I've read documentation that indicates this, on one of those days I read hundreds of articles on GraphQL :) I think Jonas Helfer or Lee Byron says it explicitly somewhere. I imagine it will be a fairly straight forward improvement to use `context`. I will update my post with the results. – agm1984 Sep 04 '17 at 23:15
  • Lol, it was like a one line change, everything from `root.db` to `context.db` and it removed those code smellerific `Object.assign()`s, after I passed `db` into the context object in the middleware. Thanks again. – agm1984 Sep 04 '17 at 23:20