GraphQl with absinthe: How to map data to different structures
Tags: graphql, absinthe, elixir
Published: 2019-02-24 16:57:57

---
title: GraphQl with absinthe: How to map data to different structures
published: true
description: 
tags: graphql, absinthe, elixir
series: graphql
---

After starting using graphql with absinthe and discovering some [gotchas](https://dev.to/revent/graphql-with-absinthe-59kb) everything went nice and smooth. After a few days tho, I encountered a case that our data source has a different data structure than one that has to be defined in graphql. While it was mostly straight forward to implement, I didn't find many resources on how to do it.

##### Assertions

- you are familiar with [elixir](https://elixir-lang.org). [Where to learn?](https://dev.to/matt24ray/best-resources-for-learning-elixir-467f)
- you are familiar with both [graphql and absinthe](https://dev.to/revent/graphql-with-absinthe-59kb).

##### Works on my machine

- elixir: 1.7.3
- phoenix: 1.4.0
- absinthe: 1.4.13

## Bussiness rules

Existing `user` entity has field `name`, which oddly enough doesn't contain a string, but a map (saved as json in DB) with two keys (`first` and `last`). But graphql has to have `first_name`, `second_name` and their combined value as `full_name` to be alongside `id`.

## Initial setup

To start to implement needed business requirements, as usual, there is some bootstrap code that is needed. This is no exception. 

We will have to implement four modules:

- `MyApp.Schema` -> graphql structures, queries and mutations.
- `MyApp.UsersRepository` -> represents existing repository.
- `MyApp.UsersRepository.User` -> represents exisitng ecto schema.
- `MyApp.UsersResolver` -> graphql resolver callbacks.

Both `MyApp.UsersRepository` and `MyApp.UsersRepository.User` in this example is outside of the graphql boundary. This part of code should live in a different context, and shouldn't be influenced in any way by the fact that now it has a new consumer (graphql) that requires different data structure.

Note that `MyApp.Schema` defines an object `user` that has fields described in the business rules, but `MyApp.UsersRepository` expect name values to be nested under `name` key (representing existing repository structure).

```elixir
defmodule MyApp.Schema do
  @moduledoc """
    GraphQL schema description (available objects, queries and mutations)
  """

  use Absinthe.Schema

  alias MyApp.UsersResolver

  @doc """
    User object with few scalar fields
  """
  object :user do
    field :id, :integer
    field :first_name, :string
    field :last_name, :string
    field :full_name, :string
  end
end

defmodule MyApp.UsersRepository do
  @moduledoc """
    Actual repository, used as change boundary.
    This code will not change at all during this blog post.
  """

  defmodule User do
    @moduledoc """
      In a real-life scenario, this most likely would be an ecto schema.

      For sake of example, simple struct will also do.
    """
    defstruct id: nil, name: %{"first" => nil, "last" => nil}
  end

  @doc """
    Gets a list of users from the data source.
    In real life scenario most likely from DB via ecto.

    In this case list with one hardcoded user.
  """
  def get_all() do
    [%User{id: 1, name: %{"first" => "John", "last" => "Doe"}}]
  end

  @doc """
    Creates a user in a data store.
    In real life scenario most likely to DB via ecto.

    In this case, checks if data has required structure and if so, add id (like autogenerated id value).
  """
  def create(%User{name: %{"first" => _, "last" => _}} = user), do: {:ok, %{user | id: 10}}
  def create(_), do: {:error, "Wrong structure"}
end

defmodule MyApp.UsersResolver do
  @moduledoc """
    Handles data processing for graphql.

    Sort of like repository, but defined as graphql resolve callbacks.
  """
  alias MyApp.UsersRepository
  alias MyApp.UsersRepository.User
end
```

## Receiving data

We need to be able to make the following query:

```
query {
  users {
    firstName,
    lastName,
    fullName
  }
}
```

So we need to add `query` block with `users` field.
In `MyApp.UsersResolver` we make a function that is defined as a resolver for users query. The only difference from the usual case where data maps `1:1` is that we use `&Enum.map/2` to iterate over results from `&MyApp.UsersRepository.get_all/0` with `&to_graphql/1` and can make any structure we need.

> Note that graphql by default expects atom keys. It [can be changed](https://elixirforum.com/t/serialisation-atoms/4879/4) if needed.

```elixir
defmodule MyApp.Schema do
  ...

  @doc """
    Makes query `users` available for API consumers. 
    The query is expected to return a list of `user` objects.
    `&UsersResolver.fetch_users/3` is responsible to make that happen
  """
  query do
    field :users, list_of(:user), resolve: &UsersResolver.fetch_users/3
  end
end

defmodule MyApp.UsersResolver do
  ...

  @doc """
    Fetches all users from the repository.
  """
  def fetch_users(_, _, _) do
    users = UsersRepository.get_all() |> Enum.map(&to_graphql/1)
    {:ok, users}
  end

  @doc """
    Map data from user instance to one required by graphql api.
  """
  defp to_graphql(%User{id: id, name: %{"first" => first, "last" => last}}) do
    %{id: id, first_name: first, last_name: last, full_name: "#{first} #{last}"}
  end
end
```

now executing the required query we get expected output:

```json
{
  "data": {
    "users": [
      {
        "lastName": "Doe",
        "fullName": "John Doe",
        "firstName": "John"
      }
    ]
  }
}
```

## Mutation mapping

This is a mutation we are required to be able to execute:

```
mutation {
  createUser(firstName: "Doe", lastName: "John") {
    id
    fullName
  }
}
```

Here things get a little bit more involved in absinthe. Not much, but still.
First of all, now we crate mutation. Then in `create_user` resolver, we take the second argument, as it is where arguments passed through graphql are located.
We do a similar map as previously with `to_graphql` but in reverse.

```elixir
defmodule MyApp.Schema do
  ...

  @doc """
    Mutation to create a new user
    Two fields are required, and mutation is expected to return created user instance.
  """
  mutation do
    field :create_user, :user do
      arg :first_name, non_null(:string)
      arg :last_name, non_null(:string)

      resolve &UsersResolver.create_user/3
    end
  end
end

defmodule MyApp.UsersResolver do
  ...

  @doc """
    Creates a new user to repository.
  """
  def create_user(_, args, _) do
    args |> from_graphql |> UsersRepository.create()
  end

  @doc """
    Converts graphql structure to one required by the repository.
  """
  defp from_graphql(%{first_name: first, last_name: last}) do
    %User{name: %{"first" => first, "last" => last}}
  end
end
```

And we expect to get as a response:

```json
{
  "data": {
    "createUser": {
      "id": 10,
      "fullName": "Doe John"
    }
  }
}
```

but that isn't what we actually get. `fullName` is null. Reason for it is that `&MyApp.UsersResolver. create_user/3` resolver callback returns `User` struct (received from `MyApp.UsersRepository.create/1`). The resolver should always return graphql valid structure. To fix it, we need to map `User` struct value back to graphql with previously used `&to_graphql/1` like so:

```elixir
defmodule MyApp.UsersResolver do
  ...

  def create_user(_, args, _) do
    args 
    |> from_graphql 
    |> UsersRepository.create()
    |> case do
      {:ok, user} -> {:ok, to_graphql(user)}
      error -> error
    end
  end
end
```

Now required mutation actually returns what was expected previously.

## In conclusion

- graphql doesn't interfere with data resolving. Resolve callbacks are plain elixir code, you can manipulate data as ever you please. Maps expect to have atom keys by default, but it [can be changed](https://elixirforum.com/t/serialisation-atoms/4879/4).
- Creating `MyApp.UsersResolver` in between `MyApp.Schema` and `MyApp.UsersRepository` is a great way to split models into sperate use cases. Each model deals only with [one specific task](https://codeburst.io/understanding-solid-principles-single-responsibility-b7c7ec0bf80). 
- resolvers for mutations, that return object, should take in consideration, that mutation callback has to return object in graphql valid structure (mutate return data if needed).
- mutation callback is the only place that does two jobs, saving data and preparing a response. If we applied something [CQS](https://dev.to/kspeakman/comment/6gp6)ish that mutation at most would return the newly generated id, so no response mapping would be needed in that case.

I'm still pretty new to graphql, but I really like what I see.

P.S. If you have any feedback, suggestion, question or thoughts about this topic, please let me know :)

#### P.P.S Whole code without comments

```elixir
defmodule MyApp.Schema do
  use Absinthe.Schema

  alias MyApp.UsersResolver

  object :user do
    field :id, :integer
    field :first_name, :string
    field :last_name, :string
    field :full_name, :string
  end

  query do
    field :users, list_of(:user), resolve: &UsersResolver.fetch_users/3
  end

  mutation do
    field :create_user, :user do
      arg :first_name, non_null(:string)
      arg :last_name, non_null(:string)

      resolve &UsersResolver.create_user/3
    end
  end
end

defmodule MyApp.UsersRepository do
  defmodule User do
    defstruct id: nil, name: %{"first" => nil, "last" => nil}
  end

  def get_all() do
    [%User{id: 1, name: %{"first" => "John", "last" => "Doe"}}]
  end

  def create(%User{name: %{"first" => _, "last" => _}} = user), do: {:ok, %{user | id: 10}}
  def create(_), do: {:error, "Wrong structure"}
end

defmodule MyApp.UsersResolver do
  alias MyApp.UsersRepository
  alias MyApp.UsersRepository.User

  def fetch_users(_, _, _) do
    users = UsersRepository.get_all() |> Enum.map(&to_graphql/1)
    {:ok, users}
  end

  def create_user(_, args, _) do
    args
    |> from_graphql
    |> UsersRepository.create()
    |> case do
      {:ok, user} -> {:ok, to_graphql(user)}
      error -> error
    end
  end

  defp to_graphql(%User{id: id, name: %{"first" => first, "last" => last}}) do
    %{id: id, first_name: first, last_name: last, full_name: "#{first} #{last}"}
  end

  defp from_graphql(%{first_name: first, last_name: last}) do
    %User{name: %{"first" => first, "last" => last}}
  end
end
```