Published

Phoenix GraphQL Tutorial with Absinthe

This post is a tutorial on using Phoenix and GraphQL to build a clean and powerful API with Elixir. We will start with a fresh Phoenix application and walk through turning it into a GraphQL server step-by-step.

Initial Setup

I recommend following along with a fresh Phoenix install and will assume that you’ve already run
mix phoenix.new and have an app ready to go. I’m going to use Ecto with Postgres in this post but you are welcome to use any database you’d like. You’ll just need to modify the resolver code we write later on in order to return data from your particular data source.

We are going to need a few different libraries to get everything setup so let’s go ahead and add them now. Add the following libraries to your deps and list of applications:

After updating your
mix.exs file run
mix deps.get to pull in all the new libraries.

This is a quick summary of what we just added:

Absinthe

The
absinthe ,
absinthe_plug , and
absinthe_ecto libraries are the specific GraphQL libraries that I’ve chosen to work with. There are a couple Elixir GraphQL libraries but I’ve found these to be the best and easiest to work with. We’ve also added
poison because it is required by
absinthe_plug .

I’ve chosen this blog-like data model in order to demonstrate using GraphQL to query data that has associations.

Our next step is to make a simple change to the
user model in order to take advantage of Ecto’s association querying. Open up your
user model and add the following line:

web/models/user.ex

1

2

3

4

5

6

7

schema"users"do

field:name,:string

field:email,:string

has_many:posts,MyApp.Post

timestamps()

end

Let’s also generate some seed data that we can query later on. Add this code to your
priv/repo/seeds.exs file:

priv/repo/seeds.exs

1

2

3

4

5

6

7

8

9

10

11

12

13

14

alias MyApp.User

alias MyApp.Post

alias MyApp.Repo

Repo.insert!(%User{name:"Ryan Swapp",email:"ryan@ryan.com"})

Repo.insert!(%User{name:"Rosie",email:"rosie@mydog.com"})

for_<-1..10do

Repo.insert!(%Post{

title:Faker.Lorem.sentence,

body:Faker.Lorem.paragraph,

user_id:[1,2]|>Enum.take_random(1)|>hd

})

end

Run
mix run priv/repo/seeds.exs to fill your database with some data and we should be good to go.

GraphQL Types

GraphQL Types are similar to a database schema. However, GraphQL Types do not need to represent the actual structure of the data in your database. They are extremely flexible and allow you to do things like creating data structures that represent data from a number of data sources or even virtual data fields that are temporary like a login token. The GraphQL spec is pretty easy to read so if you’d like to know more about types head on over to the type system description.

Let’s start building out the GraphQL Types for our user and post data. Create a new file at
web/schema/types.ex and add the following code:

web/schema/types.ex

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

defmodule MyApp.Schema.Types do

useAbsinthe.Schema.Notation

useAbsinthe.Ecto,repo:MyApp.Repo

object:user do

field:id,:id

field:name,:string

field:email,:string

field:posts,list_of(:post),resolve:assoc(:posts)

end

object:post do

field:id,:id

field:title,:string

field:body,:string

field:user,:user,resolve:assoc(:user)

end

end

At the top of the file we pull in the
Absinthe.Schema.Notation module which provides us with a DSL for defining our GraphQL Types. We also use
Absinthe.Ecto to enable helpers for batching association requests. The next bit of code defines our types. As you can see, we define both a
user and
post type. Each of these are represented as a GraphQL Object. Here is a quick definition from the spec:

GraphQL Objects represent a list of named fields, each of which yield a value of a specific type.

A few things to note here are that the
id fields have a special type of
id which represents a unique identifier. This can be used for things like caching and other fun stuff. The other fields have the scalar type
:string . There are a number of predefined scalar types and you can also add your own. So, for instance, you could create a
:url scalar type that ensures that a particular field is a valid URL. You’ll also notice the use of the
resolve and
assoc helpers on the
user.posts field and the
post.user field. These are really slick utility functions from the
Absinthe.Ecto library that allow us to batch requests in order to avoid N+1 queries.

With that we’ve got our types all setup. Let’s move on to building out our schema.

GraphQL Schemas

The GraphQL Schema is where we define queries that a client can access. Each query is represented as a field and you describe how that query will get its data in the form of a resolver function. Let’s dive into our schema to see what this looks like. Add a file at
web/schema.ex and add the following code:

