从 GraphQL 请求构造 MongoDB 查询

Construct MongoDB query from GraphQL request

假设我们用这个请求查询服务器,我们只想得到以下用户的电子邮件,我当前的实现从 MongoDB 请求整个用户对象,我可以想象这是非常低效的。

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

您将如何着手创建一个 MongoDB 过滤器,它只会 return 那些指定的字段?例如,

JS object
{
   "email": 1
}

我当前的服务器是 运行 Node.js、Fastify 和 Mercurius

which I can imagine is extremely inefficient.

执行此任务是一项高级功能,但有很多陷阱。我建议开始构建一个读取所有字段的简单提取。此解决方案有效并且不会 return 客户端的任何附加字段。

陷阱是:

  • 嵌套查询
  • 复杂的对象组合
  • 别名
  • 多个查询合并为一个请求

这是一个可以满足您要求的示例。 它管理别名和多个查询。

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)

使用以下方式调用它:

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

Returns

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

原始答案扩展,他在其中指出他的实现的缺陷之一是它不适用于嵌套查询和 'multiple queries into one request' 此实现试图修复的问题。

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;
}

输入

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

输出(JSON.stringify)

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