Absinthe: Cannot return null for non-nullable field

I have this error when the client asks for an entity with an association, and I didn’t preload the association.

Example:
Client sends query document:

user {
  name
  group {
    id
  }
}

A user optionally belongs to a group.
If I return the User without preloading the group:

def create_user(attrs) do
  %User{}
  |> User.changeset(attrs)
  |> Repo.insert()
end

The following error is returned to the client:

  1. locations: [{column: 11, line: 33}]
  2. message: “Cannot return null for non-nullable field”
  3. path: [“createUser”, “user”, “group”, “id”]

But with preload it will work (even when the user has no group, and thus resolves to null as well):

def create_user(attrs) do
  %User{}
  |> User.changeset(attrs)
  |> Repo.insert()
  |> case do
       {:ok, user} ->
         {:ok, Repo.preload(user, [:group])}
       error ->
         error
     end
end

In the schema, there is no non_null constraint for the group:

  object :user do
    field :group, :user_group
  end

  object :user_group do
    field :id, non_null(:id)
  end

Question: why without preloading do I obtain the error? group can be null anyway from the Absinthe schema, so why does preloading matter?

When an association is not loaded the field defaults to an %Ecto.Association.NotLoaded{} struct. So in your case the group field of the %User{} struct defaults to %Ecto.Association.Notloaded{}.

Once you call Repo.preload/2 the group field is resolved and replaced with either the associated %Group{} struct or nil (in case of a belongs_to association).

Then Absinthe is first going to check if the group field is nil in which case it will do nothing. If the field is an %Ecto.Association.Notloaded{} struct, Absinthe will call Map.get/2 to get the value for the :id field on that struct. However these structs have no :id field and the Map.get/2 call is going to return nil. But the :id field is non-nullable, so it blows up.

I.e. Absinthe sees that there’s a map (or a struct in this case) in the group field, and then grabs the defined object fields from it.

2 Likes

@benwilson512 if a field is a Ecto.Association.Notloaded struct, can’t we resolve it to nil?
Currently the resolver tries to fetch data from that struct.

Sometimes I don’t want to preload associations, for example because I know this optional association is nil. But I still have to execute extra SQL to get rid of the Ecto.Association.Notloaded in the struct.

I can always manually replace the Notloaded struct by nil. But then I wondered, why not change all Notloaded associations to nil in Absinthe :thinking: