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:

the “Cannot return null for non-nullable field” error came up both when the mutation failed… and when it succeeded

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!