Tech Racho エンジニアの「?」を「!」に。
  • 開発

RailsでGraphQL APIをつくる: Part 2 - Railsで基本的なAPIを書く(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Part 1〜3の導入部は共通のため、2と3では省略しました。

RailsでGraphQL APIをつくる: Part 2 - Railsで基本的なAPIを書く(翻訳)

このブログ記事シリーズでは以下の3つのトピックについて扱います。

  1. GraphQLとは何か
  2. Railsで基本的なAPIを書く(本記事)
  3. ベストプラクティス集

Railsで基本的なAPIを書く

元ネタは私のslide#4です。

コードを書き始める前に最も大まかなユーザー要件を定義してから、順に実装を進めます。

トップレベルのユーザー要件
1. ユーザーはAPIを使うためのアカウントを必要としている。
2. ユーザーはAPIから自分のアカウント情報を取得できる。

要件1. 「ユーザーはAPIを使うためのアカウントを必要としている」

認証用gemのインストール

Railsアプリなら好みの認証用gemをインストールするだけなので、実に簡単です。DeviseClearanceあたりがよいでしょう。本記事の例では新しめのgemということでClearanceを使いますが、どれでも同じなので好みの認証gemを使えます。

Clearanceの詳しいインストール方法は省略します(ドキュメントをご覧ください)。インストール後、以下の3つのコマンドを実行します。

$ rails generate clearance:install
$ rails generate clearance:routes
$ rake db:migrate

実に簡単です。Rubyコミュニティにひたすら感謝。🙏

APIトークンとUserモデルを設定する

先に進む前に、API経由でユーザーを許可(authorize)するauth_tokenが必要です。今回はhas_secure_token(Rails 5以降ではgemのインストールは不要です)でUser#api_tokenを生成することにしました。generate後のUserモデルは次のような感じになります。

class User < ActiveRecord::Base
  include Clearance::User
  has_secure_token :api_token
end

# == Schema Information
#
# Table name: users
#
#  id                 :integer          not null, primary key
#  created_at         :datetime         not null
#  updated_at         :datetime         not null
#  email              :string           not null
#  encrypted_password :string(128)      not null
#  confirmation_token :string(128)
#  remember_token     :string(128)      not null
#  api_token          :string

要件1.を達成できました。おめでとうございます🎉

要件2. 「ユーザーはAPIから自分のアカウント情報を取得できる」

これからGraphQL APIを作成します。目標は、以下のようなクエリをやりとりできるAPIエンドポイントの作成です。

query {
  me: viewer {
    id
    email
    created_at
  }
}

上のようなクエリを受信し、以下のようなJSONを返します。

{
  "data": {
    "me": {
      "id": 1,
      "email": "wayne.5540@gmail.com",
      "created_at": 1477206061
    }
  }
}

必要なgemをインストールする

# コアとなるgem
gem 'graphql', '~> 1.0.0'

# GraphQL APIエクスプローラを作成できる便利なgem(必須ではない)
gem 'graphiql-rails'

# TDD
group :development, :test do
  gem 'rspec-rails', '~> 3.5'
  gem 'shoulda-matchers'
  gem 'factory_girl_rails'
  gem 'faker'
end

APIエンドポイントを作成する

Authorizationヘッダー{ "Authorization" => "Token #{user.api_token}" }を追加したPOST /graphql?query=graphql-queryリクエストを受信できるようにします。

そのために、まずリクエストの処理に必要なルーティングを書きます。

# config/routes.rb
Rails.application.routes.draw do
  post "graphql" => "graphqls#create"
end

続いてコントローラを書きます。

class GraphqlsController < ApplicationController
  before_action :authenticate_by_api_token!

  def create
    query_string = params[:query]
    query_variables = JSON.load(params[:variables]) || {}
    context = { current_user: current_user }
    result = Schema.execute(query_string, variables: query_variables, context: context)
    render json: result
  end
end

Schema.execute(query_string, variables: query_variables, context: context)行はgraphql gemの基本的な利用法を示しており、ここでクエリ文字列、グラフ変数、コンテキストをSchemaに渡します。Schemaはこの後で定義します。


GraphQLの型について

コントローラのアクションで行われることを理解するために、ここでGraphQLの型システムについて簡単に説明します。前述のクエリ例をもう一度引用します。

query {
  me: viewer {
    id
    email
    created_at
  }
}

実際には、このクエリにSchemaQueryTypeUserTypeという3つの異なるスコープが含まれています。

各々の型はAPI開発者が定義します。以下の画像で各スコープの概要をご覧ください。

Schemaスコープ: 最も外側のスコープであり、QueryおよびMutationという特殊型を含みます。QueryはHTTPのGETリクエストに相当し、MutationPOSTリクエストに相当すると考えるとわかりやすいでしょう。

QueryTypeスコープ:

UserTypeスコープ:


GraphQLで型を定義する

それでは型定義コードを書いてみましょう。GraphQL::ObjectType APIを利用すれば比較的簡単です。

UserTypeスコープ: User型を定義するために、各フィールドを定義します。レスポンスのカスタマイズにはresolveブロックを使います。カスタマイズしない場合はフィールド名のメソッドから取得します(例: フィールドidでuser.idがレスポンスとして使われる)。
今回の例ではupdated_atcreated_atIntegerに設定したいので、resolveブロックのupdated_atフィールドでobj.updated_at.to_iを呼び出します。

# app/graph/types/user_type.rb
UserType = GraphQL::ObjectType.define do
  name "User"
  description "1人のユーザー"

  field :id, types.Int
  field :email, types.String
  field :updated_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.updated_at.to_i
    }
  end
  field :created_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.created_at.to_i
    }
  end
