2022/05/12(金) 社内勉強会用の資料です。
「GraphQLのここがいい!」「積極的にGraphQLを採用しよう!」といった意図のものではなく、GraphQLに興味がある人のためのイントロダクションとして、自分が調べたことをまとめています。
GraphQLに興味があるという人は、ぜひ末尾のリンク等参考になさってください。
GraphQLとは
GraphQLは、APIのための問い合わせ言語としてMeta(旧Facebook)によって開発されました。
RESTApiの問題点を改善するために実装され、2015年にオープンソース化されています。
2017年にはGithubがいち早くRESTApiでの実装をGraphQLに置換する等、実績もある技術です。
GraphQLの特徴
GraphQLの言語仕様
すでに「問い合わせ言語(Query Language)」と記載しているとおり、GraphQLは文法を持つ言語です。
RESTとの違いとして
- APIのエンドポイントが単一
- 強く型付けされている
- 取得するデータの形状をリクエスト側で指定できる
といった特徴があります。
クエリ言語
GraphQLサーバーにリクエストを出すための言語。
ウェブ開発では、主にクライアントサイドアプリケーションからAPIサーバーのデータ取得・更新処理に利用されます。
データを取得する際の形状を指定することができ、引数を定義することで動的に取得するデータの条件を変更することも可能。 コマンドの種類は下記の3種類です。
コマンド名 | 役割 |
クエリ(query) | データの取得 |
ミューテーション(mutation) | データの更新・削除 |
サブスクリプション(subscription) | サーバーとの相互通信(web socket利用) |
リクエストする際には取得したいデータの形状を定義すると、定義と同じ形状のデータが返ってきます。
スキーマ言語
スキーマ言語は、GraphQLサーバーが提供するAPI仕様を定義するための言語です。 GraphQLのリクエストを定義・バリデーションする用途で使用され、取得リソースの形状や型定義を行うことができます。
GraphQL-Codegenなどのパッケージを利用する場合、サーバーサイドのスキーマ定義を参照して型や関数を自動生成してくれます。そのため、クライアント側で改めて型を定義する必要はなく、容易にサーバーサイド・クライアントサイドの型定義を同期できます。
また、他の言語でも見られるような「インターフェース」「ユニオン」「列挙型」等の言語機能を利用した型定義も可能です。
RESTとの違いとGraphQLの利点
型付け
スキーマ言語の定義を見ていただいてわかる通り、GraphQLではRESTApiと異なりレスポンスの型を定義することができます。
Typescriptなどの静的型付けの言語と組み合わせることによって、型のサポートを受けながら効率的な開発をすることができます
取得するデータの形状
RESTApiでは、APIのレスポンスとして取得するデータの形状は該当するAPIの仕様によって決まります。
それぞれのAPI毎にサーバーサイドでフィールドを絞り込んだ状態でレスポンスするため、必要なデータを取得することができるAPIが存在しない場合は、APIの改修・新規作成が必要になります。
また、リクエスト側では必要のないフィールドもレスポンスに含まれるため、取得した後で必要なフィールドのみにトリムする手間も生じます。
さらに、単一のAPIで必要なデータが取得できない場合には、複数のAPIからデータフェッチを行うなどという処理を行う必要も生まれてきます。
結果として、必要なデータに対して必要十分以上に膨大な処理・リクエストが生じる場合があります。
一方、GraphQLではリクエスト側で取得するデータの形状を定義することができるので、必要十分のデータのみを取得することができます。
また、そのレコードに関連するデータが一緒に欲しい場合も、わざわざ複数のAPIを実行する必要はなく、少ないリクエストで必要十分のデータフェッチが完了します。
liftsのリストと、各liftに紐づくtrailsを取得する例
GraphQLのクエリ
query
RESTApiでいうところのGETリクエストにあたるのがqueryです。 queryは以下のような構成で定義されます。
// 引数に利用する変数の型定義
query users($status:String){
// クエリ名
users(
status: $status // 引数(`$`が変数として扱われる)
) {
// 取得するフィールド
id
name
email
}
}
// 引数なしの場合
query {
users{
id
name
email
}
}
mutation
RESTApiでいうところのPOST,PUT, PATCH, DELETE等の役割を果たすのがmutationです。
mutationは以下のような構成で定義されます。
// 引数に利用する変数の型定義
mutation createUser($name:String $email:String $authType:AuthType{
// クエリ名
createUser(
// 引数(`$`が変数として扱われるのはqueryと同じ)
name:$name
email:$email
authType:$authType
) {
// 取得するフィールド
id
name
email
}
}
subscription
subscriptionは、サーバーサイドとの双方向通信を行うために使います。
例えば、他のユーザーがデータを変更した際にその変更内容をアプリケーションに通知したい場合等、サブスクリプションを実行することでリアルタイムにデータの取得を行うことができます。
subscription onUserInfoChange($id:String!){
// クエリ名
onUserInfoChange(
id: $id // 引数(`$`が変数として扱われるのはqueryと同じ)
) {
// 取得するフィールド
id
name
email
}
}
// 引数なしの場合
subscription {
onUserInfoChange{
id
name
email
}
}
フラグメント
フラグメントは、異なるクエリで取得するフィールドを共通化したい場合などに利用するエイリアスです。
たとえば、ユーザー取得とユーザー作成のリクエストで、一部のフィールドを共通化したい場合はこのように記述します。
mutation {
createUser(...) {
...UserFragment
createdAt
createdBy
}
}
query {
users{
...UserFragment
email
}
}
// フラグメント名 フラグメントを適用するデータ型
fragment UserFragment on User {
// 取得するフィールド
id
name
}
試してみよう!
以下のサイトで、指定したクエリを実行してみましょう!
[お題]
① allLift
クエリを使って、すべてのLiftとLiftに紐づくTrailを取得しましょう。
② allLift
に引数を指定して、OPEN
のリフトを取得しましょう。
③ allLift
に渡す引数を変数にして実行してみましょう。
④ setLiftStatus
で、上記クエリで取得したリフトのステータスを変更しましょう。
⑤ 1度のクエリで複数の命令を実行することもできます。
以下のように記述して、allLifts
とallTrail
を取得しましょう。
また、開発ツールのNetwork
から、クエリ実行時に1度のリクエストしかされていないことを確認しましょう。
query {
allLifts{
// 任意のフィールド
}
allTrails{
// 任意のフィールド
}
}
⑥ 以下のクエリのid
, name
を、フラグメントを使って共通化しましょう
query {
Lift(id:"astra-express") {
id
name
status
}
allLifts{
id
name
night
capacity
elevationGain
}
}
⑦ 以下のように記載してサブスクリプションを開始することができます。
隣の人にsetLiftStatus
を実行してもらい、ステータスが変更されたリフトのidを確認しましょう。
(隣に誰もいない方は、ブラウザを2つ立ち上げて実行してください)
subscription {
liftStatusChange {
id
name
status
}
}
⑧ 好きなクエリを実行したりスキーマ定義を確認してみましょう。
もっと勉強したい方に
GraphQLを試せるところ
GraphQLの関連パッケージ・サービス
GraphQL-Codegen
Apollo-Server-Express
Hasura
Apollo-Client
GraphQLを実装したい人
[書籍]初めてのGraphQL
[動画]react nest graphql typescriptまとめて勉強してみたい方向け(ちょっと古いかも?)
開発環境くれって方