Elixir で Absinthe を使って GraphQL に触れてみた

前々から気になっていた GraphQL を Elixir 言語で作られたライブラリの [Absinthe](http://absinthe-graphql.org/) を使って試してみました。軽く調べた感じでは Elixir で GraphQL するなら Absinthe 選ぶで良さそうです。まだベータですが[本も出ています](https://pragprog.com/book/wwgraphql/craft-graphql-apis-in-elixir-with-absinthe)。
この記事では[こちら](https://www.howtographql.com/graphql-elixir/0-introduction/)にあるチュートリアルを元に query と mutation に触れるところまでやったことをメモします。
サンプルコードは[こちら](https://github.com/ma2gedev/graphql_sample)。

## 前提

Erlang & Elixir & Phoenix あたりはインストールされている。

## サンプルアプリのセットアップ

Phoenix Framework を使って土台となるアプリを作ります。

```bash
$ mix phx.new graphql_sample --no-brunch --no-html
$ cd graphql_sample
```

mix.exs の deps へ absinthe を追加します。

```elixir:mix.exs
def deps do
  ...省略...
  {:absinthe_ecto, "~> 0.1.0"},
  {:absinthe_plug, "~> 1.3.0"}
end
```

absinthe を追加したのでライブラリを取得して DB まで作ります。

```bash
$ mix deps.get
$ mix ecto.create
```

## データモデルのセットアップ

GraphQL でクエリをかけたり、データ操作をするためにモデルをセットアップします。
シンプルに links というテーブルを作るだけです。

```bash
$ mix phx.gen.context News Link links url:string description:text
```

seed 用のデータを作ります。

```elixir:priv/repo/seeds.ex
alias GraphqlSample.News.Link
alias GraphqlSample.Repo

%Link{url: "http://graphql.org/", description: "The Best Query Language"} |> Repo.insert!
%Link{url: "http://dev.apollodata.com/", description: "Awesome GraphQL Client"} |> Repo.insert!
```

seed データを DB へ反映します。

```bash
$ mix ecto.setup
```

## Query

まずは schema を定義します。lib/graphql_sample_web/schema.ex として以下内容を記載します。
見てなんとなく分かりますが、`object :link do ...` で link という object を定義、`query do ...` で `allLinks` クエリを定義しています。

```elixir:lib/graphql_sample_web/schema.ex
defmodule GraphqlSampleWeb.Schema do
  use Absinthe.Schema

  alias GraphqlSampleWeb.NewsResolver

  object :link do
    field :id, non_null(:id)
    field :url, non_null(:string)
    field :description, non_null(:string)
  end

  query do
    field :all_links, non_null(list_of(non_null(:link))) do
      resolve &NewsResolver.all_links/3
    end
  end
end
```

Absinthe が `all_links` を GraphQL 用に `allLinks` に変換してくれるようです。

`GraphqlSampleWeb.NewsResolver` module は別途定義が必要で、以下のように lib/graphql_sample_web/resolvers/news_resolvers.ex へ定義します。

```elixir:lib/graphql_sample_web/resolvers/news_resolvers.ex
defmodule GraphqlSampleWeb.NewsResolver do
  alias GraphqlSample.News

  def all_links(_root, _args, _info) do
    links = News.list_links()
    {:ok, links}
  end
end
```

このレベルであれば Resolver なくてもという感じですが、複雑になってくることを考えるとこういった層を挟むことで複雑度を抑えられるのかなという感触。module 分けたり、よりテスタブルになったりね。

## 動作確認のための設定

GraphiQL というブラウザで動くライブラリを使って GraphQL の動作を確認します。
以下のように router.ex へ追加することで /graphiql が GraphQL 用のエンドポイントとして有効になるようです。

```elixir:lib/graphql_sample_web/router.ex
  scope "/" do
    pipe_through :api

    forward "/graphiql", Absinthe.Plug.GraphiQL,
      schema: GraphqlSampleWeb.Schema,
      interface: :simple,
      context: %{pubsub: GraphqlSampleWeb.Endpoint}
  end
```

`$ iex -S mix phx.server` でアプリを立ち上げ localhost:4000/graphiql へアクセスすると、こんな画面が表示されます。

![GraphiQL の画面](https://qiita-image-store.s3.amazonaws.com/0/17636/5eab438d-1fc5-675a-66a1-07a5fd271437.png)


左側のペインに以下クエリを入力して実行すると、link table に格納されているデータが返ってきます。

```
{
  allLinks {
    id
    url
    description
  }
}
```

## Mutation

次にデータ操作のための Mutation を試します。

lib/graphql_sample_web/schema.ex へ以下を追加します。
`arg` というのが初めて出てきていますが、これはその名の通り引数です。
これも結構見た目通りなので、Absinthe 頑張ってくれているなと思います。

```elixir:lib/graphql_sample_web/schema.ex
  mutation do
    field :create_link, :link do
      arg :url, non_null(:string)
      arg :description, non_null(:string)

      resolve &NewsResolver.create_link/3
    end
  end
```

Resolver 側には以下を追加します。

```elixir:lib/graphql_sample_web/resolvers/news_resolvers.ex
  def create_link(_root, args, _info) do
    case News.create_link(args) do
      {:ok, link} ->
        {:ok, link}
      _error ->
        {:error, "could not create link"}
    end
  end
```

ここまで出来たらアプリを立ち上げてクエリ同様に localhost:4000/graphiql へアクセスします。

今度は左のペインへ以下のように入力して実行します。

```
mutation {
  createLink(
    url: "https://github.com/ma2gedev/power_assert_ex",
    description: "Power Assert Elixir",
  ) {
    id
    url
    description
  }
}
```

これで link table へ追加されました。再度クエリで allLinks を叩くことで結果が増えていることが確認できます。


## まとめ

REST との違いを感じることができたので、とりあえず入り口くらいには立てたかなと。
ただ client 側で必要なものだけを取るとか、N+1 を解消させる例とか試したいところが全然試せていない感じなので、GraphQL を理解するにはもう少し頑張らねば。。。

## Resources

- Building a GraphQL Server with Elixir Backend Tutorial https://www.howtographql.com/graphql-elixir/0-introduction/
- graphql/graphiql: An in-browser IDE for exploring GraphQL. https://github.com/graphql/graphiql
- Absinthe: Home http://absinthe-graphql.org/
- Craft GraphQL APIs in Elixir with Absinthe: Flexible, Robust Services for Queries, Mutations, and Subscriptions by Bruce Williams and Ben Wilson | The Pragmatic Bookshelf https://pragprog.com/book/wwgraphql/craft-graphql-apis-in-elixir-with-absinthe
- 今回実際に試したコード https://github.com/ma2gedev/graphql_sample