前々から気になっていた GraphQL を Elixir 言語で作られたライブラリの Absinthe を使って試してみました。軽く調べた感じでは Elixir で GraphQL するなら Absinthe 選ぶで良さそうです。まだベータですが本も出ています。
この記事ではこちらにあるチュートリアルを元に query と mutation に触れるところまでやったことをメモします。
サンプルコードはこちら。
前提
Erlang & Elixir & Phoenix あたりはインストールされている。
サンプルアプリのセットアップ
Phoenix Framework を使って土台となるアプリを作ります。
$ mix phx.new graphql_sample --no-brunch --no-html
$ cd graphql_sample
mix.exs の deps へ absinthe を追加します。
def deps do
...省略...
{:absinthe_ecto, "~> 0.1.0"},
{:absinthe_plug, "~> 1.3.0"}
end
absinthe を追加したのでライブラリを取得して DB まで作ります。
$ mix deps.get
$ mix ecto.create
データモデルのセットアップ
GraphQL でクエリをかけたり、データ操作をするためにモデルをセットアップします。
シンプルに links というテーブルを作るだけです。
$ mix phx.gen.context News Link links url:string description:text
seed 用のデータを作ります。
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 へ反映します。
$ mix ecto.setup
Query
まずは schema を定義します。lib/graphql_sample_web/schema.ex として以下内容を記載します。
見てなんとなく分かりますが、object :link do ...
で link という object を定義、query do ...
で allLinks
クエリを定義しています。
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 へ定義します。
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 用のエンドポイントとして有効になるようです。
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 へアクセスすると、こんな画面が表示されます。
左側のペインに以下クエリを入力して実行すると、link table に格納されているデータが返ってきます。
{
allLinks {
id
url
description
}
}
Mutation
次にデータ操作のための Mutation を試します。
lib/graphql_sample_web/schema.ex へ以下を追加します。
arg
というのが初めて出てきていますが、これはその名の通り引数です。
これも結構見た目通りなので、Absinthe 頑張ってくれているなと思います。
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 側には以下を追加します。
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