I think I'm missing something obvious in the way GraphQL resolvers work. This is a simplified example of my schema (a Place
that can have AdditionalInformation
):
import { ApolloServer, gql } from 'apollo-server';
const typeDefs = gql`
type Place {
name: String!
additionalInformation: AdditionalInformation
}
type AdditionalInformation {
foo: String
}
type Query {
places: [Place]
}
`;
And the associated resolvers:
const resolvers = {
Query: {
places: () => {
return [{name: 'Barcelona'}];
}
},
AdditionalInformation: {
foo: () => 'bar'
}
};
const server = new ApolloServer({typeDefs, resolvers});
server.listen().then(({ url }) => {
console.log(`API server ready at ${url}`);
});
When I execute a basic query:
{
places {
name,
additionalInformation {
foo
}
}
}
I always get null
as the additionalInformation
:
{
"data": {
"places": [
{
"name": "Barcelona",
"additionalInformation": null
}
]
}
}
It's my first GraphQL app, and I still don't get why the AdditionalInformation
resolver is not automatically executed. Is there some way to let GraphQL know it has to fire it?
I've found this workaround but I find it a bit tricky:
Place: {
additionalInformation: () => { return {}; }
}}
Let's assume for a moment that
additionalInformation
was a Scalar, and not an Object type:The value returned by the
places
resolver is:If you were to make a similar query...
What would you expect
additionalInformation
to be? It's value will be null because there is noadditionalInformation
property on thePlace
object returned by theplaces
resolver.Even if we make
additionalInformation
an Object type (likeAdditionalInformation
), the result is the same -- theadditionalInformation
field will resolve to null. That's because the default resolver (the one used when you don't specify a resolver function for a field) simply looks for a property with the same name as the field on the parent object. If it fails to find that property, it returns null.You may have specified a resolver for a field on
AdditionalInformation
(foo
), but this resolver is never fired because there's no need -- the wholeadditionalInformation
field is null so all of the resolvers for any fields of the associated type are skipped.To understand why this is a desirable behavior, imagine a different schema:
We have a database with an
articles
table and animages
table as our data layer. An article may or may not have an image associated with it. My resolvers might look like this:Let's say our call
getArticlesWithImages
resolves to a single article with no image:[{ title: 'Foo', content: 'All about foos' }]
As a consumer of the API, I request:
The
image
field is optional. If I get back an article object with a nullimage
field, I understand there was no associated image in the db. As a front end client, I know not to render any image.What would happen if GraphQL returned a value for the
image
regardless? Obviously, our resolver would break, since it would not be passed any kind of parent value. Moreover, however, as a consumer of the API, I would have to now parse the contents ofimage
and somehow determine whether an image was in fact associated with the article and I should do something with it.TLDR;
As you already suggested, the solution here is to specify a resolver for
additionalInfo
. You can also simply return that value in yourplaces
resolver, i.e.:In reality, if the shape of your schema aligns with the shape of your underlying data layer, it's unlikely you'll encounter this sort of issue when working with real data.