🐭

【GraphQL】入門編 ~基礎とQuery / Mutationの書き方~ | Offers Tech Blog

2022/06/09に公開
2

概要

こんにちは、Offers を運営している株式会社 overflow の Software Engineer(主戦場はフロントエンド)の Kazuya です。今回は、昨今の WEB アプリケーション開発で採用がされるケースが増えてきている GraphQL について紹介します。

「GraphQL」という名前だけは耳にしたことがあるけど、実際はどういうものなのか分からないという人も多いのではないでしょうか?本記事では、GraphQL 初心者の方でも理解しやすいよう、概要だけでなく RESTful API との違いや具体的な使用方法も掲載していますので、ぜひ参考にしてもらえればと思います。

はじめに

本記事では、GraphQL の基礎と一部実装例を紹介します。GraphQL を扱う上での基礎的な内容ですので、実際のプロダクト開発等では活用できない場合があります。そのことを予めご了承の上、参考にしていただけると幸いです。

GraphQL とは?

GraphQLのロゴ画像

GraphQL とは、API 向けに作られたクエリ言語およびクエリを実行するサーバーサイドの実装(ランタイム)のことです。これだけだと分かりづらいのでもう少し簡単にまとめると、「クエリ言語とスキーマ言語で構成された API の規格」と言ったところでしょうか。これでもまだざっくりとしてしまっているので、理解しやすくするために GraphQL の特徴を一部紹介します。

  • アプリケーションが呼び出すエンドポイントが 1 つ
  • 柔軟なリクエストで最小限のレスポンス
  • 1 回のリクエストで複数のリソースにアクセス可能
  • スキーマによる型付けにより、堅牢な開発ができる

このように GraphQL は、柔軟性、開発者にとっての使いやすさを向上させるために設計されていることが分かると思います。前提としてなぜ、Meta(旧 Facebook) は GraphQL を開発したのでしょうか。本題に入る前に、開発の経緯と設計に関係のあるグラフ理論について軽く紹介していきます。

https://graphql.org/

GraphQL 開発の経緯とグラフ理論

開発の経緯

GraphQL が開発される以前、Meta(旧 Facebook) は RESTful API(以降、REST)と FQL(Facebook Query Language)を運用し、データを取り扱っていました。しかし、REST には「過剰なデータ取得(オーバーフェッチング)」という課題があり、処理の複雑化やパフォーマンス低下などを引き起こす要因となっています。また、クエリ構造とレスポンス構造に乖離が発生しており、プロダクトの開発/運用する上で課題となっていました。

そこでこれらの課題を改善するために、2012 年に開発がスタートし、2015 年に OSS(オープンソース)として公開されました。その後は、Github を始めとした様々なサービスで導入が進み、現在ではアプリケーション開発における選択肢の 1 つとして注目されています。

GraphQL の導入企業

  • Facebook
  • Github
  • YouTube
  • Reddit
  • Netflix
  • Twitter
  • overflow

グラフ理論との関係

GraphQL は、グラフ理論について知っておくことでより理解を深めることができると思います。グラフ理論とは、点(ノード)とそれらを結ぶ線(エッジ)で構成される図形のことです。グラフ理論についての解説は、以下の記事で詳しく解説されていますので、割愛させてもらいます。

https://qiita.com/maskot1977/items/e1819b7a1053eb9f7d61
https://dev.classmethod.jp/articles/graph-theory/

そんなグラフ理論ですが、GraphQL のスキーマ設計のベースになっています。GraphQL は名前の通り、グラフ(Graph)を扱う問い合わせ言語という側面をもっており、実際に有向グラフで可視化してみると、構造が類似していることが分かります。

グラフ理論の画像
友人関係図(左)とユーザーの GraphQL(右)

GraphQL のメリット/デメリット

メリット

  • 柔軟なリクエストで最小限のレスポンス(≒ 通信効率を最適化させやすい)
  • 開発をサポートするツールが豊富
  • クエリの学習コストが低い
  • スキーマをベースとした型補完やチェック
  • アプリケーションが呼び出すエンドポイントが 1 つ

デメリット

  • REST に比べてキャッシュが複雑になりやすい
  • パフォーマンスの分析が難しい
  • 複雑な処理を実装するのが難しい
  • 動画等の大容量バイナリの扱いが難しい

GraphQL の基礎

スキーマ言語

GraphQL のスキーマ言語は、API の仕様を定義する言語として扱われます。搭載されている型システムにより、クエリやレスポンス、バリデーションなどに対して型を定義できます。

各コンポーネント

  • Type
  • Field
  • Interface
  • Union
  • Scalar
  • Description

クエリ言語

GraphQL のクエリ言語は、データ取得の Query、データ更新の Mutation、イベント監視の Subscription の3種類が存在しており、これらは「オペレーション型」と呼ばれています。

REST および SQL との対応表

Rest SQL GraphQL
データ取得 GET Select Query
データ追加 POST Create Mutation
データ更新 PATCH Update Mutation
データ削除 DELETE Delete Mutation
イベント監視 - - Subscription

