---
title: Querying with GraphQL optional arguments in Elm
teaser: 'How do optional arguments translate to a language with no such concept?

  '
tags: elm,graphql
author: Amanda Beiner
published_on: 2020-05-15
---

I was recently working on integrating an Elm app with a Node GraphQL API. I had
previously worked on a React/TypeScript project with a GraphQL API and had a
great developer experience using generated types from [Apollo Tooling]'s CodeGen
CLI. It automatically generated types for query response based on the GraphQL
operations used in the app. This meant that we didn't have to manually write
types for every GraphQL fragment used throughout the app. 

I was looking for something similar for elm, and used [dillonkearns/elm-graphql]
on the recommendation of several colleagues. In the end, the development
experience wasn't very similar to Apollo codegen— it was game changing.

## Elm and GraphQL: A Love Story ❤️

Pairing GraphQL's schema definition language with a declarative and strongly 
typed language like Elm in the client enhances the benefits of both by
enforcing the contract between the API and the client. By [introspecting on the 
schema], we can infer:

* What queries and mutations we have access to
* What information those queries and mutations need in order to run successfully
* What the response will look like

Leveraging Elm's robust type system means that we can automate how to build
query and mutation functions and how to decode their responses. As a result, we
can be sure that if our code is compiling, the query is:

* Being constructed correctly
* Receiving all necessary arguments
* Responding predictably 
* Decoded correctly

## Building a GraphQL query in Elm

For an evergreen To Do app, our GraphQL mutation to create a new `Todo` might 
look something like this:

```javascript
mutation {
  createTodo(title: "Walk the pupper") {
    id
    title
  }
}

```

We can generate types based on that schema by running `elm-graphql
https://example.com/graphql` against our GraphQL endpoint. You'll notice that
elm-graphql created an `Api` directory with a bunch of pre-generated files. Using
those functions, we can write an Elm query that looks like this:

```elm
type alias Todo =
  { id : Api.Scalar.Id
  , title : String
  }

mutation : String -> Cmd Msg
mutation title =
  Api.Mutation.createTodo { title = title } todoFragment
    |> Graphql.Http.queryRequest "https://example.com/graphql"
    |> Graphql.Http.send TodoCreated

todoFragment : Graphql.SelectionSet.SelectionSet Todo Api.Object.Todo
todoFragment =
  Graphql.SelectionSet.map2 Todo
    Todo.id
    Todo.title

```

Here, we define a `Todo` type alias, which represents the shape to which our
decoded response value should conform. The `Mutation` module has a function
`createTodo` that was generated from our schema. We pass the `createTodo`
function a record containing arguments for the mutation, as well as a
`todoFragment` that describes that values that we would like to get back.

Our `todoFragment` is an `elm-graphql` [`SelectionSet`], which takes [two type
variables]. The first parameter is the kind of data structure we want to receive
after running our mutation. In this case, it's the `Todo` type alias that we
defined above. The second type parameter, often called the "typelock", lets the
compiler know what selection sets are valid to compose together. In this case,
we're using the generated `Api.Object.Todo` type.

Once we've passed in our required arguments and defined what fields we want
returned, we have `Graphql.Http` execute that query, ultimately returning the
`TodoCreated` message with the decoded query result. 

That's it. We've successfully abstracted away the implementation details of 
building and decoding a query.

## Handling Optional Arguments

Let's say we got feedback that users wanted to be able to add notes to their 
`Todo`s so that they can add links or other necessary information. We update the
GraphQL schema to have a nullable `notes` field.

```javascript
type Mutation {
  createTodo(title: String!, notes: String) : Todo
}

```

The `notes` argument is optional. We can tell because it doesn't have a
`!` in the schema definition. How do we handle optional arguments in a language
without a concept of optional arguments?

### Types of nothing: a quick aside

The [GraphQL spec] identifies two different ways to represent the lack of a 
value: it can be explicitly `null`, or just _missing_. Which means that there
are three different ways we might want to execute our `createTodo` mutation:
with a `notes` value, without a `notes` value, or with a null `notes` value.

```javascript
// With a notes
mutation {
  createTodo(title: "Walk the pupper", notes: "5 laps around the block" ) {
    // desired fields here
  }
}

// Witout a notes
mutation {
  createTodo(title: "Walk the pupper") {
    // desired fields here
  }
}

// Null notes
mutation {
  createTodo(title: "Walk the pupper", notes: null) {
    // desired fields here
  }
}
```

In this case, I have a specific goal in mind that I want to remember, so I
create a `Todo` with a title and a notes field:

```javascript
mutation {
  createTodo(title: "Walk the pupper", notes: "5 laps around the block" ) {
    // desired fields here
  }
}
```

But oh no! I forgot that I actually have 3 puppers, and I want to make sure that
I walk them all. So I want to execute a mutation to change the title of my
`Todo` to account for all of my puppers. I don't want to pass a value in for the
notes, so I just set the argument to `null`

```javascript
mutation {
  editTodo(id: 1, title: "Walk all three of the puppers", notes: null) {
    id
    title
    notes
  }
}
```

The response from this mutation will look something like:

```javascript
{
  "data": {
    "todo": {
      id: 1,
      title: "Walk all three of the puppers",
      "notes": "null"
    }
  }
}
```

