Change backend service based on GraphQL Param - graphql

I have a GraphQL Schema as such:
BigParent (parentParam: Boolean) {
id
name
Parent {
id
name
Child (childParam: Boolean) {
id
name
}
}
}
How can I write resolvers such that I call different backend APIs based on whether the parentParam is true or the childParam is true? The first option is straight-forward. The second one needs to kind of reconstruct the Graph based on the values returned by the service data returned at the level of Child.
I'm not considering both the options as true, as I'll assign some priority so that the param at child level is not considered while the param at the parent level is passed.

You can get the arguments for any field selection in the query by traversing the GraphQL resolver info parameter.
Assuming your query looks like this:
query {
BigParent(parentParam: Boolean) {
id
name
Parent {
id
name
Child(childParam: Boolean) {
id
name
}
}
}
}
You should be able to do something like this:
function getChildParam(info) {
// TODO: Return 'childParam'
}
const resolvers = {
Query: {
async BigParent(parent, args, context, info) {
// 'args' contains the 'parentParam' argument
const parentParam = args.parentParam;
// Extract the 'childParam' argument from the info object
const childParam = getChildParam(info);
if (parentParam || childParam) {
return context.datasource1.getData();
}
return context.datasource2.getData();
},
},
};

Related

Why graphql-js executor stops resolving child fields that have their own resolvers when the parent resolver return null/undefined?

While writing a lib for GraphQL in JavaScript I stumbled upon a curious behavior. I managed to isolate it in a very simple example. Let's take this server snippet:
const { ApolloServer, gql } = require("apollo-server")
const typeDefs = gql`
type Book {
resolveItSelf: String
}
type Query {
book: Book
}
`
const resolvers = {
Query: {
book: () => {
return null // same behavior with undefined here
}
},
Book: {
resolveItSelf: () => "resolveItSelf"
}
}
const server = new ApolloServer({ typeDefs, resolvers })
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})
If we query this server with the following query:
{
book {
resolveItSelf
}
}
We get this result:
{
"data": {
"book": null
}
}
So, I was expecting the graphql executor to try to resolve the "resolveItSelf" field (which have its own resolver) even if the book resolver returned null.
A way to get the behavior I expect is to change the book's resolver a little bit:
const resolvers = {
Query: {
book: () => {
return {} // empty object instead of null/undefined
}
},
Book: {
resolveItSelf: () => "resolveItSelf"
}
}
Then we get this result:
{
"data": {
"book": {
"resolveItSelf": "resolveItSelf"
}
}
}
The field is resolved even if the parent is empty !
So my question is why the graphql-js executor stop trying to resolve fields if the parent's resolver return null/undefined, even though requested fields can be resolved on their own ? (Is there a section in the draft that cover this ?)
In GraphQL, null represents a lack of a value. If a field resolves to null, it doesn't make sense for its "child" field resolvers' to be called since they wouldn't be returned in the response anyway.
From the Value Completion section of the spec (emphasis mine):
If the fieldType is a Non‐Null type:
a. Let innerType be the inner type of fieldType.
b. Let completedResult be the result of calling CompleteValue(innerType, fields, result, variableValues).
c. If completedResult is null, throw a field error.
d. Return completedResult.
If result is null (or another internal value similar to null such as undefined or NaN), return null.
If fieldType is a List type:
a. If result is not a collection of values, throw a field error.
b. Let innerType be the inner type of fieldType.
c. Return a list where each list item is the result of calling CompleteValue(innerType, fields, resultItem, variableValues), where resultItem is each item in result.
If fieldType is a Scalar or Enum type:
a. Return the result of “coercing” result, ensuring it is a legal value of fieldType, otherwise null.
If fieldType is an Object, Interface, or Union type:
a. If fieldType is an Object type.
i. Let objectType be fieldType.
b. Otherwise if fieldType is an Interface or Union type.
i. Let objectType be ResolveAbstractType(fieldType, result).
c. Let subSelectionSet be the result of calling MergeSelectionSets(fields).
d. Return the result of evaluating ExecuteSelectionSet(subSelectionSet, objectType, result, variableValues) normally (allowing for parallelization).
In other words, even if a field's type is an Object (and therefore has a selection set of fields that may also be resolved), if it resolves to null, no further execution happens along that path.

Field variableValues not working on graphql library execution on node

I'm playing with the graphql library (https://github.com/graphql/graphql-js) on node but I'm having some hard time on passing variable attributes...
const variableValues = {
routing, // String
statuses, // Array
date // Input type described in the query
}
return graphql({
schema: schema,
source: query,
rootValue: resolvers,
variableValues: variableValues
})
Unfortunately the variableValues are not passed to the resolver (if I log the context from the resolver, it show me that the variableValues is an empty object).
Any suggestions?
Variable values are not passed to your context. Variables are used to substitute values inside an operation. So instead of using literal values like this:
query GetUser {
getUser(id: 42) {
name
}
}
we can write
query GetUser($userId: ID!) {
getUser(id: $userId) {
name
}
}
In this particular example, userId would be exposed to the resolver for getUser as the id argument. The arguments for a field are provided as a the second parameter to the resolver function, separate from the context (which is the third parameter passed to the resolver).
const resolvers = {
Query: {
getUser: (root, args, ctx) => {
console.log(args.id) // prints the value of $userId
...
},
},
}
Note that variables may be used as arguments to directives as well, in which case they will not be passed to the resolver as part of the argument map at all.

Apollo GraphQL: Resolver not called on mutation subfield

