View Source Our First Query
The first thing our viewers want is a list of our blog posts, so that's what we're going to give them. Here's the query we want to support:
{
posts {
title
body
}
}
To do this we're going to need a schema. Let's create some basic types
for our schema, starting with a :post
. GraphQL has several fundamental
types on top of which all of our types will be
built. The Absinthe.Type.Object
type is the right one
to use when representing a set of key value pairs.
Since our Post
Ecto schema lives in the Blog.Content
Phoenix
context, we'll define its GraphQL counterpart type, :post
, in a
matching BlogWeb.Schema.ContentTypes
module:
In blog_web/schema/content_types.ex
:
defmodule BlogWeb.Schema.ContentTypes do
use Absinthe.Schema.Notation
object :post do
field :id, :id
field :title, :string
field :body, :string
end
end
The GraphQL specification requires that type names be unique, TitleCased words. Absinthe does this automatically for us, extrapolating from our type identifier (in this case
:post
gives us"Post"
). If really needed, we could provide a custom type name as a:name
option to theobject
macro.
If you're curious what the type :id
is used by the :id
field, see
the GraphQL spec. It's
an opaque value, and in our case is just the regular Ecto id, but
serialized as a string.
With our type completed we can now write a basic schema that will let us query a set of posts.
In blog_web/schema.ex
:
defmodule BlogWeb.Schema do
use Absinthe.Schema
import_types BlogWeb.Schema.ContentTypes
alias BlogWeb.Resolvers
query do
@desc "Get all posts"
field :posts, list_of(:post) do
resolve &Resolvers.Content.list_posts/3
end
end
end
For more information on the macros available to build a schema, see their definitions in
Absinthe.Schema
andAbsinthe.Schema.Notation
.
This uses a resolver module we've created (again, to match the Phoenix context naming)
at blog_web/resolvers/content.ex
:
defmodule BlogWeb.Resolvers.Content do
def list_posts(_parent, _args, _resolution) do
{:ok, Blog.Content.list_posts()}
end
end
Queries are defined as fields inside the GraphQL object returned by
our query
function. We created a posts query that has a type
list_of(:post)
and is resolved by our
BlogWeb.Resolvers.Content.list_posts/3
function. Later we'll talk
more about the resolver function parameters; for now just remember
that resolver functions can take two forms:
- A function with an arity of 3 (taking a parent, arguments, and resolution struct)
- An alternate, short form with an arity of 2 (omitting the first parameter, the parent)
The job of the resolver function is to return the data for the
requested field. Our resolver calls out to the Blog.Content
module,
which is where all the domain logic for posts lives, invoking its
list_posts/0
function, then returns the posts in an :ok
tuple.
Resolvers can return a wide variety of results, to include errors and configuration for advanced plugins that further process the data.
If you're asking yourself what the implementation of the domain logic looks like, and exactly how the related Ecto schemas are built, read through the code in the absinthe_tutorial repository. The tutorial content here is intentionally focused on the Absinthe-specific code.
Now that we have the functional pieces in place, let's configure our Phoenix router to wire this into HTTP:
In blog_web/router.ex
:
defmodule BlogWeb.Router do
use BlogWeb, :router
pipeline :api do
plug :accepts, ["json"]
end
scope "/api" do
pipe_through :api
forward "/graphiql", Absinthe.Plug.GraphiQL,
schema: BlogWeb.Schema
forward "/", Absinthe.Plug,
schema: BlogWeb.Schema
end
end
In addition to our API, we've wired in a handy GraphiQL user interface to play with it. Absinthe integrates both the classic GraphiQL and more advanced GraphiQL Workspace interfaces as part of the absinthe_plug package.
Now let's check to make sure everything is working. Start the server:
$ mix phx.server
Absinthe does a number of sanity checks during compilation, so if you misspell a type or make another schema-related gaffe, you'll be notified.
Once it's up-and-running, take a look at http://localhost:4000/api/graphiql:
Make sure that the URL
is pointing to the correct place and press the play button. If everything goes according to plan, you should see something like this:
Next Step
Now let's look at how we can add arguments to our queries.