I've lost my note that I want to walk five laps! Differentiating between an
argument that is `null` and an argument that is missing allows us to update data
without having to re-enter all of the data we want to keep the same. Instead of
having to re-write my note about how many laps I want to do, I can simply say:

```javascript
mutation {
  editTodo(id: 1, title: "Walk all three of the puppers") {
    id
    title
    notes
  }
}
```

In this case, we haven't passed a value for `notes`, because we just want 
to leave it alone. If I later decide I want to delete my notes, I have the
option of setting it to `null` explicitly.

`dillonkearns/elm-graphql` represents these three states through the 
[`OptionalArgument`] type, which is defined as:

```elm
type OptionalArgument a
    = Present a
    | Absent
    | Null
```

In the case of creating or editing a `Todo`, we'll probably want to represent
`notes` as a `Maybe String` in our Elm logic, but be sure to convert it to
an `OptionalArgument` type before sending it to GraphQL. The 
`OptionalArgument` has some useful functions for converting a `Maybe` into
[`Present`, `Absent`, or `Null`] values. This allows us to be sure that we are
either setting `notes` to a string or `null`, or just leaving its 
current value alone.

### Passing in Optional Arguments

The type signature for the `createTodo` mutation that allows optional arguments 
now looks like this:

```elm
type alias CreateTodoRequiredArguments =
  { title : String }

type alias CreateTodoOptionalArguments =
  { notes : OptionalArgument String }

todos : (CreateTodoOptionalArguments -> CreateTodoOptionalArguments) -> CreateTodoRequiredArguments -> SelectionSet decodesTo Api.Object.Todo -> SelectionSet decodesTo RootQuery
```

Now the first parameter handles our optional arguments, and the second one
handles our required arguments. Unlike the required arguments, the optional 
arguments aren't passed through as a record of values — it's a function that 
takes in `CreateTodoOptionalArguments` and returns `CreateTodoOptionalArguments`.

That's because if we look in our `createTodo` query implementation, we'll see the
following `let` statement:

 ```elm
 createTodo fillInOptionals requiredArgs object_ =
    let
      filledInOptionals =
        fillInOptionals { notes = Absent }
    in
 ```

This query function passes in a default value of `{ notes = Absent }` to 
our `fillInOptionals` function. If we want to overwrite this default, we'll have
to write a function that accepts this `{ notes = Absent }` record and
overwrites its value:

```elm
mutation : String -> Maybe String -> Cmd Msg
mutation title notes =
  Query.todos (\optionals ->
      { optionals
        | notes =
          OptionalArgument.fromMaybe notes
      }
    )
    { userId = userId }
    todoFragment
    |> Graphql.Http.queryRequest "https://example.com/graphql"
    |> Graphql.Http.send TodoCreated
```

Here, we write a function that takes in the default 
`{ notes = Absent }` value, and replaces the value with the result of
`OptionalArgument.fromMaybe notes`. If a user has not entered any notes, this 
value will continue to be `{ notes = Absent }`. But if a user does enter notes,
it will change to `{ notes = Present "example" }`.

### Using the `identity` Function

Let's say we're adding a new page in our app that has a quick-add feature, which
allows a user to quickly add a `Todo` with only a title field. The `notes`
will never exist here, so we don't have to worry about using it to overwrite the
optional values— we always want it to be `Absent`. We can simply tell the
mutation to always return the default.

```elm
mutation : String -> Cmd Msg
mutation title =
  Mutation.createTodo (\optionals -> optionals) { title = title } todoFragment
    |> Graphql.Http.mutationRequest "https://example.com/graphql"
    |> Graphql.Http.send TodoCreated
```

Here, we wrote a function that takes in a value and returns that value. You can
replace that lambda with the `identity` function:

```elm
identity : a -> a
```

Just like the anonymous function we wrote above, the identity function returns
whatever argument it is given. That means we can rewrite the mutation to look
like:

```elm
mutation : String -> Cmd Msg
mutation title =
  Mutation.createTodo identity { title = title } todoFragment
    |> Graphql.Http.mutationRequest "https://example.com/graphql"
    |> Graphql.Http.send TodoCreated
```

With either syntax, the `Mutation.createTodo` function will receive 
`{ notes = Absent }` and return `{ notes = Absent }`. You might see
`identity` used this way in the wild.

## Wrap up

Elm and GraphQL are a natural fit, and the `dillonkearns/elm-graphql` library
makes this interop feel seamless. With a little understanding around the edges
of handling optional arguments in Elm, we can fully leverage the promise of
type safe HTTP requests.

[Apollo Tooling]: https://github.com/apollographql/apollo-tooling#apollo-cli
[dillonkearns/elm-graphql]: https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest/
[introspecting on the schema]: https://graphql.org/learn/introspection/
[GraphQL spec]: http://spec.graphql.org/June2018/#sec-Null-Value
[`OptionalArgument`]: https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest/Graphql-OptionalArgument
[`Present`, `Absent`, or `Null`]: https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest/Graphql-OptionalArgument#fromMaybe
[`identity`]: https://package.elm-lang.org/packages/elm/core/latest/Basics#identity
[`SelectionSet`]: https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest/Graphql-SelectionSet 
[two type variables]: https://github.com/dillonkearns/elm-graphql/blob/5.0.0/FAQ.md#how-do-i-read-the-type-of-a-selectionset
