Search code examples
graphqlelixirdgraphabsinthe

Absinthe result has all null values


I'm totally new to Elixir, Phoenix, and Absinthe...so go easy on me. :)

I'm experimenting with a graph database called Dgraph using a library called Dlex. I've written a simple query designed to look up and return a list of users (I only have two users at the moment):

def list_users() do
  query = """
    {
      users(func: type(User)) {
        uid
        email
        name
      }
    }
  """

  {:ok, %{"users" => result}} = Dlex.query(:dlex, query)

  IO.inspect(result)

  {:ok, result}
end

The output from IO.inspect(result) is precisely what I expect and want—a list of my two users:

[
  %{"email" => "[email protected]", "name" => "Rob", "uid" => "0x1"},
  %{"email" => "[email protected]", "name" => "Bridget", "uid" => "0x2"}
]

However, when I run this query with GraphiQL, all the values in the result are null for some reason:

{
  "data": {
    "users": [
      {
        "email": null,
        "name": null,
        "uid": null
      },
      {
        "email": null,
        "name": null,
        "uid": null
      }
    ]
  }
}

Any idea what I'm doing wrong?


Solution

  • It looks to me like you could be returning the appropriate data in your user resolver, with one exception: when Absinthe goes to resolve the fields for each user, the default resolver only looks for the fields as atom keys. When it can't find :uid, :email, or :name in the parent maps, it returns nil.

    You could convert the result to have atoms as keys. One option if you want to go that route would just be to map over each user and explicitly copy what you want.

    users = Enum.map(users, &%{uid: &1["uid"], email: &1["email"], name: &1["name"]})
    

    That is one more place you'd need to update when keys are added, though. And a lot of attempts at more dynamic solutions don't follow best practices (they open the app up to creating new atoms at runtime, which is a bad idea, or throwing errors when they see unrecognized keys).

    One solution I've used in the past is creating my own default MapGet middleware that checks for both atom and string keys.

    defmodule MyAppWeb.GraphQL.Middleware.MapGet do
      @moduledoc """
      Default resolver that checks for both atom and string keys.
      """
      @behaviour Absinthe.Middleware
    
      @impl Absinthe.Middleware
      def call(%{source: source} = info, key) do
        value =
          with :error <- Map.fetch(source, key),
               :error <- Map.fetch(source, to_string(key)) do
            nil
          else
            {:ok, value} ->
              value
          end
    
        %{info | state: :resolved, value: value}
      end
    end
    

    The Absinthe docs describe swapping out the default middleware. In my app it was pretty simple.

    defmodule MyAppWeb.GraphQL.Schema do
      use Absinthe.Schema
      # ...
    
      def middleware(middleware, field, object) do
        map_get = {{MyAppWeb.GraphQL.Middleware.MapGet, :call}, field.identifier}
        Absinthe.Schema.replace_default(middleware, map_get, field, object)
      end
    
      # ...
    end