A beginner’s guide to GraphQL with Elm

Mario Martinez
6 min readJun 21, 2018

--

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.

Graphqelm has two parts — a CLI for generating Elm files from a GraphQL schema and an Elm API

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.

Our generated Elm files from the GitHub GraphQL API

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.Object
user : 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 at Github/Object/User.elm. It has information about decoding the GraphQL response. This is where we get the functions User.name and User.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 at Github/Object.elm. The second Github.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 Query
query : 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.name
type 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.

Main.elm

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.

--

--