I am trying to return a Query type from a mutation, which I could make work in some cases but not the way I want it. The problem is not particularly related to the Query type being used, as I found the same behaviour using other types than Query.
You can run and modify this code on https://codesandbox.io/s/1z8kjy8m93
Server
const { ApolloServer, gql } = require("apollo-server");
const typeDefs = gql`
type Query {
hello(msg: String): String
}
type Mutation {
someMutation(someArg: String): MutationResponse
}
type MutationResponse {
query: Query
status: String
}
`;
const resolvers = {
Query: {
hello: (root, args, context) => {
console.log("hello: args = ", args);
return `${args.msg}, world !`;
}
},
Mutation: {
someMutation: (root, args, context) => {
console.log("someMutation: args = ", args);
return { status: `Mute Mute: ${args.someArg}` };
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Mutation
mutation mutateMe($mutationArg: String = "YoloMute !", $helloMsg: String = "Yolhello") {
someMutation(someArg: $mutationArg) {
status
query {
hello(msg: $helloMsg)
}
}
}
Response
{
"data": {
"someMutation": {
"status": "Mute Mute: YoloMute !",
"query": null
}
}
}
I don't understand why the hello resolver is not called and the query field is null.
The status field is duly filled by the someMutation resolver, but as the query field is not resolved there I would expect GraphQL to call an existing resolver for this field, which exists and should be called for the Query type.
I found other ways that technically work but are not satisfying:
Directly returning the Query type works:
https://codesandbox.io/s/ovr9zpwr7q
Duplicating lines in the schema
works: https://codesandbox.io/s/l4yrqzmq6l
This issue isn't really specific to the Query type, but rather deals with how you've set up your resolvers.
The status field is duly filled by the someMutation resolver, but as the query field is not resolved there I would expect GraphQL to call an existing resolver for this field, which exists and should be called for the Query type.
There is no resolver for the entire Query type, or any other type. Resolvers exist only for individual fields of a particular type. When a resolver isn't defined for a field, GraphQL will default to looking for a property on the parent object with the same name as the field, and will return the value of that property.
Let's walk through your document. The root-level field is:
someMutation(someArg: $mutationArg)
The parent value is the root value for all root-level mutations. Unless you're using a custom root value, this will typically be an empty object. If you didn't define a resolver for the someMutation field of the Mutation type, GraphQL would look for a property called someMutation in your root value and return that (i.e. undefined, which would be coerced to null in the response). We do have a resolver, though, and it returns:
{
status: `Mute Mute: ${args.someArg}`,
}
Now, let's resolve the status field. Our parent object is the result returned by the parent field's resolver. In this case, the object above. We have no resolver for status on MutationResponse, so GraphQL looks for a status property on the parent -- it finds one and uses that. status has a Scalar type, so whatever value is returned by the resolver will be coerced into the appropriate scalar value.
What about the query field? Again, we have no resolver for a query field on the MutationResponse. However, we also don't have a property called query on the parent object. So, all GraphQL can do is return null for that field.
Even though the return type for query is an ObjectType, because it resolves to null, any resolvers for fields on that ObjectType will not be fired. Returning null means the object doesn't exist, so we don't need to bother resolving any fields on it. Imagine if a field returned a User object. If it returned null, there would be no need to resolve the user's name, for example.
So... how do we get around this? There's two ways:
Either add a property for query to the object returned by someMutation's resolver, like this:
{
status: `Mute Mute: ${args.someArg}`,
query: {},
}
Or, add a resolver for the field:
MutationResponse: {
query: () => {},
},
Either way will ensure that the query field will resolve to a non-null value (in this case, just an empty object). Because the value resolved is not null and the return type is an ObjectType (in this case Query), now the resolvers for that type's fields will be triggered and hello will resolve as expected.

Apollo Graphql Resolver for a Nested Object

So I have a type like this:
type CardStatus {
status: String
lastUpdated: String
}
type CardCompany {
cardStatus: CardStatus
}
type ExternalAccounting {
cardCompany: CardCompany
}
type User {
balance: String
externalAccounting: ExternalAccounting
}
And my resolver looks something like this
const User = {
balance: (root, args, context) => getBalance().then((res)=>res)
cardStatus: (??)
}
I want to use a resolver to set the nested cardStatus field in the user object.
Balance is the direct field of an object, it's easy- I just run a resolver and return the result to get balance. I want to run a cardStatus api call for the deeply nested cardStatus field, but I have no idea how to do this. I've tried something in my resolver like this:
const User = {
balance: {...}
externalAccounting: {
cardCompany: {
cardStatus: (root) => { (...) },
},
},
}
But it doesn't work in that it does not set the cardStatus nested field of the user object. Seems like it should be relatively easy but I can't find an example online..
You should define the cardStatus resolver under the type CardCompany.
When defining types, they should all be on the same level in the resolver map. Apollo will take care of resolving the query's nested fields according to the type of each nested field.
So your code should look something like this:
const User = {
balance: (root, args, context) => getBalance().then((res)=>res)
}
const CardCompany = {
cardStatus: (root) => { (...) },
}
And I'm not sure how it is connected to your executable schema but it should probably be something similar to:
const schema = makeExecutableSchema({
typeDefs,
resolvers: merge(User, CardCompany, /* ... more resolvers */ )
})

GraphQL query and check the returned data

I would like to have a query containing a check, for example when querying users, only users that have a non-null email are wanted. How could I add such check? Thanks!
users {
id,
username,
email
}
GraphQL fields can have arguments.
So you could pass an argument to your users field, named onlyNonNullEmail: boolean:
type Query {
users(onlyNonNullEmail: Boolean) {
...
}
}
Then, inside your resolve function:
users: (_, args, context, ast) => {
const { onlyNonNullEmail } = args;
// If onlyNonNullEmail is true, return only users with a non-null email
// Else proceed as usual
}

Resources