简体   繁体   中英

Construct MongoDB query from GraphQL request

Let's say we query the server with this request, we only want to get the following user's Email, My current implementation requests the whole User object from the MongoDB, which I can imagine is extremely inefficient.

GQL
{
  user(id:"34567345637456") {
    email
  }
}

How would you go about creating a MongoDB filter that would only return those Specified Fields? Eg,

JS object
{
   "email": 1
}

My current server is running Node.js, Fastify and Mercurius

which I can imagine is extremely inefficient.

Doing this task is an advanced feature with many pitfalls. I would suggest starting building a simple extraction that read all the fields. This solution works and does not return any additional field to the client.

The pitfalls are:

  • nested queries
  • complex object composition
  • aliasing
  • multiple queries into one request

Here an example that does what you are looking for. It manages aliasing and multiple queries.

const Fastify = require('fastify')
const mercurius = require('mercurius')

const app = Fastify({ logger: true })

const schema = `
  type Query {
    select: Foo
  }

  type Foo {
    a: String
    b: String
  }
`

const resolvers = {
  Query: {
    select: async (parent, args, context, info) => {
      const currentQueryName = info.path.key

      // search the input query AST node
      const selection = info.operation.selectionSet.selections.find(
        (selection) => {
          return (
            selection.name.value === currentQueryName ||
            selection.alias.value === currentQueryName
          )
        }
      )
      
      // grab the fields requested by the user
      const project = selection.selectionSet.selections.map((selection) => {
        return selection.name.value
      })

      // do the query using the projection
      const result = {}
      project.forEach((fieldName) => {
        result[fieldName] = fieldName
      })

      return result
    },
  },
}

app.register(mercurius, {
  schema,
  resolvers,
  graphiql: true,
})

app.listen(3000)

Call it using:

query {
  one: select {
    a
  }
  two: select {
    a
    aliasMe:b
  }
}

Returns

{
  "data": {
    "one": {
      "a": "a"
    },
    "two": {
      "a": "a",
      "aliasMe": "b"
    }
  }
}

Expanding from @Manuel Spigolon original answer, where he stated that one of the pitfalls of his implementation is that it doesn't work on nested queries and 'multiple queries into one request' which this implementation seeks to fix.

function formFilter(context:any) {
    let filter:any = {};

    let getValues = (selection:any, parentObj?:string[]) => {
        //selection = labelSelection(selection);

        selection.map((selection:any) => {
            // Check if the parentObj is defined
            if(parentObj)
                // Merge the two objects
                _.merge(filter, [...parentObj, null].reduceRight((obj, next) => {
                    if(next === null) return ({[selection.name?.value]: 1});
                    return ({[next]: obj});
                }, {}));

            // Check for a nested selection set
            if(selection.selectionSet?.selections !== undefined){
                // If the selection has a selection set, then we need to recurse
                if(!parentObj) getValues(selection.selectionSet?.selections, [selection.name.value]);

                // If the selection is nested
                else getValues(selection.selectionSet?.selections, [...parentObj, selection.name.value]);
            }
        });
    }

    // Start the recursive function
    getValues(context.operation.selectionSet.selections);

    return filter;
}

Input

{
  role(id: "61f1ccc79623d445bd2f677f") {
        name
    users {
      user_name
      _id
      permissions {
        roles
      }
    }
    permissions
  }
}

Output (JSON.stringify)

{
   "role":{
      "name":1,
      "users":{
         "user_name":1,
         "_id":1,
         "permissions":{
            "roles":1
         }
      },
      "permissions":1
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM