たくろぐ!

世界一のチラ裏

GraphQLについて公式サイトを翻訳してみた

URL

こちらが公式の学習ページ。

graphql.org

Introduction

GraphQLの導入

この記事でGraphQLがどのように動作し、どのように利用するのか学びましょう。 GraphQLサービスのビルド方法を探していますか? GraphQLを実現するのを助けてくれるライブラリがさまざまな言語で提供されています。
実践的なチュートリアルでの深い学習を希望であれば、"How to GraphQL fullstack tutorial website"へアクセスしてください。
edXでも"Exploring GraphQL: A Query Language for APIs"という無料のオンライン講座を提供しています。

GraphQLはAPI向けのクエリ言語で、自身のデータを定義したtypeシステムを利用することでクエリを実行するためのサーバサイドのランタイム(実行環境)です。
GraphQLは特定のデータベースやストレージエンジンに依存しておらず、むしろすでにあるご自身のコードやデータによって支えられています。

GraphQLサービスはそれらのtypeやフィールドを定義することで作成されており、それぞれのtypeのそれぞれのフィールドに機能を提供します。
例えば、誰がログインしているかやその人の名前を知らせてくれるGraphQLサービスは以下のようなものです。

type Query {
  me: User
}

type User {
  id: ID
  name: String
}

さらにそれぞれのtypeのそれぞれのフィールドの機能は以下のようになります。

function Query_me(request) {
  return request.auth.user;
}

function User_name(user) {
  return user.getName();
}

典型的なのはWebサービスでのURLですが、ひとたびGraphQLサービスが動作すれば、検証したり、実行するのにGraphQLのクエリを受け取ります。
受け取られたクエリは最初そのtypeやフィールドのみが定義済みかを保証するためにチェックされ、その後結果を生成するために与えられた機能を実行します。

例えば以下のようなクエリです。

{
  me {
    name
  }
}
Could produce the JSON result:

{
  "me": {
    "name": "Luke Skywalker"
  }
}

GraphQLについて以下のような観点でもっと学習してみましょう。

  • クエリ言語としてのGraphQL
  • typeシステム
  • GraphQLサービスがどのように動くか
  • このセクションの記事でのGraphQLを使ったベストプラクティス

これらはあなたたちがよく遭遇する問題を解決してくれるでしょう。

Queries and Mutations

クエリとミューテーション

このページでは、どのようにGraphQLサーバがクエリを処理するのか詳細に学習していきましょう。

Fields

フィールド

もっとも簡単なものでは、GraphQLはオブジェクトの特定のフィールドを要求するためのものです。
まずは簡単なクエリとそれを実行したとき得られる結果をみてみましょう。

