elm-graphql logo

The Beginners Guide to elm-graphql

Samuel Wood
6 min readFeb 24, 2020

Recently I’ve been delving into Elm, dinking around with small personal projects with the hopes of transitioning the technology to something bigger in the future. I’ve ran into fairly big pain points that no amount of stack overflow or official Elm docs seemed to solve. Lots of these pain-points came from the fact that Elm 0.19 is fairly new. That, being paired with it’s relatively small community, creates a shortage in resources for newcomers of Elm. Elm is still a niche language. When you pair that niche language with an even more niche library, the exact documentation that you need can be quite difficult to come by. I spent many-a-hours, piecing together code snippets from year-old Medium articles and deprecated Github repos. I yearned for the tutorials that I will now go fourth and provide, hopefully offering somebody else the guidance they’re looking for.

Elm x GraphQl

One of the pain-points I found was implementing GraphQL. There are a few libraries I found searching around, but I settled fairly quickly on elm-graphql, written by Dillon Kearns. The most attractive feature from this library was the, presumably Elm-inspired, automatic type checking of your GraphQL schema. This type-safe attribute is a great way to continue the spirit of writing safe, working code, that we all love about Elm.

Lets Get Into It

This tutorial is modeled around this example found in the elm-graphql GitHub repo, but with the goal of adding more clarity and less noise. I found the official README.md to be slightly lacking when it came to a full implementation example. The repo’s example files were good, but not explained as well as I would’ve liked, as a newbie. With this walk-through, I hope we can get around that. I’ll get you from zero to hero and have your app running a successful elm-graphql query in just a few minutes.

Prerequisites:

  • Download Elm and be somewhat familiar with it.
  • That’s it.

Create an Elm project:

Set up a basic Elm project with an Main.elm file in your src directory. We won’t go into detail on how to do this in this tutorial but feel free to refer to this article, this package, or anything else you need to get your project bootstrapped.

Once you’re project is ready to go, we can begin.

You’ll need a few things at this point: the elm-graphql command line tool, as well as elm-graphl, and various other packages.

Install necessary packages:

elm install dillonkearns/elm-graphql
elm install elm/json
elm install krisajenkins/remotedata

Install the command line tool:

Official docs recommend installing it as a dev-dependency in your package.json. This allows your whole team to be working off the same version. However, given this won’t be going into a large team environment, we’re going to install it globally (this is the method I first used, as it is a more familiar method to me)

npm install -g @dillonkearns/elm-graphql

Now your project is all setup and ready to roll. As part of this library, we need to use the command line tool to generate type-safe Elm for us to refer to. This tool talks to your GraphQL endpoint, checks the schema, and generates elm code exclusively for this endpoint. With that being said, if your schema changes in the back end, you must rerun this command in order to generate new Elm code that incorporates these new changes.

Let’s run this command that auto-generates the accommodating elm code.

elm-graphql https://elm-graphql.herokuapp.com --base StarWars

If you look in your project, you should now see a StarWars directory in your src directory. We’ll reference this in Main.elm in a second. You won’t ever need to change any of this code, but it can be helpful to go look through if you confused along the way.

Now Let Have Some Fun

Setup the API-specific functions of our file

1: In our Main.elm, let’s delete all preexisting code start by importing everything we need:

import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import Html.Attributes exposing ( style )
import Graphql.Document as Document
import Graphql.Http
import Graphql.Http.GraphqlError
import Graphql.Operation exposing (RootQuery)
import Graphql.OptionalArgument exposing (OptionalArgument(..))
import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
import RemoteData exposing (RemoteData)
import StarWars.Interface
import StarWars.Interface.Character as Character
import StarWars.Query as Query

2: Let’s create a couple type aliases that essentially hold the same value for now.

...type alias CharacterAlias =
{ name : String
, friends : List String
}
type alias Response =
CharacterAlias

3: Now we create the elm-graphql equivalent of a standard GraphQL fragment:

