Writing Schemas

A GraphQL API starts by building a schema. Using Absinthe, schemas are normal modules that use Absinthe.Schema.

Here’s a schema that supports looking up an item by ID:

myapp/schema.ex
defmodule MyApp.Schema do

  use Absinthe.Schema

  # Example data
  @items %{
    "foo" => %{id: "foo", name: "Foo"},
    "bar" => %{id: "bar", name: "Bar"}
  }

  query do
    field :item, :item do
      arg :id, non_null(:id)
      resolve fn %{id: item_id}, _ ->
        {:ok, @items[item_id]}
      end
    end
  end

end

You may want to refer to the Absinthe API documentation for more detailed information as you look this over..

Some macros and functions used here that are worth mentioning, pulled in automatically from Absinthe.Schema.Notation by use Absinthe.Schema:

You’ll notice we mention some types being referenced: :item and :id. :id is a built-in scalar type (like :string, :boolean, and others), but :item we need to define ourselves.

We can do it in the same MyApp.Schema module, using the object macro defined by Absinthe.Schema.Notation:

myapp/schema.ex
@desc "An item"
object :item do
  field :id, :id
  field :name, :string
end

Now, you can use Absinthe to execute a query document. Keep in mind that for HTTP, you’ll probably want to use Absinthe.Plug instead of executing GraphQL query documents yourself. Absinthe doesn’t know or care about HTTP, but the absinthe_plug project does – and handles the vagaries of interacting with HTTP GraphQL clients so you don’t have to.

If you were executing query documents yourself (lets assume for a local tool), it would go something like this:

"""
{
  item(id: "foo") {
    name
  }
}
"""
|> Absinthe.run(MyApp.Schema)

# Result
{:ok, %{data: %{"item" => %{"name" => "Foo"}}}}

Importing Types

We could also move our type definitions out into a different module, for instance, MyApp.Schema.Types, and then use import_types in our MyApp.Schema:

myapp/schema.ex
defmodule MyApp.Schema do
  use Absinthe.Schema

  import_types MyApp.Schema.Types

  # ...

end

It’s a nice way of separating the top-level query and mutation information, which define the surface area of the API, with the actual types that it uses.