{
  hero {
    name
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

クエリがその結果とまさに同じ形式であるのにすぐに気が付いたことでしょう。
これはGraphQLの必要不可欠な特徴です。
というのもあなたは期待する結果がいつでも得られ、サーバ側もクライアント側がどのフィールドを要求しているのか厳密にわかるからです。

フィールドのnameの型はStringで、この例では"R2-D2"というスターウォーズの主役の名前が返ってきます。

そしてもう一つ、上記のクエリは相互作用的なものです。
これはあなたはクエリを好きなように変更でき、新しい結果を確認できるということを意味しています。
appearsIn フィールドをheroオブジェクトに追加して結果を確認してみてください。

前の例では、String型を返すheroの名前を要求しましたが、フィールドもオブジェクトを参照できます。
この例では、そのオブジェクトにフィールドのsub-selectionを作成しています。
GraphQLのクエリは関連オブジェクトやそれらのフィールドを横断でき、クラシックなRESTアーキテクチャなら何往復かのクエリになるものを、クライアントが関連するデータを一つのリクエストで取得できるようにしてくれます。

{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }

この例ではfriendsフィールドが要素の配列を返していることに注意してください。
GraphQLのクエリは一つの要素でも複数の要素のリストでも同じように見えますが、私たちはスキーマが何を示しているのかによってどちらを期待しているのかが理解することができます。

Arguments

私たちはGraphQLによってオブジェクトやそのフィールドを跨いでデータ取得するだけでもGraphQLがとても便利な言語となっていることに気が付くでしょう。
しかしさらにフィールドに引数を渡す能力を追加するとしたら、話はもっとおもしろくなります。

{
  human(id: "1000") {
    name
    height
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

RESTのようなシステムでは、クエリパラメータやURLセグメントのような形で一つの引数の組み合わせしか渡せません。
しかしGraphQLではフィールドやネストされたオブジェクトでもそれ自身の引数の組み合わせを取得できます。
それは結果として、GraphQLが複数のAPIフェッチを実現するための完全な(REST APIの)補完となることを意味しています。
あなたはクライアントごとに別々にやる代わりに、サーバでデータ変換を実行するために一度だけスカラ型のフィールドに引数を渡すことでさえできます。

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

引数は多くの異なる型を持っています。
上記の例では、 Enumeration 型、それは選択肢の有限セットの一つを意味しているのですが(この例ではMETERFOOTのいずれか)、GraphQLはデフォルト型セットから成り、GraphQLサーバも自身のカスタムされた型を宣言できますが、転送用のフォーマットにシリアライズも可能です。

GraphQL type systemについては以下をお読みください。

graphql.org

Aliases

エイリアス

もしあなたが鋭い目を持っていれば気づいたかもしれませんが、結果オブジェクトのフィールドがそのクエリでのフィールドの名前と一致していたが引数がなかったとき、あなたは直接異なる引数で同じフィールドに対してクエリを発行できません。
これはエイリアスが必要だからです。
エイリアスはフィールドの結果を自身の好きな名前にリネームできます。

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

上記の例では、2つのheroフィールドはコンフリクトしていますが、異なる名前でエイリアスすることができるので1つのリクエストで両方の結果を得ることができます。

Fragments

フラグメント

アプリ内でちょっとだけ複雑なページがあることを考えてみましょう。
そこではfriendsと一緒に2つのherosを確認することができます。
そのようなクエリは比較対象の側から少なくとも1回はフィールドを繰り返す必要があり、複雑であることを容易に想像できます。

それはGraphQLがフラグメントという再利用可能なユニットを含んでいるからです。
フラグメントによって私たちはフィールドの組み合わせを構成でき、必要な場面でクエリにそれらを含めることができます。

以下ではフラグメントを使って上記の状況をどのように解決できるかを例として挙げています。

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}
fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

もしフィールドが繰り返されていたら、上記のクエリをみて繰り返し宣言の方法が理解できるでしょう。
フラグメントの考え方は複雑なアプリケーションのデータに必要なものをより小さなチャンクと呼ばれる単位に分割するときによく使われます。
特にUIコンポーネントと異なるフラグメントを1つの初期データに結合する際に必要です。

Using variables inside fragments

フラグメントによってクエリやミューテーションで宣言された変数にアクセスすることができます。詳細は変数をご覧ください。

query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}
fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "friendsConnection": {
        "totalCount": 4,
        "edges": [
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          },
          {
            "node": {
              "name": "C-3PO"
            }
          }
        ]
      }
    },
    "rightComparison": {
      "name": "R2-D2",
      "friendsConnection": {
        "totalCount": 3,
        "edges": [
          {
            "node": {
              "name": "Luke Skywalker"
            }
          },
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          }
        ]
      }
    }
  }
}

Operation name

オペレーション名

これまでクエリキーワードとクエリ名を省略する場合省略的なシンタックスを使用してきましたが、本番のアプリではこれらを使うことでコードがより明確になり、利便性が高くなります。

以下はオペレーションの type やオペレーション名としての HeroNameAndFriends のクエリキーワードが含まれている例です。

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

このオペレーションの type はクエリ、ミューテーション、サブスクリプションのどれかで、あなたがどのオペレーション type を意図して使用しようとしているのかを表現します。

オペレーションの type は省略されたクエリシンタックスを使用しない限り必須です。

それはそのオペレーションのなかで名前や変数定義を提供できないからです。

オペレーション名はそのオペレーションのなかで有義的で、明示的な名前です。
それはマルチオペレーションの文書内でのみ必須ですが、その利用はデバッグやサーバサイドのロギングにとても役に立つので(その使用を)推奨されています。

When something goes wrong (you see errors either in your network logs, or in the logs of your GraphQL server) it is easier to identify a query in your codebase by name instead of trying to decipher the contents. Think of this just like a function name in your favorite programming language. For example, in JavaScript we can easily work only with anonymous functions, but when we give a function a name, it's easier to track it down, debug our code, and log when it's called. In the same way, GraphQL query and mutation names, along with fragment names, can be a useful debugging tool on the server side to identify different GraphQL requests.

Variables

変数

So far, we have been writing all of our arguments inside the query string. But in most applications, the arguments to fields will be dynamic: For example, there might be a dropdown that lets you select which Star Wars episode you are interested in, or a search field, or a set of filters.

It wouldn't be a good idea to pass these dynamic arguments directly in the query string, because then our client-side code would need to dynamically manipulate the query string at runtime, and serialize it into a GraphQL-specific format. Instead, GraphQL has a first-class way to factor dynamic values out of the query, and pass them as a separate dictionary. These values are called variables.

When we start working with variables, we need to do three things:

Replace the static value in the query with $variableName Declare $variableName as one of the variables accepted by the query Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary Here's what it looks like all together:

