Creating a GraphQL field without a resolver with Absinthe

Hey, all! I’m stumped with what seems like a fairly simple issue. I’m using Phoenix and Absinthe to set up a GraphQL backend. Ideally, I’d like to have a structure that looks like this:

{
  posts {
    published {
      title
    }
    draft {
      title
    }
  }
}

To do so, I thought I would need to delegate the posts field in my schema to an object that responded to published and draft . I did this like so:

# In my Schema
field :posts, type: :posts_by_state

# In my Post type definitions
object :post do
  # ...
end

object :posts_by_state do
  field :published, list_of(:post) do
    resolve fn _, _, _ -> ... end
  end

  field :draft, list_of(:post) do
    resolve fn _, _, _ -> ... end
  end
end

This didn’t work and instead returns null for the entire posts field. However, if I changed the posts field in the schema to include a “blank” resolver, it worked as expected:

field :posts, type: :posts_by_state do
  resolve fn _, _, _ -> {:ok, []} end
end

Is this best practice or is there a better way to tell a field to delegate entirely to an object? More generally, is there a better way to structure this?

Hey @kieraneglin, good question. The behavior you’re seeing here is expected, although usually the dummy resolver you’d use is:

field :posts, type: :posts_by_state do
  resolve fn _, _, _ -> {:ok, %{}} end
end

If you don’t specify a resolver, Absinthe will use the default resolver, which does a Map.get(parent_value, field_name). In your case if the root value is %{} (which it is by default) then if you don’t specify a posts field resolver Absinthe does Map.get(%{}, :posts) which of course returns nil. Since the field returns nil, no sub fields are executed.

Really though, these seem more like things that should be arguments IE:

{
  published: posts(status: PUBLISHED) { title }
  draft: posts(status: DRAFT) { title }
}
4 Likes

Thank you for the thorough answer! I agree with your point about these becoming arguments. However, I ran into a problem: I wanted to use some authorization middleware that would only allow authors to see all draft posts (this example is getting contrived, but I promise we’re trying to solve a similar issue in practice). I imagined something like this:

object :posts_by_state do
  field :published, list_of(:post) do
    # Everyone can access this!
    resolve fn _, _, _ -> ... end
  end

  field :draft, list_of(:post) do
    # Only `authors` can access this
    middleware(Middleware.Authorization, :author)

    resolve fn _, _, _ -> ... end
  end
end

It is still viable to accomplish this with arguments or should I be approaching this problem in a different way?

Sorry to bump, but I’m still unsure about my follow up question re: serving up similar endpoints based on permission level. Do you have any advice on the matter?

Thank you!