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.
Related
I am using Apollo GraphQL server and directives.
Here is my simple schema. Notice the directive on the token field, User type.
const typeDefs = `
directive #allow(service: String) on FIELD_DEFINITION
type User {
email: String!
pass: String!
... other fields here
token: String #allow(service: "login")
}
type Mutation {
login(email: String!, pass: String!): User
}`;
I would like to return the token field only if the login has been called. Otherwise, I would like to return the User object without the token field, all I could find is throwing an Exception or returning the null in the "token" field.
class SkipDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field, details) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
// If called in context different from "login"
// Here I would like to just "delete" the "token" field
else {
const result = await resolve.apply(this, args);
return result;
}
};
}
}
Ideas?
If a field is requested, it should be returned with either a value matching the field's type or else null. To do otherwise would break the spec.
There is no way you can modify this behavior through a schema directive. A field definition directive can only change runtime behavior by modifying the field's resolver. However, by the time the resolver is called, the selection set has already been determined so it's too late to modify it. Returning null or throwing an error are pretty much the only two options.
You might be able to implement some kind of workaround through either the formatResponse option or a custom plugin. However, because this behavior would break the spec, there's no telling if it wouldn't cause issues with client libraries or other tools.
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.
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.
I am using graphql-tools. After receiving a GraphQL query, I execute a search using ElasticSearch and return the data.
However, usually the requested query includes only a few of the possible fields, not all. I want to pass only the requested fields to ElasticSearch.
First, I need to get the requested fields.
I can already get the whole query as a string. For example, in the resolver,
const resolvers = {
Query: {
async user(p, args, context) {
//can print query as following
console.log(context.query)
}
.....
}
}
It prints as
query User { user(id:"111") { id name address } }
Is there any way to get the requested fields in a format like
{ id:"", name:"", address:"" }
In graphql-js resolvers expose a fourth argument called resolve info. This field contains more information about the field.
From the GraphQL docs GraphQLObjectType config parameter type definition:
// See below about resolver functions.
type GraphQLFieldResolveFn = (
source?: any,
args?: {[argName: string]: any},
context?: any,
info?: GraphQLResolveInfo
) => any
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
In the fieldNodes field you can search for your field and get the selectionSet for the particular field. From here it gets tricky since the selections can be normal field selections, fragments or inline fragments. You would have to merge all of them to know all fields that are selected on a field.
There is an info object passed as the 4th argument in the resolver. This argument contains the information you're looking for.
It can be helpful to use a library as graphql-fields to help you parse the graphql query data:
const graphqlFields = require('graphql-fields');
const resolvers = {
Query: {
async user(_, args, context, info) {
const topLevelFields = graphqlFields(info);
console.log(Object.keys(topLevelFields)); // ['id', 'name', 'address']
},
};
Similarly for graphql-java you may do the same by extending the field parameters with myGetUsersResolverMethod(... DataFetchingEnvironment env).
This DataFetchingEnvironment would be injected for you and you can traverse through this DataFetchingEnvironment object for any part of the graph/query.
This Object allows you to know more about what is being fetched and what arguments have been provided.
Example:
public List<User> getUsers(final UsersFilter filter, DataFetchingEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.getSelectionSet();
selectionSet.getFields(); // <---List of selected fields
selectionSet.getArguments(); // <--- Similarly but MAP
...
}
In fact you may be alluding to look ahead data fetching. The above should give you enough insights into the fields requested and you can take it from there to tailor you downstream calls manually. But also you may look into a more efficient way to do this by using the data fetchers for Building efficient data fetchers by looking ahead
I am using graphql-tools. After receiving a GraphQL query, I execute a search using ElasticSearch and return the data.
However, usually the requested query includes only a few of the possible fields, not all. I want to pass only the requested fields to ElasticSearch.
First, I need to get the requested fields.
I can already get the whole query as a string. For example, in the resolver,
const resolvers = {
Query: {
async user(p, args, context) {
//can print query as following
console.log(context.query)
}
.....
}
}
It prints as
query User { user(id:"111") { id name address } }
Is there any way to get the requested fields in a format like
{ id:"", name:"", address:"" }
In graphql-js resolvers expose a fourth argument called resolve info. This field contains more information about the field.
From the GraphQL docs GraphQLObjectType config parameter type definition:
// See below about resolver functions.
type GraphQLFieldResolveFn = (
source?: any,
args?: {[argName: string]: any},
context?: any,
info?: GraphQLResolveInfo
) => any
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
In the fieldNodes field you can search for your field and get the selectionSet for the particular field. From here it gets tricky since the selections can be normal field selections, fragments or inline fragments. You would have to merge all of them to know all fields that are selected on a field.
There is an info object passed as the 4th argument in the resolver. This argument contains the information you're looking for.
It can be helpful to use a library as graphql-fields to help you parse the graphql query data:
const graphqlFields = require('graphql-fields');
const resolvers = {
Query: {
async user(_, args, context, info) {
const topLevelFields = graphqlFields(info);
console.log(Object.keys(topLevelFields)); // ['id', 'name', 'address']
},
};
Similarly for graphql-java you may do the same by extending the field parameters with myGetUsersResolverMethod(... DataFetchingEnvironment env).
This DataFetchingEnvironment would be injected for you and you can traverse through this DataFetchingEnvironment object for any part of the graph/query.
This Object allows you to know more about what is being fetched and what arguments have been provided.
Example:
public List<User> getUsers(final UsersFilter filter, DataFetchingEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.getSelectionSet();
selectionSet.getFields(); // <---List of selected fields
selectionSet.getArguments(); // <--- Similarly but MAP
...
}
In fact you may be alluding to look ahead data fetching. The above should give you enough insights into the fields requested and you can take it from there to tailor you downstream calls manually. But also you may look into a more efficient way to do this by using the data fetchers for Building efficient data fetchers by looking ahead