query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
{
  "episode": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

Now, in our client code, we can simply pass a different variable rather than needing to construct an entirely new query. This is also in general a good practice for denoting which arguments in our query are expected to be dynamic - we should never be doing string interpolation to construct queries from user-supplied values.

Variable definitions

The variable definitions are the part that looks like ($episode: Episode) in the query above. It works just like the argument definitions for a function in a typed language. It lists all of the variables, prefixed by $, followed by their type, in this case Episode.

All declared variables must be either scalars, enums, or input object types. So if you want to pass a complex object into a field, you need to know what input type that matches on the server. Learn more about input object types on the Schema page.

Variable definitions can be optional or required. In the case above, since there isn't an ! next to the Episode type, it's optional. But if the field you are passing the variable into requires a non-null argument, then the variable has to be required as well.

To learn more about the syntax for these variable definitions, it's useful to learn the GraphQL schema language. The schema language is explained in detail on the Schema page.

Default variables

Default values can also be assigned to the variables in the query by adding the default value after the type declaration.

query HeroNameAndFriends($episode: Episode = JEDI) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

When default values are provided for all variables, you can call the query without passing any variables. If any variables are passed as part of the variables dictionary, they will override the defaults.

Directives

ディレクティブ

We discussed above how variables enable us to avoid doing manual string interpolation to construct dynamic queries. Passing variables in arguments solves a pretty big class of these problems, but we might also need a way to dynamically change the structure and shape of our queries using variables. For example, we can imagine a UI component that has a summarized and detailed view, where one includes more fields than the other.

Let's construct a query for such a component:

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}
{
  "episode": "JEDI",
  "withFriends": false
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

Try editing the variables above to instead pass true for withFriends, and see how the result changes.

We needed to use a new feature in GraphQL called a directive. A directive can be attached to a field or fragment inclusion, and can affect execution of the query in any way the server desires. The core GraphQL specification includes exactly two directives, which must be supported by any spec-compliant GraphQL server implementation:

@include(if: Boolean) Only include this field in the result if the argument is true. @skip(if: Boolean) Skip this field if the argument is true. Directives can be useful to get out of situations where you otherwise would need to do string manipulation to add and remove fields in your query. Server implementations may also add experimental features by defining completely new directives.

Mutations

ミューテーション

Most discussions of GraphQL focus on data fetching, but any complete data platform needs a way to modify server-side data as well.

In REST, any request might end up causing some side-effects on the server, but by convention it's suggested that one doesn't use GET requests to modify data. GraphQL is similar - technically any query could be implemented to cause a data write. However, it's useful to establish a convention that any operations that cause writes should be sent explicitly via a mutation.

Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update. Let's look at a simple example mutation:

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

Note how createReview field returns the stars and commentary fields of the newly created review. This is especially useful when mutating existing data, for example, when incrementing a field, since we can mutate and query the new value of the field with one request.

You might also notice that, in this example, the review variable we passed in is not a scalar. It's an input object type, a special kind of object type that can be passed in as an argument. Learn more about input types on the Schema page.

Multiple fields in mutations

A mutation can contain multiple fields, just like a query. There's one important distinction between queries and mutations, other than the name:

While query fields are executed in parallel, mutation fields run in series, one after the other.

This means that if we send two incrementCredits mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don't end up with a race condition with ourselves.

Inline Fragments

インラインフラグメント

Like many other type systems, GraphQL schemas include the ability to define interfaces and union types. Learn about them in the schema guide.

If you are querying a field that returns an interface or a union type, you will need to use inline fragments to access data on the underlying concrete type. It's easiest to see with an example:

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    `... on Droid {`
      primaryFunction
    }
    `... on Human {`
      height
    }
  }
}
{
  "ep": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

In this query, the hero field returns the type Character, which might be either a Human or a Droid depending on the episode argument. In the direct selection, you can only ask for fields that exist on the Character interface, such as name.

To ask for a field on the concrete type, you need to use an inline fragment with a type condition. Because the first fragment is labeled as ... on Droid, the primaryFunction field will only be executed if the Character returned from hero is of the Droid type. Similarly for the height field for the Human type.

Named fragments can also be used in the same way, since a named fragment always has a type attached.

Meta fields

Given that there are some situations where you don't know what type you'll get back from the GraphQL service, you need some way to determine how to handle that data on the client. GraphQL allows you to request __typename, a meta field, at any point in a query to get the name of the object type at that point.

{
  search(text: "an") {
    __typename
    `... on Human {`
      name
    }
    `... on Droid {`
      name
    }
    `... on Starship {`
      name
    }
  }
}
{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      },
      {
        "__typename": "Human",
        "name": "Leia Organa"
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1"
      }
    ]
  }
}

In the above query, search returns a union type that can be one of three options. It would be impossible to tell apart the different types from the client without the __typename field.

GraphQL services provide a few meta fields, the rest of which are used to expose the Introspection system.