Returning the Query type in GraphQL

Pro tip, or terrible mistake? You decide

I’m no stranger to weird GraphQL tricks. So let me hit you up with another one. Did you know that the initial Query type in GraphQL works just like any other type? Well one thing you may not realize is that you can actually include it anywhere in your schema, which lets you use it multiple times in the same query.

Try and edit this example on Launchpad, the GraphQL server playground.

Let’s look at two examples.

1. Accessing the query type in subtrees

Let’s say for some reason, you want to be able to access the Query type somewhere on your schema that isn’t the root of a query. Easy, just make your schema like this:

type Query {
hello: String
query: Query
}

Now, you can run a query that looks like this:

query Nested {
hello
query {
hello
query {
hello
}
}
}

Is this useful? No, because there’s no benefit to having the query field nested in this way if you can’t access the parent value. You just as well might have accessed that field at the root of the query.

But wait till you see the next one.

2. Accessing the parent value from Query fields

In the stone age, GraphQL didn’t have the context feature on the server, so people used to pass the current user as the root parameter into the Query type. We don’t need to do that anymore, since we can pretty much always use context instead. So it’s not really used… what can we do with it?

In the new subscriptions spec, the root value on the subscription field is cleverly used to deliver the payload from your event stream. If the payload comes from a mutation result, you can think of the subscription as picking up where that mutation left off. I think that’s really cool. Well, it turns out you can do the same thing with queries as well…

type Query {
widget(id: String): Widget
}
type Mutation {
createWidget: Query # wat
}
type Widget {
id: String!
name: String!
}

The most important thing to note here is that Mutation.createWidget simply returns Query — not a special payload type. Now watch this resolver trick:

const resolvers = {
Query: {
widget: (root, args, context) => {
return widgets[root.id || args.id];
},
},
Mutation: {
createWidget: (root, args, context) => {
const widget = {
id: nextWidgetId, name: faker.name.jobTitle };
widgets[widget.id] = widget;
return widget;
},
},
};

As you can see here, we made the widget field on Query able to get its id either from root or from args. So now, putting it all together:

mutation Create {
createWidget {
widget { # This is actually Query.widget
name
}
hello
}
}

You can now use any field on the Query type in the result of your mutation! So not only can you access the hello field from above, but you can call the widget field. But not only that — the widget field actually knows about the widget that was just created! Check this:

{
"data": {
"createWidget": {
"widget": {
"name": "Dynamic Metrics Consultant"
},
"hello": "Hello world!"
}
}
}

Crazy cool, right?

Conclusion

The GraphQL Query type is just like any other type in your schema. Usually you only use it as the starting point of a query, but it can actually go anywhere you want. I think this has the potential of making mutation return types a lot more flexible.

Enjoy this art I found on Stack Overflow. Did you know Medium posts always get more reads with an image?