web/schema.ex

1

2

3

4

5

6

7

8

9

10

11

12

13

14

defmodule MyApp.Schema do

useAbsinthe.Schema

import_types MyApp.Schema.Types

query do

field:posts,list_of(:post)do

resolve&MyApp.PostResolver.all/2

end

field:users,list_of(:user)do

resolve&MyApp.UserResolver.all/2

end

end

end

At the top we pull in
Absinthe.Schema which provides us with a DSL for building out our queries. We also use the
import_types helper to pull in the types that we previously defined. This utility function is very useful for breaking up your code into manageable pieces. In a real application your types are likely going to be huge. You can separate each of these types into their own files and then pull them into your schema with the
import_types helper.

The main portion of this file is the query definition. We define fields for
:posts and
:users and then pass our
MyApp.PostResolver.all/2 and
MyApp.UserResolver.all/2 functions to the
resolve helper. These two fields are similar to an index function within a phoenix controller in that they return a list of a particular resource. Within each of these resolver functions we will describe how to get our data. Let’s build those next.

GraphQL Resolvers

In GraphQL, resolvers describe how to get data for a particular query. This is where you can use a library like Ecto to get data or, in the case of a database unsupported by Ecto, you can use a database client directly to return data. Let’s create two new files at
web/resolvers/post_resolver.ex and
web/resolvers/user_resolver.ex and add the following code:

web/resolvers/post_resolver.ex

1

2

3

4

5

6

7

8

defmodule MyApp.PostResolver do

alias MyApp.Repo

alias MyApp.Post

def all(_args,_info)do

{:ok,Repo.all(Post)}

end

end

web/resolvers/user_resolver.ex

1

2

3

4

5

6

7

8

defmodule MyApp.UserResolver do

alias MyApp.Repo

alias MyApp.User

def all(_args,_info)do

{:ok,Repo.all(User)}

end

end

Repo.all is going to return a list of Ecto structs for each resource but the important thing to note is that you just need to return a list of maps or structs. If you are using, for instance, the RethinkDB Elixir library you could just return a list of maps returned by your RethinkDB client. The reason we need to return a list here is that in our query field we stated that we would return a
list_of(:user) or
list_of(:post) . If we want to return just one
post or one
user we could have defined a field like so:

web/schema.ex

1

2

3

4

field:user,type::user do

arg:id,non_null(:id)

resolve&MyApp.UserResolver.find/2

end

In this example we are passing an
id argument to the
MyApp.UserResolver.find/2 function in order to return just one
user . The resolver function would look something like this:

web/resolvers/user_resolver.ex

1

2

3

4

5

6

deffind(%{id:id},_info)do

caseRepo.get(User,id)do

nil->{:error,"User id #{id} not found"}

user->{:ok,user}

end

end

With that we have all the key pieces in place.

GraphiQL and Routing

In order to play around with our new GraphQL server we are going to add GraphiQL and setup our router to point requests to the new server. Open up
web/router.ex and add the following code:

web/router.ex

1

2

3

4

5

6

7

8

9

10

11

scope"/",MyApp do

pipe_through:browser# Use the default browser stack

get"/",PageController,:index

end

forward"/api",Absinthe.Plug,

schema:MyApp.Schema

forward"/graphiql",Absinthe.Plug.GraphiQL,

schema:MyApp.Schema

Now, GraphQL requests can be made to
/api and we can go to
http://localhost:4000/graphiql to play around in the GraphiQL data explorer. Start your server
mix phoenix.server and go to that url in your browser. You should see something like this:

Let’s go ahead and add our first query. Type the following query into the query box and hit the play button:

1

2

3

4

5

6

{

posts{

title,

body

}

}

If we want to get the user of each post we could run the following query:

1

2

3

4

5

6

7

8

9

{

posts{

title,

body,

user{

name

}

}

}

We can also query for users and their posts:

1

2

3

4

5

6

7

8

{

users{

name,

posts{

title

}

}

}

Pretty neat, eh?

One thing to note here is that our server will return data in the exact shape of our query. This is one of the main benefits of GraphQL and allows you to get exactly the data that you need for any particular scenario.

Conclusion

I hope this tutorial was helpful. If you have any issues or just want to say hey feel free to leave a comment below. Also, I’ve created a repo for this tutorial on my github that you can find here. This post ends at the branch checkpoint-1.