A beginner’s guide to GraphQL with Elm
Edit 10th September 2018
This post was written for Elm 0.18 and with the release of Elm 0.19, quite a few things changed. Most notably, Graphqelm was renamed dillonkearns/elm-graphql. I believe this post is somewhat useful but may be confusing in places because of the changes that Elm 0.19. I intend to one day update this post for Elm 0.19 but please be mindful that if you follow what’s been written verbatim, it will not work. With that caveat, please enjoy this post!
Introduction
Let me ask you a question: what do you most like about working with Elm? Is it the fact that there’s a notion of “if it compiles, it works”? In this post we’re going to talk about writing Elm programs with a new Elm GraphQL library where if it compiles, not only does it just work, the GraphQL queries you write in your program are valid according to the GraphQL schema!
So how does it work? The dillonkearns/graphqelm package has two parts — the command line interface (CLI) and the library that you use in your Elm program.
Generating your library
This is dead easy— you just need to point this at the GraphQL API that you would like to use.
Get the CLI of graphqelm from npm by running the following command.
$ npm i -g graphqelm
Although you can run the command anywhere, it’s most useful in an Elm project. Let’s go ahead and create a new Elm project. I like to use elm-new
to quickly get a project up and running. You can install it by running:
$ npm i -g elm-new
Now you can create and open a new Elm project with:
$ elm-new my-graphqelm-project
$ cd my-graphqelm-project
Running the Graphqelm CLI
We’re now ready to run our graphqelm CLI. Let’s point it at the GitHub GraphQL API. If you’re following along, you’ll need to obtain an API key from GitHub.
Executing the following command in your terminal will generate an Elm library based on the GitHub GraphQL schema.
$ graphqelm https://api.github.com/graphql --base Github --header 'Authorization: Bearer <your GitHub API token>'
Let’s talk a little bit about the options. The --header
allows you to set http headers for the request. In the case of GitHub, an authorisation header is required. The --base
flag allows you to set a namespace for the generated API. If we had left this blank, our generated files would use the name Api
rather than Github
.
OK, now take a look at your directory and if all went well, you’ll see a set of generated files.
So that’s cool but how do you use it?
Using Graphqelm in your Elm project
Installing Graphqelm for your Elm project
In the previous step, we generated an Elm library for the GitHub GrapqhQL API but we still need to install the Graphqelm library itself.
$ elm-package install dillonkearns/graphqelm
Our first query
Let’s start off with something nice and simple and try to implement the following GraphQL query.
query {
user (login: "octocat") {
name
}
}
This query is almost self explanatory — we want to fetch the name of the GitHub user with the login of octocat.
If we test the above query in GitHub’s graphiql explorer, we get the following response.
{
"data": {
"user": {
"name": "The Octocat"
}
}
}
Writing some Elm
I’m going to assume that you know a little Elm but if you’re reading this with no prior knowledge, I hope that you’ll still be able to follow along.
Let’s comment out the code generated by elm-new
in our main.elm
file (we’ll need that stuff later) and add the following at the top.
type alias User = { name : Maybe String }
This is our first line. We might get back a user’s display name, we might not. Since Elm doesn’t handle nil
or null
values like many languages, we need to use a Maybe
type.
At this point, you might be wondering:
How did you know to use a
Maybe
? And how did you know that the return type is going to be a string?
If we take a peak at the library we generated and go to Github/Object/User.elm
and take a look at the type definition for name
, we will find that it is:
name : FieldDecoder (Maybe String) Github.Object.User
And that’s how we know that name
will return a Maybe String
.
Let’s add the Response
type alias.
type alias Response = { user : Maybe User }type alias User = { name : Maybe String }
The response is also a Maybe type. After all, what would happen if we said that we’re looking for a user with the login mister-grumpy-kitten
and they didn’t exist?
Creating our query in Elm
We need a function that can map GitHub’s GraphQL response to the User
type alias that we defined in the previous section. Add this to the top of the file.
import Graphqelm.SelectionSet exposing (SelectionSet, with)
import Github.Object.User as User
import Github.Objectuser : SelectionSet User Github.Object.User
user =
User.selection User
|> with User.name
When I first started looking at this, it was confusing because there are names that look similar. 😕 It’s because Elm’s syntax allows for modules and types to be the same name.
Github.Object.User
is a module from the library that we generated. It’s a file which you can find atGithub/Object/User.elm
. It has information about decoding the GraphQL response. This is where we get the functionsUser.name
andUser.selection
.Github.Object
is another module from the library that we generated and defines the type constructors for the all the object types that are defined by the GitHub GraphQL schema. It’s located atGithub/Object.elm
. The secondGithub.Object.User
is a type from this module.- All the other references to
User
refer to the type alias that we created earlier.
We’ve defined our types and now we can use Graphqelm to construct a query.
module Main exposing (main)import Graphqelm.Operation exposing (RootQuery)
import Graphqelm.SelectionSet exposing (SelectionSet, with)
import Github.Object.User as User
import Github.Object
import Github.Query as Queryquery : SelectionSet Response RootQuery
query =
Query.selection Response
|> with (Query.user { login = "octocat" } user)user : SelectionSet User Github.Object.User
user =
User.selection User
|> with User.nametype alias Response = { user : Maybe User }type alias User = { name : Maybe String }
The query
function maps our Response
type alias to that of GitHub’s GraphQL response.
Sending our GraphQL query
Well, that’s all well and good but it’s not much use if we there isn’t a way to send it to the server and get data back. The bit of code of that sends the request looks like this.
import Graphqelm.HttpmakeRequest : Cmd Msg
makeRequest =
query
|> Graphqelm.Http.queryRequest
"https://api.github.com/graphql"
|> Graphqelm.Http.withHeader "authorization" "Bearer <your github bearer token>"
|> Graphqelm.Http.send GotResponse
GotResponse
is one of the options in the Msg
type. For those of you new to Elm, this is a type that just about every Elm program has for the update
function.
type Msg
= GotResponse (Result (Graphqelm.Http.Error Response) Response)
This is similar to the HTTP example in the Elm guide except that we’re using Graphqelm.Http.Error
instead of Http.Error
. You might be wondering why Response
appears twice. In short, it’s so that if there is an error, you can still use the response structure. Interested readers can read more about it here.
Using RemoteData
In the Graphqelm examples, rather than use Result
, RemoteData
is used. If you think about it, there’s four states our request could have:
- the request hasn’t been sent (not asked state)
- the request has been sent and we’re waiting for a response (loading state)
- the response was successful (success state)
- there was an error (failure state)
The RemoteData type accounts for these states rather than just having a success or error scenario. You can see below how this is implemented.
Putting it all together
Putting all of the above together with our usual band of Elm functions such as update
, view
and init
, we get the following code.
If all goes, well you’ll get back the name of GitHub user named Octocat.
This GraphQL query was as simple as it gets. In the next post, we’ll build on this example and look at something a bit more complex.