Operators in GraphQL?

So GraphQL is the hot tech now. It lets you interface with multiple backends over one “graph” and gives you only the data you need in the structure you ask. Now that’s neat.

However, it appears that the basic fundamental of how a “query” is constructed doesn’t allow for flexibility for operators (e.g. $and, $or, $le, $lte, $ge, $gte). It appears to be basic string matching for the arguments that you can pass.

Here’s my use-case:

I’m allowing users to query a table of 300,000+ items. They are filtering out their selection by using a faceted search feature that will dynamically generate the query and pass it to MongoDB.

Example:

{
  "age": {
    "$gte": 10,
    "$lte": 25
  },
  "location": {
    "$in": [
        "New York",
        "California"
    ]
  }
}

I think GraphQL is a bit misnamed - it’s not really a “query language” like MongoDB or SQL queries are. Think of it as a replacement for REST, where you define exactly the parameters you want to accept.

3 Likes

Will you decode this sentence for me? Code would be nice.

That makes sense Sashko! That being the case, does that mean the problem is outside of the GraphQL domain? If MDG (and Facebook) will be betting on GraphQL (Apollo in your case), then this has to be a problem that has a solution. It seems unwise to maintain a GraphQL internet facing node AND a node with Mongo or SQL behind a REST server. :frowning:

Operators are essential in every query language, how could GraphQL expect to layer itself over if it can’t match the same feature.

Doesn’t seem to make sense on the face of it. Hopefully @sashko will untie this knot for us.

To me, this question is the same as:

However, it appears that the basic fundamental of how REST parameters are constructed doesn’t allow for flexibility for operators

GraphQL is just a fancy version of Meteor methods, or REST API endpoints.

So let’s say you have the following query:

{
  me {
    name
    bestFriend {
      name
    }
  }
}

In your mind, translate that to:

const meResult = me();
const bestFriendResult = meResult.bestFriend();

return {
  me: {
    name: meResult.name,
    bestFriend: {
      name: bestFriendResult.name,
    }
  }
}

GraphQL is nothing more than a language for calling a bunch of nested functions over a network. It’s not a query language with operators etc.

1 Like

Yes…the ‘QL’ in GraphQL shows naming things is still a problem everywhere not just in ComSci :wink:

1 Like

Okay I see how I was incorrect in seeing the paradigm that GraphQL operated in. However, if it expects to be used more heavily then how does it expect to succeed unless you can “pass” those operators through to the resolvers?

Here’s an open issue on Facebook’s GraphQL repo about it: https://github.com/graphql/graphql-js/issues/290

@sashko, we discussed it a bit over Slack and it seems that the only way to get this concept of “dynamic parameters” to play nice with GraphQL is that I’ll need to implement this: https://github.com/taion/graphql-type-json I’m going to give this a shot and report back with my results. If this doesn’t work out, I’ll have no choice but to refactor Apollo out of my stack… :frowning: It’s a deal breaker that I can’t $gte, $lte in my arguments, even if it wasn’t meant to. :frowning:

To tag onto Sashko’s explanation; here’s a Reddit post from a user detailing how GraphQL is less like a query language.

What’s the name of the slack handle you’re discussing GraphQL and is everyone invited?

Wait, so we need another library to query with basic mongo operators using the Apollo abstraction over GraphQL?

No, from the Reddit posting above:

Those fields between the parens work like function arguments, and those function arguments are interpreted by the domain.

I haven’t looked too closely yet at Apollo but from Sashko’s comment I assume it should work like this:

You create a query on the Apollo server. Your Meteor app then only hands over the parameters for the query.

Someone correct me if I’m wrong.

That’s the way it works for Azure Mobile Services - you can do simple stuff like INSERT and UPDATE easily. But stuff like a JOIN has to be done either on the client or via a prepared query on the server.

@mgranda You do the operating in whatever data source query language you are using. For instance, if you need to limit the number of results you get back with graphql you pass a limit parameter to the query and then in the graphql resolver you use that limit parameter in the query language of your choice to get the results you want to send back to the client. Here is an example:

Query

{
  human(limit: 10) {
    name
    height
  }
}

If you’re using Mongo then in your resolver function you do:

// some resolver function in a query defined on the schema
human: {  
  type: humanType, //defined somewhere
  args: {
    limit: {
      name: 'limit',
      type: new GraphQLNumber
    }
  },
  resolve: (root, {limit}) => {
    return Humans.find({}, {limit: limit});
  }
}

You can pass whatever args you want to the query in order to use with MongoDB queries or any other database and query language. GraphQL is just an abstraction like REST to standardize how a client/server communicate. All the query magic happens in resolver functions on the server.

3 Likes

Ahhh – got it. That makes sense. We have to reduce the complexity over the wire, due to limitations of REST. Just pass in params to the server and make the call there.

It could actually be easier this way as we won’t have write (in some cases) the same query client and server as we do today.

@ryanswapp, so there are a few “built in” RESTful operators, like your example with limit: 10?

@ryanswapp I really appreciate your example! However it doesn’t scale well if you’re wanting to do greater than, less than, in list, not in list, is empty, is not empty… you’d have to hardcode every argument with it’s own operator baked into it for this right?

I’m totally cool at this point just passing the entire MongoDB object as a single argument and letting the resolver handle it just as you did here:

  resolve: (root, {limit}) => {
    return Humans.find({}, {limit: limit});
  }

@aadams I think in the case of limit: 10, he built it himself because it will give you the functionality out of the box to make that an argument, the problem herein lies when you want to make more complex arguments.

Thanks. Then what are the limitations of parameters over REST? Can we pass in anything we want, as long as we know how to decode it on the server side ?

With a query like this on the client:

{
   person ( userId: 1234, active: true, limit: 10 )
}

I’d have to define this query on the server, or is this query an actual parameter, or will the server need to parse the JSON and extract out the operators, or is there a “built in” way within GraphQL to define this particular JSON object or a REST call matching this, and map it to a “resolver” on the server?

Now I’m getting more confused about how the “nuts and bolts” of how this works…

I don’t think there are any limitations over REST. If I’m understand @Sashko correctly GraphQL is purely just a transport layer with some translation built in. It is “dumb” in terms of operators.

That being said, I don’t think that query is correct.

The rank you have within the functions curly braces would be interpreted as a field you’re looking to obtain from GraphQL, not actually a parameter you’re passing to it.

1 Like

I think GraphQL is very scalable. More so than any other client/server protocol I’m aware of. Maybe a better way to think of a graphql query is to think of it as a Meteor subscription while the resolve function is the Meteor publication. However, instead of the subscription filling minimongo with data the subscription call itself returns the data you’ve requested (like a Collection.find call). If you need to pass a bunch of arguments to the query then just define a bunch of possible arguments and then you can use them however you want in the resolve function.

Also, you should never be passing a mongo query as a graphql argument or as an argument to any other client-to-server protocol. The mongo query should be built on the server from parameters that the client sends the server. Otherwise you open yourself to all kinds of security issues if you are directly passing mongo queries from the client to a find call on the server.

@aadams In my example limit is just an arbitrary argument I defined in the schema query on the server. One nice thing about GraphQL is that you can define whatever arguments you want as well as the type of that argument.

1 Like

100% correct.

Correct.

Totally agreed.

3 Likes

There’s a Richard Feynman quote that applies here.

2 Likes

hope operators are part of a future implementation of graphql, so it can be a truly query language

1 Like