Query / Mutation の具体的な書き方

Query

データ取得のクエリである Query の書き方を解説していきます。まず、今回の解説に使うスキーマは以下の通りで、ユーザーの情報を 1 つ返す user を定義しています。その際に返すユーザーの情報は User 型で定義されたものになります。

schema.graphql
# ルートオペレーション型
type Query {
  user: User!
}

# User型
type User {
  id: ID!
  name: String!
  email: String!
}
user.graphql
query GetUser {
  user {
    id
    name
  }
}
レスポンス
{
  "data": {
    "user": {
      "id": "hoge",
      "name": "foo",
    }
  }
}

上記の場合、User 型には email も含まれていますが GetUser で定義していないため、レスポンスには含まれていません。このように必要に応じてデータを追加したり削ったりできるため、レスポンスを最小限にできます。

Query 内で複数のフィールドを含める方法

Query 内で複数のフィールドを含める際には、以下のように書きます。フィールドを複数含めることができますが、入れ過ぎはパフォーマンスに影響があるため、実装の際は注意しましょう。

schema.graphql
# ルートオペレーション型
type Query {
  user: User!
  users: [User!]
}

# User型
type User {
  id: ID!
  name: String!
  email: String!
}
user.graphql
query GetUser {
  user {
    id
    name
  }
  users {
    id
    name
    email
  }
}
レスポンス
{
  "data": {
    "user": {
      "id": "hoge",
      "name": "foo",
    }
    "users": [
      {
        "id": "hoge",
        "name": "foo",
	"email": "foo",
      },
      {
        "id": "hoge",
        "name": "foo",
	"email": "foo",
      }
    ]
  }
}

Fragment

Fragment は、クエリの一部をフラグメント化して再利用する機能のことです。この機能によりフィールド指定の冗長な繰り返しを防ぐことができます。以下の Query では、hoge_userfoo_user という 2 つのフィールドがありますが、どちらもレスポンスは共通であることが分かります。

user.graphql
query GetUser {
  hoge_user: user {
    id
    name
  }
  foo_user: user {
    id
    name
  }
}

上記の Query に Fragment を導入する場合は、以下のように書きます。

user.graphql
query GetUser {
  hoge_user: user {
    ...userFragment
  }
  foo_user: user {
    ...userFragment
  }
}

fragment userFragment on User {
   id
   name
}

Fragment は、fragment Fragment名 on 型名 { フィールド } で定義でき、Query(後述の Mutation も対象)に適応させる場合は、...Fragment名 で展開できます。これは JavaScript のスプレッド構文と同じ書き方です。

Mutation

最後にデータ更新のクエリである Mutation の書き方を解説していきます。まず、今回の解説に使うスキーマは以下の通りで、ユーザーを登録する createUser を定義しています。その際に返すユーザーの情報は User 型で定義されたものになります。

schema.graphql
# ルートオペレーション型
type Mutation {
  createUser(data: UserCreateInput!): User!
}

# User型
type User {
  id: ID!
  name: String!
  email: String!
}

# UserCreateInput型
input UserCreateInput {
  id: ID
  name: String!
  email: String!
}
user.graphql
mutation CreateUser {
  createUser(data: {
    name: "hoge",
    email: "foo@hoge.co.jp"
  }) {
    name
  }
}
レスポンス
{
  "data": {
    "createUser": {
      "name": "hoge",
    }
  }
}

基本的な書き方は、Query と変わりません。ですが、createUser には引数 data が存在しています。この引数 data は、UserCreateInput という型が定義されており、リクエストするためには、ここで定められた値を与える必要があります。今回の場合は nameemail が、Null を許容しない(必須)になっているため、この2つは必ず設定する必要があるということになります。なお、id に関しては、Null が許容されている(オプション)のため、設定しなくてもリクエスト可です。

まとめ

今回は GraphQL の基礎と Query と Mutation の書き方についてまとめてみました。本記事で少しでも理解を深めることができていれば幸いです。GraphQL には、本記事で紹介した機能以外にも様々な強力な機能があるので、興味のある方はぜひ触れてみてください。

次回以降は、実際にプロダクト開発でどのように活用していけばいいのかなど、より実践ベースで紹介できればと考えていますので、気長にお待ちいただければと思います。(頑張って早めに書きます)

少々長くなりましたが、最後まで読んで頂き、ありがとうございました。「いいね」していただけると記事執筆の励みになりますので、参考になったと思った方は是非よろしくお願いします!

関連記事

https://zenn.dev/offers/articles/20220523-component-design-best-practice
https://zenn.dev/offers/articles/20220418-what-is-bff-architecture
https://zenn.dev/offers/articles/20220519-thinking-about-dark-mode

Offers Tech Blog

Discussion

qaynamqaynam

Mutationの書き方間違っていませんか?


type Mutation {
-  createUser: createUser(data: UserCreateInput!): User!
+  createUser(data: UserCreateInput!): User!
}