【GraphQL】基礎的な書き方から、AniList API用のMutationクエリ(複数レコード更新)まで。

Anime

はじまり

リサちゃん
リサちゃん

あぁぁ、目が回りそうだぁ!

135ml
135ml

GraphQLを組み立てているね

リサちゃん
リサちゃん

これって、()なの? {}なの?

135ml
135ml

おさらいしてみましょうか

GraphQLを触ってみました

なので、今回はGraphQLに関する記事を書いてみます。

本記事では、GraphQLの基礎的な書き方をから、初心者でも迷わずにクエリを構築できるように、基本的なQueryとMutationの書き方を紹介します。

後の方では、実際にAniListというサービスで叩いた結果を紹介します。

GraphQLの基本的な部分

まず、GraphQLとは?

スゴイざっくり言うと、「APIにアクセスするための仕組み」です。

LinuxでおなじみのRed Hatさんの記事ではこう書いてあります。(会社の業務で触ったLinuxなつかしい・・・)

GraphQL (グラフキューエル、グラフQL) とは、API (アプリケーション・プログラミング・インタフェース向けのクエリ言語とサーバーサイドのランタイムの両方を指します。GraphQL は、クライアントがリクエストしたデータのみを返すことを優先します。

ここから引用

なるほど、クエリだけじゃなくてランタイムも引っくるめて「GraphQL」なんだな。

GraphQLは、同じAPIの仕組みの一つである「Rest API」と比べて、としては、データの取得や更新が行いやすいクエリ言語を備えています。

クエリを書く前に

クエリを書く前に、以下の2点を確認する必要があります。

1. GraphQLスキーマ

GraphQLスキーマは、GraphQLサーバーで提供されているデータの種類と構造を定義するものです。スキーマは、SDL(Schema Definition Language)と呼ばれる言語で記述されます。

2. 取得したいデータ

どのようなデータを取得したいかを明確にしましょう。具体的な項目名や関連データなども考慮する必要があります。

クエリの書き方

基本構文

GraphQLクエリの基本構文は次のとおりです。

query {
  # 取得したいデータのフィールドを記述
}

例えば、ユーザー情報と投稿情報を取得するクエリは次のようになります。

query {
  user {
    id
    name
    birthDate
  }
  posts {
    id
    title
    content
  }
}

このクエリは、userフィールドとpostsフィールドを指定しています。userフィールドではIDとユーザー名を取得し、postsフィールドではID、題名、内容を取得します。

Arguments

取得する情報に条件を付けることが出来ます。

query {
  users(name: "Tom") {
    id
    name
    age
    birthDate
  }
}

この場合は、名前が「Tom」だけのユーザーを取得することになります。

Nest

ときどき、取得する情報が細分化されて、さらに属性を持っていたりもします。

query {
  users(name: "Tom") {
    id
    name
    age
    birthDate {
      year
      month
      day
    }
  }
}

この場合は、ユーザーの誕生日が、さらに年、月、日に分かれているパターンとなります。

Variables

そして、属性のパラメータ値は、まとめて別の場所で宣言することが出来ます。

例えば、こんなクエリに対して、

query {
  users(name: "Tom") {
    id
    name
    age
    birthDate(year: 2023) {
      year
      month
      day
    }
  }
}

queryフィールドとは別に、variablesフィールドとして、パラメータを一括に宣言できます。

宣言方法は次の節で・・・

{
  userName: "Tom"
  birthYear: 2023
}

query ($username: String, $birthYear: Int) {
  users(name: $userName) {
    id
    name
    age
    birthDate(year: $birthYear) {
      year
      month
      day
    }
  }
}

パラメータに使える型は、String、Int、Float、Booleanなど、色々あります。

JavaScriptとかでJSON.stringifyすれば・・・

JavaScritpなどでクエリを作る際に、JSON.stringify()を使えば、クエリとは別にvariablesを宣言できます。

例えば、JavaScriptライクな言語「Google Apps Script」からGraphQLを打つ場合は、こんな感じでAPIにアクセスする記述が出来ます。

let variables = {
  userName: "Tom"
  birthYear: 2023
}

let query = `query ($username: String, $birthYear: Int) {
  users(name: $userName) {
    id
    name
    age
    birthDate(year: $birthYear) {
      year
      month
      day
    }
  }
}
`;

let options = {
  method: "POST"
  , headers: {
    "Content-Type": "application/json",
    "Accept": "application/json"
  }, payload: JSON.stringify({
    query: query
    , variables: variables
  })
};

let response = UrlFetchApp.fetch(url, options);

Mutation

さて、queryフィールドでは、データを参照するのが主な用途ですが、データを書き換えたりもしたくなります。

そこで使うのが、mutationフィールドです。

mutation {
  createUser(input: { name: "Mary", age: 94 }) {
    id
    name
    age
  }
}

mutationフィールドでは、編集する属性を指定して、どの属性をレスポンスとして受け取るかを指定することが出来ます。上記のクエリの場合だと、IDがアッチ側で自動採番だとしてこんな風に返ってきます。

{"data":{"createUser":{"id":3,"name":"Mary,"age":94}}}

Mutationでvariables

mutationフィールドでも、もちろんvariablesが利用できます。

宣言方法は、「JavaScriptとかでJSON.stringifyすれば・・・」の節と同様。

variables = {
  userName: "Mary"
  age: 94
}

mutation ($userName: String, $age: Int) {
  createUser(input: { name: $userName, age: $age }) {
    id
    name
    age
  }
}

Mutation内のNestにvariablesを使う

mutationフィールド内で入れ子になっている部分にvariablesを使いたい場合に、こんがらがってしまうかもしれません。(実際に僕はこんがらがりました。)

こんな風にします。

variables = {
  userName: "Mary"
  age: 94
  birthYear: 2005
}

mutation ($userName: String, $age: Int) {
  createUser(input: { name: $userName, age: $age, birthDate: {year: $birthYear}) {
    id
    name
    age
    birthDate {
      year
      month
      day
    }
  }
}

あくまで、createUserの「{}」の中身は「参照」したい属性なので、「()」の引数に変数を「設定」しましょう。

variables = {
  userName: "Mary"
  age: 94
  birthYear: 2005
}

mutation ($userName: String, $age: Int) {                                           // <- 変数を設定したい
  createUser(input: { name: $userName, age: $age, birthDate: {year: $birthYear}) {  // <- 対象を設定したい
    id                             // <- 参照したい
    name                           // <- 参照したい
    age                            // <- 参照したい
    birthDate {
      year                         // <- 参照したい
      month                        // <- 参照したい
      day                          // <- 参照したい
    }
  }
}

そして、実際にAniList APIで叩いてみた

「AniList」というのは、自分が今までに見たアニメや、これから見たいアニメ、読みたいマンガなどを登録、管理できるサービスです。

AniList

APIのリファレンスのGetting-Startedはここで、GraphQLの属性のリファレンスはここです。

その管理しているデータベースには、「AniList API」を使って参照することができて、編集することも出来ます。

こんな風に打ち込みます。まずはqueryでデータ取得です。

variables = {
  username: "anilistUserName"
  , userid: "anilistUserId"
}

query ($username: String, $id: Int) {
  MediaListCollection(userName: $username, userId: $id, type: ANIME) {
    lists {
      entries {
        media {
          id
          title {
            native
          }
          coverImage {
            extraLarge
          }
          siteUrl
          studios (isMain: true, sort: FAVOURITES) {
            nodes {
              name
            }
          }
        }
        score (format: POINT_100)
        status
        progress
        completedAt {
            year
            month
            day
        }
        notes
        updatedAt
      }
    }
  }
}

次で、mutationでデータの編集をしています。

variables = {
  mediaId: 21127
  , status: "CURRENT"
  , score: 100
  , progress: 13
  , completedAtYear: 2022
  , completedAtMonth: 1
  , completedAtDay: 13
  , notes: "エル・プサイ・コングルゥ。"
}

mutation ($mediaId: Int, $status: MediaListStatus, $score: Float, $progress: Int, $completedAtYear: Int, $completedAtMonth: Int, $completedAtDay: Int, $notes: String) {
  SaveMediaListEntry (mediaId: $mediaId, status: $status, score: $score, progress: $progress, notes: $notes, completedAt: {year: $completedAtYear, month: $completedAtMonth, day: $completedAtDay}) {
    id
    mediaId
    status
    score (format: POINT_100)
    progress
    completedAt {
      year
      month
      day
    }
    notes
  }
}

レスポンス

{"data":{"SaveMediaListEntry":{"id":246757832,"mediaId":21127,"status":"CURRENT","score":100,"progress":13,"completedAt":{"year":2022,"month":1,"day":13},"notes":"\u30a8\u30eb\u30fb\u30d7\u30b5\u30a4\u30fb\u30b3\u30f3\u30b0\u30eb\u30a5\u3002"}}}

複数レコードを編集したいときは?

AniList APIでは、「1分間に90リクエストまで」のレート制限が設けられています。

そのため、複数のアニメを編集したいときは、クエリをまとめて送りたいですよね。

Google Apps Scriptで組むと、こんな風に複数レコードに対する1クエリが作成できます。

let variableses = [
      {
        "mediaId": 21127
        , "status": "CURRENT"
        , "score": 100
        , "progress": 13
        , "completedAtYear": 2021
        , "completedAtMonth": 1
        , "completedAtDay": 13
        , "notes": "エル・プサイ・コングルゥ。"
      }
      , {
        "mediaId": 9253
        , "status": "PLANNING"
        , "score": 90
        , "progress": 13
        , "completedAtYear": 2017
        , "completedAtMonth": 3
        , "completedAtDay": 15
        , "notes": "ファーーーハッハッハッハ"
      }
    ];
    console.log(variableses.length);
    console.log(`getRequestOptionsAnilist: 3333333333333333333333333333333333333333333333`);
    for(let i = 0; i < variableses.length; i++){
      query = `
        ${query}
        saveMedia${i}: SaveMediaListEntry (mediaId: ${variableses[i]["mediaId"]}, status: ${variableses[i]["status"]}, score: ${variableses[i]["score"]}, progress: ${variableses[i]["progress"]}, notes: "${variableses[i]["notes"]}", completedAt: {year: ${variableses[i]["completedAtYear"]}, month: ${variableses[i]["completedAtMonth"]}, day: ${variableses[i]["completedAtDay"]}}) {
          id
          mediaId
          status
          score (format: POINT_100)
          progress
          completedAt {
            year
            month
            day
          }
          notes
        }
      `;
    }
    query = `mutation {${query}}`;
    console.log(query);
    console.log(`getRequestOptionsAnilist: 44444444444444444444444444444444444444444444`);

    options = {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${access_token}`,
        "Content-Type": `application/json`,
        "Accept": `application/json`,
      }
      , payload: JSON.stringify({
        query: query
      })
      , muteHttpExceptions : true
    }

上記でミソなのは、SaveMediaListEntryの前に、saveMedia${i}:という風にフィールドに名前を付けている点です。

少し脱線しますが、1つのリクエストに付きのクエリの長さのレート制限は、確認することが出来ませんでした。まあ程々にでしょうか。

まとめ

以上がGraphQLの基礎的なクエリの書き方と少し応用といった感じでした。

querymutationを使いこなして、効果的にデータを取得・変更できるようになると、GraphQLの真価がより体感できるでしょう。

ぜひこれを参考に、日々の活動でGraphQLを活用してみてください。

おしまい

リサちゃん
リサちゃん

ほぁぁ、こう書くのか!

135ml
135ml

一回書けてしまえばなぁ。

以上になります!

コメント

タイトルとURLをコピーしました