end

QueryTypeスコープ: UserTypeの場合と同じ要領でviewerフィールドを作成し、typeUserTypeを指定します。

# app/graph/types/query_type.rb
QueryType = GraphQL::ObjectType.define do
  name "Query"
  description "このスキーマのクエリのルート(root)"

  field :viewer do
    type UserType
    description "Current user"
    resolve ->(obj, args, ctx) {
      ctx[:current_user]
    }
  end
end

context[:current_user]Schema.executeメソッドに渡され、Schemaの下のすべてのフィールドで使えるようになります。

なお、UserType自身もcurrent_userを取得できるので、実際にはUserTypeにcurrent_userを渡す必要はありません。ここでは、QueryType#viewerresolveブロック内でcurrent_userが返すuserオブジェクトがUserTypeに渡される様子を例示するために、あえて使っています。

Schemaスコープ:

# app/graph/schema.rb

Schema = GraphQL::Schema.define do
  query QueryType
end


仕上げの設定

あとは設定をいくつか追加すれば完了です。

グラフや型のautoload:

# config/application.rb
module GraphBlog
  class Application < Rails::Application
    # ...
    config.autoload_paths << Rails.root.join('app', 'graph', 'types')
  end
end

GraphiQLの設定(Graph APIのインタラクティブUI):

最初に、GraphiQLエンジンをRailsルーティングにマウントします。

# config/routes.rb

Rails.application.routes.draw do
  # ...
  mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
  # ...
end

次にデフォルトのAuthorizationヘッダーを設定します。

# config/initializers/graphiql.rb

GraphiQL::Rails.config.headers['Authorization'] = -> (context) {
  "Token #{context.request.env[:clearance].current_user.try(:api_token)}"
}

あとはRailsサーバーを起動してhttp://localhost:3000/graphiqlを開けば、GraphQLで遊べるようになります。

サンプルクエリをいくつか掲載します。

query {
  viewer {
    id
    email
  }
}
query {
  me: viewer {
    ...userFields
  }
  alsoIsMe: viewer {
    ...userFields
  }
}

fragment userFields on User {
  user_id: id
  email
}

以上で、Railsプロジェクトで初めてのGraphQL APIエンドポイントが動くようになりました。🎉

今回のガイドではRailsで簡単なGraphQL APIを書くにとどめましたので、次回の「RailsでGraphQL APIをつくる: Part 3 - ベストプラクティス集」でベストプラクティスをいくつかご紹介します。



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。