1

I'm learning ho to develop GraphQL service with express, express-graphql, **graphql, mongoose,

db.collection.find has an optional query parameter that specifies selection filter using query operators.

I wonder if it is possible to define a schema in which to define an argument for a query field that ultimately it is passed as it is to the collection find methods.

for example I expect that the graphql query:

{ todosQuerable(query: {title: "Andare a Novellara"}) 
  { _id, title, completed } 
}

responds with:

{
  "data": {
    "todos": [
      {
        "title": "Andare a Novellara",
        "completed": false
      }
    ]
  }
}

since in mongo

> db.Todo.find({title: 'Andare a Novellara'})
{ "_id" : ObjectId("600d95d2e506988bc4430bb7"), "title" : "Andare a Novellara", "completed" : false }

I'm thinking something like:

   todosQuerable: {
        type: new graphql.GraphQLList(TodoType),
        args: {
          query: { type: <???????????????> },
        },
        resolve: (source, { query }) => {
          return new Promise((resolve, reject) => {
            TODO.find(query, (err, todos) => {
              if (err) reject(err)
              else resolve(todos)
            })
          })
        }
      }

I have made a few attempts but have not been able to get an idea of which type I should use in this case

ho help reproduce the problem here the source repository of my tests

Please note that this works fine:

  todosByTitle: {
    type: new graphql.GraphQLList(TodoType),
    args: {
      title: { type: graphql.GraphQLString },
    },
    resolve: (source, { title }) => {
      return new Promise((resolve, reject) => {
        TODO.find({title: {$regex: '.*' + title + '.*', $options: 'i'}}, (err, todos) => {
          if (err) reject(err)
          else resolve(todos)
        })
      })
    }
  }

but what I'm looking for is something more generic: I would like to grab graphql field argument named query and pass it as is to the the query parameter of the mongo collection find.

Franco Rondini
  • 10,841
  • 8
  • 51
  • 77

1 Answers1

1

So the good news is you can do whatever you want. The bad news is that:

  1. You have to do it yourself
  2. You have to add every searchable field, so you'll probably end up with two copies of the Todo object here.

The type you're looking for is just a custom input object type like this:

Notice the GraphQLInputObjectType below is different from GraphQLObjectType.

var TodoQueryType = new graphql.GraphQLInputObjectType({
  name: 'TodoQuery',
  fields: function () {
    return {
      _id: {
        type: graphql.GraphQLID
      },
      title: {
        type: graphql.GraphQLString
      },
      completed: {
        type: graphql.GraphQLBoolean
      }
    }
  }
});
      todosQuerable: {
        ...
        type: new graphql.GraphQLList(TodoType),
        ...
        args: {
          query: { type: TodoQueryType },
        },
        ...
      }

These two queries work great!

(this is me using aliases so I can make the same query twice in one call)

{
  titleSearch: todosQuerable(query:{ title:"Buy orange" }) {
    _id
    title
    completed
  }
  idSearch: todosQuerable(query:{ _id:"601c3f374b6dcc601890048d" }) {
    _id
    title
    completed
  }
}

Footnote:

Just to have it said, this is generally a GraphQL anti-pattern, as this is building an API based on your database choices, rather than as a client-driven API.

Regex Edit as requested:

If you're trying to do regular expression lookups, you have to figure out how to programmatically convert your strings into regular expressions. i.e. your input is a string ("/Novellara/"), but mongoose requires passing a RegExp to do wildcards (/Novellara/, no quotes).

You can do that a number of ways, but I'll show one example. If you change your input fields to use two properties of value & isExpression, like below, you can do it, but you have to specifically craft your query, since it's no longer just a passthrough.

var ExpressableStringInput = new graphql.GraphQLInputObjectType({
  name: 'ExpressableString',
  fields: {
    value: {
      type: graphql.GraphQLString
    },
    isExpression:{
      type: graphql.GraphQLBoolean,
      defaultValue: false,
    }
  }
})

var TodoQueryType = new graphql.GraphQLInputObjectType({
  name: 'TodoQuery',
  fields: function () {
    return {
      _id: {
        type: graphql.GraphQLID
      },
      title: {
        type: ExpressableStringInput
      },
      completed: {
        type: graphql.GraphQLBoolean
      }
    }
  }
});

// resolver
      todosQuerable: {
        type: new graphql.GraphQLList(TodoType),
        args: {
          query: { type: TodoQueryType },
        },
        resolve: async (source, { query }) => {
          const dbQuery = {};

          if (query.title.isExpression) {
            dbQuery.title = new RegExp(query.title.value);
          } else {
            dbQuery.title = query.title.value;
          }

          return new Promise((resolve, reject) => {
            TODO.find(dbQuery, (err, todos) => {
              if (err) reject(err)
              else resolve(todos)
            })
          })
        }
      }

your query would then look like

query {
  todosQuerable(query:{ title: { value: "Buy.*", isExpression: true }}) {
    _id
    title
    completed
  }
}

This query makes sense in my mind. If I think about the form you would show to a user, there is probably an input box and a checkbox that says "is this a regular expression?" or something, which would populate this query.

Alternatively, you could do like string matching: if the first and last characters are "/", you automagically make it into a regex before passing it into mongoose.

Dan Crews
  • 3,067
  • 17
  • 20
  • thank you for answering, this works in most cases, but I still can't figure out how to pass **like** queries, such as: `{"title": /Novellara/}` , that works in mongo `> db.Todo.find({"title": /Novellara/})` or similar forms of query, see https://stackoverflow.com/q/3305561/1657028 for examples. If you have any considerations on this could you integrate it into the answer please – Franco Rondini Feb 06 '21 at 00:43
  • see also: https://github.com/rondinif/lab05-mongodb-graphql/commit/fd5f4611b5c5bfbde402440c2fcaeb9ce5406808 – Franco Rondini Feb 06 '21 at 03:18
  • 1
    Added requested regex implementation – Dan Crews Feb 19 '21 at 20:19
  • great update!, now i have a better understanding of how to define query types in graphql. thank you very much. example code repo updated: https://github.com/rondinif/lab05-mongodb-graphql/commit/80a02d4d5dfaf9ac5a02edc24d36403646026256 – Franco Rondini Feb 21 '21 at 19:01