Solving “Cannot return null for non-nullable field” error with GraphQL API using Kronky, Absinthe & Elixir
I was scratching my head for weeks trying to figure out why my Absinthe GraphQL API mutations weren’t resolving the right output. It turns out that it was an easy fix.
⁉️ The problem
Right now I’m building a GraphQL API for a project using Elixir’s Absinthe library. I’m using the Kronky library to help resolve useful error messages when a mutation fails.
All of the queries worked as expected, but the mutations always returned the same error, even when the mutation succeeds (!).
This is what it returned when I call the updatePost mutation:
Here’s what that error looks like:
{
"data": {
"updatePost": null
},
"errors": [
{
"locations": [
{
"column": 0,
"line": 7
}
],
"message": "Cannot return null for non-nullable field",
"path": [
"updatePost",
"successful"
]
}
]
}
My configuration
Here’s my current configuration (in case it helps you with debugging):
🎉 The simple solution
After leaving the problem for a couple of weeks while I dealt with other commitments, I figured that today I’d spend my train journey figuring it out.
A couple of hours later, it worked! I found the solution when reading through Matthew Segret’s excellent Yummy Phoenix GraphQL project on GitHub. Full credit to Matthew Segret.
The problem: I was missing a middleware
function in my lib/my_app_web/schema.ex
file. This is what I had to add:
alias MyAppWeb.Schema.Middleware.TranslateMessages
def middleware(middleware, _field, %Absinthe.Type.Object{identifier: :mutation}) do
middleware ++ [&build_payload/2, TranslateMessages]
end
def middleware(middleware, _field, _object) do
middleware
end
It looks like this middleware function translates the response from a mutation’s resolver function (like {:ok, post}
) to a Kronky payload (which contains three fields: result
, successful
, and messages
).
For this middleware to work, you need a TranslateMessages module. Here’s what TranslateMessages looks like in lib/my_app_web/schema/middleware/translate_messages.ex
:
defmodule MyAppWeb.Schema.Middleware.TranslateMessages do
@behaviour Absinthe.Middleware
def call(%{value: value} = resolution, _config) do
result = do_translate_messages(value)
Absinthe.Resolution.put_result(resolution, {:ok, result})
end
defp do_translate_messages(%Kronky.Payload{} = payload) do
Map.update!(payload, :messages, fn messages ->
Enum.map(messages, &translate_message/1)
end)
end
defp do_translate_messages(value), do: value
defp translate_message(%Kronky.ValidationMessage{} = validation_message) do
opts = Map.get(validation_message, :options)
template = Map.get(validation_message, :template)
Map.update!(validation_message, :message, fn _message ->
Gettext.dgettext(MyAppWeb.Gettext, "errors", template, opts)
end)
end
end
This seems to be quite a niche problem (I only found a handful of posts mentioning the error message online, and they were usually related to Apollo Server and problems with resolver functions), but if anyone else out there runs into the same issue, hopefully this’ll make it an easy fix!
If you have any trouble with this, please post in the comments below. I’ll do my best to help.
Good luck and happy Elixir-ing!