...{-
`characterInfoSelection` below is equivalent to defining
a fragment like this in raw GraphQL:
fragment characterInfo on Character {
name
friends {
name
}
}
-}characterInfoSelection :
SelectionSet
CharacterAlias
StarWars.Interface.Character
characterInfoSelection =
SelectionSet.map2 CharacterAlias
Character.name
(Character.friends Character.name)

4: Now for a basic query. All this really does is call the “hero” query from our StarWars directory, allowing us to pass in our desired arguments:

...query : SelectionSet Response RootQuery
query =
-- We use `identity` to say that we aren't giving any
-- optional arguments to `hero`. Read this blog post for more:
-- https://medium.com/@zenitram.oiram/graphqelm-optional-arguments-in-a-language-without-optional-arguments-d8074ca3cf74
Query.hero identity characterInfoSelection

5: We need something to call our query, or rather apply it to the proper HTTP requests

...makeRequest : Cmd Msg
makeRequest =
query
|> Graphql.Http.queryRequest "https://elm-graphql.herokuapp.com"
|> Graphql.Http.send (RemoteData.fromResult >> GotResponse)

And now for the beef our file

1: Let’s define our model:

...type Msg = GotResponse Modeltype alias Model =
RemoteData (Graphql.Http.Error Response) Response

2: As per every elm module, we will need an init, update, and view. :

...init : () -> ( Model, Cmd Msg )
init _ = ( RemoteData.Loading, makeRequest )
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GotResponse response ->
( response, Cmd.none )
view : model -> Html Msg
view model =
div [style "padding" "30px"] [text (Debug.toString model)]

Notice, the Cmd Msg is where the magic happens. On initial load, the init function calls our makeRequest function, which executes all our defined-above API logic for us. The update function is called, updating our Model to the API response. The view simply takes our model, which is of type RemoteData, and converts it to a string via the standard Debug module

3: Let’s add an empty subscriptions function and use Browser’s element to connect all of our code:

...subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
main =
Browser.element { init = init, update = update, view = view, subscriptions = subscriptions }

4: Now, if you run ‘elm reactor’ in your terminal, and navigate to localhost:8000/src/Main.elm, you’ll see a beautiful, functioning elm x elm-graphql module.

Final File

module Main exposing (..)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import Html.Attributes exposing ( style )
import Graphql.Document as Document
import Graphql.Http
import Graphql.Http.GraphqlError
import Graphql.Operation exposing (RootQuery)
import Graphql.OptionalArgument exposing (OptionalArgument(..))
import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
import RemoteData exposing (RemoteData)
import StarWars.Interface
import StarWars.Interface.Character as Character
import StarWars.Query as Query
type alias CharacterAlias =
{ name : String
, friends : List String
}
type alias Response =
CharacterAlias
query : SelectionSet Response RootQuery
query = Query.hero identity characterInfoSelection
characterInfoSelection :
SelectionSet
CharacterAlias
StarWars.Interface.Character
characterInfoSelection =
SelectionSet.map2 CharacterAlias
Character.name
(Character.friends Character.name)
makeRequest : Cmd Msg
makeRequest =
query
|> Graphql.Http.queryRequest "https://elm-graphql.herokuapp.com"
|> Graphql.Http.send (RemoteData.fromResult >> GotResponse)
type Msg
= GotResponse Model
type alias Model =
RemoteData (Graphql.Http.Error Response) Response
init : () -> ( Model, Cmd Msg )
init _ =
( RemoteData.Loading, makeRequest )
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GotResponse response ->
( response, Cmd.none )
view : model -> Html Msg
view model =
div [style "padding" "30px"] [text (Debug.toString model)]
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
main =
Browser.element { init = init, update = update, view = view, subscriptions = subscriptions }

Thanks for reading folks! I sure hope I helped somebody out here. I’d love to hear feedback if anybody feels the urge to leave a response. Always open to criticism, bugs, and whatever else you want to throw at me!

--

--