I have an graphql
/apollo-server
/graphql-yoga
endpoint. This endpoint exposes data returned from a database (or a REST endpoint or some other service).
I know my data source is returning the correct data -- if I log the result of the call to the data source inside my resolver, I can see the data being returned. However, my GraphQL field(s) always resolve to null.
If I make the field non-null, I see the following error inside the errors
array in the response:
Cannot return null for non-nullable field
Why is GraphQL not returning the data?
There's two common reasons your field or fields are resolving to null: 1) returning data in the wrong shape inside your resolver; and 2) not using Promises correctly.
Note: if you're seeing the following error:
the underlying issue is that your field is returning null. You can still follow the steps outlined below to try to resolve this error.
The following examples will refer to this simple schema:
Returning data in the wrong shape
Our schema, along with the requested query, defines the "shape" of the
data
object in the response returned by our endpoint. By shape, we mean what properties objects have, and whether those properties' values' are scalar values, other objects, or arrays of objects or scalars.In the same way a schema defines the shape of the total response, the type of an individual field defines the shape of that field's value. The shape of the data we return in our resolver must likewise match this expected shape. When it doesn't, we frequently end up with unexpected nulls in our response.
Before we dive into specific examples, though, it's important to grasp how GraphQL resolves fields.
Understanding default resolver behavior
While you certainly can write a resolver for every field in your schema, it's often not necessary because GraphQL.js uses a default resolver when you don't provide one.
At a high level, what the default resolver does is simple: it looks at the value the parent field resolved to and if that value is a JavaScript object, it looks for a property on that Object with the same name as the field being resolved. If it finds that property, it resolves to the value of that property. Otherwise, it resolves to null.
Let's say in our resolver for the
post
field, we return the value{ title: 'My First Post', bod: 'Hello World!' }
. If we don't write resolvers for any of the fields on thePost
type, we can still request thepost
:and our response will be
The
title
field was resolved even though we didn't provide a resolver for it because the default resolver did the heavy lifting -- it saw there was a property namedtitle
on the Object the parent field (in this casepost
) resolved to and so it just resolved to that property's value. Theid
field resolved to null because the object we returned in ourpost
resolver did not have anid
property. Thebody
field also resolved to null because of a typo -- we have a property calledbod
instead ofbody
!One important thing to keep in mind is that in JavaScript, almost everything is an Object. So if the
post
field resolves to a String or a Number, the default resolver for each of the fields on thePost
type will still try to find an appropriately named property on the parent object, inevitably fail and return null. If a field has an object type but you return something other than object in its resolver (like a String or an Array), you will not see any error about the type mismatch but the child fields for that field will inevitably resolve to null.Common Scenario #1: Wrapped Responses
If we're writing the resolver for the
post
query, we might fetch our code from some other endpoint, like this:The
post
field has the typePost
, so our resolver should return an object with properties likeid
,title
andbody
. If this is what our API returns, we're all set. However, it's common for the response to actually be an object which contains additional metadata. So the object we actually get back from the endpoint might look something like this:In this case, we can't just return the response as-is and expect the default resolver to work correctly, since the object we're returning doesn't have the
id
,title
andbody
properties we need. Our resolver isn't needs to do something like:Note: The above example fetches data from another endpoint; however, this sort of wrapped response is also very common when using a database driver directly (as opposed to using an ORM)! For example, if you're using node-postgres, you'll get a
Result
object that includes properties likerows
,fields
,rowCount
andcommand
. You'll need to extract the appropriate data from this response before returning it inside your resolver.Common Scenario #2: Array Instead of Object
What if we fetch a post from the database, our resolver might look something like this:
where
Post
is some model we're injecting through the context. If we're usingsequelize
, we might callfindAll
.mongoose
andtypeorm
havefind
. What these methods have in common is that while they allow us to specify aWHERE
condition, the Promises they return still resolve to an array instead of a single object. While there's probably only one post in your database with a particular ID, it's still wrapped in an array when you call one of these methods. Because an Array is still an Object, GraphQL will not resolve thepost
field as null. But it will resolve all of the child fields as null because it won't be able to find the appropriately named properties on the array.You can easily fix this scenario by just grabbing the first item in the array and returning that in your resolver:
If you're fetching data from another API, this is frequently the only option. On the other hand, if you're using an ORM, there's often a different method that you can use (like
findOne
) that will explicitly return only a single row from the DB (or null if it doesn't exist).A special note on
INSERT
andUPDATE
calls: We often expect methods that insert or update a row or model instance to return the inserted or updated row. Often they do, but some methods don't. For example,sequelize
'supsert
method resolves to a boolean, or tuple of the the upserted record and a boolean (if thereturning
option is set to true).mongoose
'sfindOneAndUpdate
resolves to an object with avalue
property that contains the modified row. Consult your ORM's documentation and parse the result appropriately before returning it inside your resolver.Common Scenario #3: Object Instead of Array
In our schema, the
posts
field's type is aList
ofPost
s, which means its resolver needs to return an Array of objects (or a Promise that resolves to one). We might fetch the posts like this:However, the actual response from our API might be an object that wraps the the array of posts:
We can't return this object in our resolver because GraphQL is expecting an Array. If we do, the field will resolve to null and we'll see an error included in our response like:
Unlike the two scenarios above, in this case GraphQL is able to explicitly check the type of the value we return in our resolver and will throw if it's not an Iterable like an Array.
Like we discussed in the first scenario, in order to fix this error, we have to transform the response into the appropriate shape, for example:
Not Using Promises Correctly
GraphQL.js makes use of the Promise API under the hood. As such, a resolver can return some value (like
{ id: 1, title: 'Hello!' }
) or it can return a Promise that will resolve to that value. For fields that have aList
type, you may also return an array of Promises. If a Promise rejects, that field will return null and the appropriate error will be added to theerrors
array in the response. If a field has an Object type, the value the Promise resolves to is what will be passed down as the parent value to the resolvers of any child fields.A Promise is an "object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value." The next few scenarios outline some common pitfalls encountered when dealing with Promises inside resolvers. However, if you're not familiar with Promises and the newer async/await syntax, it's highly recommended you spend some time reading up on the fundamentals.
Note: the next few examples refer to a
getPost
function. The implementation details of this function are not important -- it's just a function that returns a Promise, which will resolve to a post object.Common Scenario #4: Not Returning a Value
A working resolver for the
post
field might looks like this:getPosts
returns a Promise and we're returning that Promise. Whatever that Promise resolves to will become the value our field resolves to. Looking good!But what happens if we do this:
We're still creating a Promise that will resolve to a post. However, we're not returning the Promise, so GraphQL is not aware of it and it will not wait for it to resolve. In JavaScript functions without an explicit
return
statement implicitly returnundefined
. So our function creates a Promise and then immediately returnsundefined
, causing GraphQL to return null for the field.If the Promise returned by
getPost
rejects, we won't see any error listed in our response either -- because we didn't return the Promise, the underlying code doesn't care about whether it resolves or rejects. In fact, if the Promise rejects, you'll see anUnhandledPromiseRejectionWarning
in your server console.Fixing this issue is simple -- just add the
return
.Common Scenario #5: Not chaining Promises correctly
You decide to log the result of your call to
getPost
, so you change your resolver to look something like this:When you run your query, you see the result logged in your console, but GraphQL resolves the field to null. Why?
When we call
then
on a Promise, we're effectively taking the value the Promise resolved to and returning a new Promise. You can think of it kind of likeArray.map
except for Promises.then
can return a value, or another Promise. In either case, what's returned inside ofthen
is "chained" onto the original Promise. Multiple Promises can be chained together like this by using multiplethen
s. Each Promise in the chain is resolved in sequence, and the final value is what's effectively resolved as the value of the original Promise.In our example above, we returned nothing inside of the
then
, so the Promise resolved toundefined
, which GraphQL converted to a null. To fix this, we have to return the posts:If you have multiple Promises you need to resolve inside your resolver, you have to chain them correctly by using
then
and returning the correct value. For example, if we need to call two other asynchronous functions (getFoo
andgetBar
) before we can callgetPost
, we can do:Common Scenario #6
Before Promises, the standard way to handle asynchronous code was to use callbacks, or functions that would be called once the asynchronous work was completed. We might, for example, call
mongoose
'sfindOne
method like this:The problem here is two-fold. One, a value that's returned inside a callback isn't used for anything (i.e. it's not passed to the underlying code in any way). Two, when we use a callback,
Post.findOne
doesn't return a Promise; it just returns undefined. In this example, our callback will be called, and if we log the value ofpost
we'll see whatever was returned from the database. However, because we didn't use a Promise, GraphQL doesn't wait for this callback to complete -- it takes the return value (undefined) and uses that.Most more popular libraries, including
mongoose
support Promises out of the box. Those that don't frequently have complimentary "wrapper" libraries that add this functionality. When working with GraphQL resolvers, you should avoid using methods that utilize a callback, and instead use ones that return Promises.If you absolutely have to use a callback, you can also wrap the callback in a Promise: