412
393

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DMM.comAdvent Calendar 2018

Day 20

Web API初心者と学ぶGraphQL

Last updated at Posted at 2018-12-19

DMM.com Advent Calendar 2018 20日目の記事です。

DMM GAMES プラットフォーム開発本部 PFシステム部所属の@SiragumoHuinです。

ゲーム自体を作るゲームエンジニアではなく、https://games.dmm.comの各画面やソーシャルアプリケーションプロバイダー(SAP)さんが使うAPIを提供しているWebグループに所属してるサーバサイドエンジニアです。
(記事を書いた当時)本日で24歳になりましたが、Webグループでは一番の若造です(新卒2年目)

tl;dr;

  • GraphQL自体はフレームワークや特定の技術を指すのではない
  • GraphQLはRESTの置き換えではない
    • ただし、RESTっぽく実装できないわけでもない
  • スキーマファースト開発ができる
  • マイクロサービスとも相性が良い
  • 事例はまだ少ない
  • ライブラリの充実度や知見もこれから
  • GraphQLで遊べるサンプル

はじめに

想定読者

  • GraphQLを学びたい人、学び始めた人
    • フロントエンジニアもサーバサイドエンジニアも
  • スキーマの書き方やQuery、Mutationの書き方を知りたい人
    • ちょっと踏み込んだQueryやMutationの書き方を知りたい人
  • 公式ドキュメントが良いって聞くけど、英語読むのツライ人
  • 実際に動かせてGraphQLを学べるものが欲しい人

この記事のサポート対象外

  • GraphQLを使うプロダクトやアーキテクチャの細かな説明

GraphQL概論

GraphQLとは

GraphQLそのものがどのような技術なのかを説明した記事はすでにいい記事がたくさんあるので、要約の箇条書きとリンクに留めます。

RESTと何が違う?

RESTはメジャーな形式ですし、GraphQLが設計から携わった初めてのWeb APIであるような人はあんまりいない(自分はそうだったが)と思うので、以下の巨人の肩に立たせていただくことにします。
基本的にはGraphQLの仕様や解決したいことを読み取れれば自然とRESTとの違いがわかると思います。

GraphQLはRESTの置き換えではない
アプリ開発の流れを変える「GraphQL」はRESTとどう違うのか比較してみた

APIリクエストを束ねて効率化

Restful APIでは、複数リソースが必要な複雑な画面を構成する際に、複数のAPIリクエストが必要になります。
RESTを綺麗に守れば守るほどパフォーマンスが落ち、コードも複雑になる欠点がありました。
RESTfulなAPIはURLとmethodでリソースを表現するためです。
GraphQLでは、単一のエンドポイントへ、欲しいリソースをHTTP POSTのbodyに明示的に記載してリクエストすることで、必要なリソースを1回のリクエストで取得し、この問題を解消しようと試みています。

スキーマファースト開発

スキーマファースト開発とは、

API開発者とフロントエンド開発者の間で事前にAPIのスキーマを相談して決定し、パラレルに開発することで結合時の手戻りを防ぐプロセス

です。GraphQLはスキーマ定義が重要であり、そもそもスキーマに齟齬があるとGraphQLサーバを起動することができません。
弊社のプラットフォーム開発では、まず始めに第一稿のスキーマを定義し、それをバックエンドエンジニア(API開発者)とフロントエンジニア双方で共有して、リファクタリングしていく形式を取っています。
バックエンドとフロント、両方の認識がGraphQLファイルに書かれたGraphQLスキーマに集約されることになります。
これによって、認識違いや結合時のトラブルを避けることができます。
フロント側とバックエンドとのやり取りにおいて、GraphQLスキーマというコードベースで議論が可能で、GitHubのissueやPRで修正依頼を出すことができるので効率的です。
GraphQLスキーマがバックエンドとフロントエンドとの共通言語としての役割を果たします。

マイクロサービスのAPI Gateway

弊社のプラットフォームではマイクロサービスとして、ゲームプラットフォームの機能を提供しようと開発を進めているのですが、GraphQLはマイクロサービスのGateway(中間層)としても良い役割を発揮します。
GraphQLサーバはスキーマ定義に対応する各データを複数のデータソースから取得しますが、GraphQLサーバから直接DBを参照しても良いですし、既存のRESTfulなAPIを内部的に利用してデータを取得しても良いです。
GraphQLの開発モデル

RESTの常識が通じないところ

GraphQLには上記のようなメリットがありますが、その仕様上、RESTで行っていた手法では解決できない問題も発生します。

リソース制限

サーバ負荷のためにAPIの利用制限をかけることは多いと思います。
RESTではN回/時間で制限をかけるのが多いと思いますが、GraphQLではクライアントが実行するクエリによって、データリソース数が変わり、負荷も変わってしまうため、負荷の計算方法を考えて制限をかけなければいけません。

キャッシュ効率

あまり詳しくないので引用だけ・・・

GraphQLは単一エンドポイントなので、HTTPのCache-ControllのようなURLベースのキャッシュ機構やCDNでのキャッシュはそのままでは使えない。もちろん、キャッシュが使えないわけではなくサーバー、CDN、クライアントの各層でキャッシングの現実的な実装が用意されている。

エラーハンドリング

RESTではエラーはHTTPのステータスコードで表現されます。
しかし、GraphQLではリクエストが成功すれば必ず200を返し、バリデートなどの何かエラーが発生した時はエラーオブジェクトに内容が吐かれてレスポンスが返ってきます。
ここに関しては、後述する実際に動くものでどのようにエラーが吐かれるのかをチェックしていきましょう。

モニタリング

GraphQLは単一のエンドポイントなので、エンドポイントごとにレスポンス性能を監視することはできません。

学習コスト

最近は日本語の記事も増えつつありますが、事例もRestful APIと比べてしまうと圧倒的に少ないです。
GraphQLはプログラミング言語に依らない技術ですが、フレームワークに関しては言語によってサポートの充実度が違う印象を受けます。
また、ちょっと複雑なクエリを書こうとするとサンプルがそこまで書かれていないので戸惑ったりしました。
本記事ではそこらへんをカバーしていければと思います。

動かして学ぶGraphQL

GraphQLを一通り楽しむことができるサンプルを用意しました。

環境

以下が満たせればOKです

  • yarnコマンドが動いて、Nodeで建てたサーバ(ローカルホスト)にWebブラウザでアクセスできる

言語

サーバサイドの言語は、本当は型があるTypeScriptが良いのですが、横着してJavaScriptです(ゴメンナサイ)

リポジトリ

Query

QueryとはRestfulなAPIでいうところのGetにあたります。
取得の結果が冪等になるものです。

1. 簡単なQuery

GraphQLスキーマ

まずはGraphQLスキーマを作っていきます。
拡張子は.graphql.gqlです。

1/text.graphql
type Query {
    text: Text
}

type Text {
    textData: String # テキスト内容
}

QueryオブジェクトとTextオブジェクトが宣言されています。

Queryオブジェクト

Queryオブジェクトでは、textという名前のQueryでTextというオブジェクトを取得するという定義が書かれています。
Queryオブジェクトは特殊なオブジェクトです。
省略されていますが実は、

schema {
    query: Query
    mutation: Mutation
}

という定義がデフォルトでされています。
(Mutationについては後述)

Textオブジェクト

String型のtextDataをフィールドとして持つTextオブジェクトという定義がされています。

GraphQLサーバ

GraphQLサーバ側のコードです。

1/index.js
const {GraphQLServer} = require('graphql-yoga');

const resolvers = {
    Query: {
        text: (obj, args, context, info) => {
            // 本当はDBなどから取得する
            const text =
                {
                    textData: 'Hello GraphQL',
                };
            return text;
        },
    },
};

const server = new GraphQLServer(
    {
        typeDefs: './src/1/text.graphql',
        resolvers,
    }
);
server.start(() => console.log(`Server is running on http://localhost:4000`));

リゾルバの中でQueryに対応する処理を書きます。
実際はDBなどからデータを取得するのですが、本質ではないのでべた書きしています。

実行

実際にGraphQLサーバを起動して、クエリを投げてみましょう。
クローンしたリポジトリ直下で、node src/1/index.js でGraphQLが起動します。
初回はyarn installを忘れずに実行してください。
エラーなく起動できたら、ブラウザでhttp://localhost:4000 にアクセスしてください。
Playgroundが起動しているはずです。

Playground

左側にクエリを書いていきます。
Control + Spaceで補完が効くので、頼りにしながら書くのが良いでしょう。

クエリ
query {
  text {
    textData
  }
}

書けたら中央の:arrow_forward:を押して、実行します。
以下のようなJSONが返ってくれば成功です。

結果
{
  "data": {
    "text": {
      "textData": "Hello GraphQL"
    }
  }
}

確認できたらGraphQLサーバをControl + Cで停止させてください。

注意:スキーマやサーバ側のコードを変更した場合はサーバを再起動させないと反映されません
また、Playgroundの補完等の読み込みが遅れる場合があるので、その場合はリロードしてください

2. 取得するフィールドを追加してみる

GraphQLスキーマ

2/text.graphql
type Query {
    text: Text
}

type Text {
    textId: ID! # テキストID
    textData: String! # テキスト内容
}

TextオブジェクトにtextId: ID!が増えました。
また、textDataの型の後ろに!が増えています。
これは、必須であることを示しており、リゾルバで型にあったフィールドを必ず返す必要があります。
ID型という見慣れない型がありますが、これは基本的にStringで、キャッシュのキーとしてよく使用される一意の識別子を表すスカラー型です。
型については5で詳しく解説します。

GraphQLサーバ

2/index.js
const {GraphQLServer} = require('graphql-yoga');

const resolvers = {
    Query: {
        text: (obj, args, context, info) => {
            // 本当はDBなどから取得する
            const text =
                {
                    textId: 'T1000',
                    textData: 'Hello GraphQL',
                };
            return text;
        },
    },
};

const server = new GraphQLServer(
    {
        typeDefs: './src/2/text.graphql',
        resolvers,
    }
);
server.start(() => console.log(`Server is running on http://localhost:4000`));

実行

GraphQLサーバを起動して、Playgroundでクエリを投げてみます。

2/クエリ
query {
  text {
    textId
    textData
  }
}

成功して、今度はtextIdも取得できたでしょうか?

2/結果
{
  "data": {
    "text": {
      "textId": "T1000",
      "textData": "Hello GraphQL"
    }
  }
}

3. 引数を取れるQueryを作ってみる

GraphQLスキーマ

3/text.graphql
type Query {
    text(textCondition: TextCondition!): Text
}

type Text {
    textId: ID! # テキストID
    textData: String! # テキスト内容
}

input TextCondition {
    textId: ID! # テキストID
}

text Queryに引数を追加しました。
textConditionという名前のTextConditionオブジェクトを引数に取ります。

引数であるTextConditionオブジェクトはinputを使って宣言します。
フィールドはTypeを使った場合と変わりません。

GraphQLサーバ

3/index.js
const {GraphQLServer} = require('graphql-yoga');

const resolvers = {
    Query: {
        text: (obj, args, context, info) => {
            console.log(args);
            console.log(args.textCondition.textId);
            let text;
            if (args.textCondition.textId === 'T1000') {
                text =
                    {
                        textId: 'T1000',
                        textData: 'Hello GraphQL',
                    };
            }
            return text;
        },
    },
};

const server = new GraphQLServer(
    {
        typeDefs: './src/3/text.graphql',
        resolvers,
    }
);
server.start(() => console.log(`Server is running on http://localhost:4000`));

argsの中に引数が入ってくるので、それがT1000だったら結果を返すようなリゾルバになっています。
本当なら、textIdをもとにDBやキャッシュからテキストデータを取得するような処理を書くと思います。

実行

3/クエリ
query {
  text(textCondition: { textId: "T1000" }) {
    textId
    textData
  }
}

以下のような結果が返って来れば成功です。

3/結果
{
  "data": {
    "text": {
      "textId": "T1000",
      "textData": "Hello GraphQL"
    }
  }
}

わざと引数を間違えてエラーを確認しても良いでしょう。

4. 複数件のテキストを取得してみる

GraphQLスキーマ

1件取得できたら、複数件取得したくなるものです。
複数のtextIdを引数に取り、複数のTextオブジェクトを取得できるようにしてみましょう。

4/text.graphql
type Query {
    text(textCondition: TextCondition!): [Text]
}

type Text {
    textId: ID! # テキストID
    textData: String! # テキスト内容
}

input TextCondition {
    textId: [ID]! # テキストID
}

配列っぽく書けばOKです。

GraphQLサーバ

単に配列で返せばOKです。
配列で返さなければエラーになってくれます。
引数も複数取得できるようになっていますが、それを利用してデータの取得とかはしてません。
自分が入れた引数が複数入っているのだけ確認してください。

4/index.js
const {GraphQLServer} = require('graphql-yoga');

const resolvers = {
    Query: {
        text: (obj, args, context, info) => {
            console.log(args);
            // 本当はIDを使ってDBから複数件取得します
            const text = [
                {
                    textId: 'T1000',
                    textData: 'Hello GraphQL',
                },
                {
                    textId: 'T1001',
                    textData: 'こんにちは GraphQL',
                },
            ];
            return text;
        },
    },
};

const server = new GraphQLServer(
    {
        typeDefs: './src/4/text.graphql',
        resolvers,
    }
);
server.start(() => console.log(`Server is running on http://localhost:4000`));

実行

Query Variablesを使ってみる

引数が増えることを想定すると実際にライブラリを使ってクエリを投げる際に、少し非効率なので、引数の部分をJSON形式で書くことができます。

4/クエリ
query($textCondition: TextCondition!)  {
  text(textCondition: $textCondition) {
    textId
    textData
  }
}

このようにクエリを書いて、Playgroundの左下にあるQUERY VARIABLESに以下のJSONを書きます。
JSONも補完が効くのでうまく使って書くと良いです。

4/Variables
{
  "textCondition": {
    "textId": ["T1000", "T1001"]
  }
}
4/結果
{
  "data": {
    "text": [
      {
        "textId": "T1000",
        "textData": "Hello GraphQL"
      },
      {
        "textId": "T1001",
        "textData": "こんにちは GraphQL"
      }
    ]
  }
}
スクリーンショット 2018-12-20 1.34.02.png

このようになれば成功です。

5. スカラー型とカスタムスカラー型とenum

スカラー型

そろそろGraphQLで使える型について説明しましょう。
GraphQLのでデフォルトで定義されている型をスカラー型と呼びます。
以下の5つがあります。

  • Int:符号付き32ビット整数
  • Float:符号付き浮動小数点
  • String:UTF-8文字シーケンス
  • Boolean:trueまたはfalse
  • ID:一意の識別子であることを示す以外はStringと同じ

カスタムスカラー型

これ以外にユーザが任意で型を作ることができます。
これをカスタムスカラー型と呼びます。
Date型などはよく定義されるのではないかと思います。
カスタムスカラー型はGraphQLではStringと同じ扱いです。

enum

いわゆる列挙型です。

GraphQLスキーマ

いろいろ詰め込んだスキーマを作ってみました。
内容はテキトーです。

5/text.graphql
scalar DateTime

type Query {
    text(textCondition: TextCondition!): [Text]
}

type Text {
    textId: ID! # テキストID
    textData: String! # テキスト内容
    length: Int! # 長さ
    bot: Boolean! # BOTからならtrue
    version: Float! # バージョン
    rank: Rank # ランク
    createDate: DateTime! # 生成日時
    updateDate: DateTime! # 更新日時
}

input TextCondition {
    textId: [ID]! # テキストID
}

enum Rank {
    GOLD
    SILVER
    COPPER
}

GraphQLサーバ

本来はDateTime型のバリデートなどを行なわないと意味がないのですが、とりあえず、カスタムスカラー型を定義できるんだなということがわかるサンプルに留めています。
詳しく扱わないといけない場合は以下の記事が参考になると思います。

Custom scalars and enums | GraphQL Tools

5/index.js
const {GraphQLServer} = require('graphql-yoga');

const resolvers = {
    Query: {
        text: (obj, args, context, info) => {
            console.log(args);
            let textData = 'Hello GraphQL';
            const text = [
                    {
                        textId: 'T1000',
                        textData: textData,
                        length: textData.length,
                        bot: true,
                        version: 1.1,
                        rank: 'GOLD',
                        createDate: "2018-12-20T10:57:34",
                        updateDate: "2018-12-20T10:57:34",
                    }
                ]
            ;
            return text;
        },
    },
};

const server = new GraphQLServer(
    {
        typeDefs: './src/5/text.graphql',
        resolvers,
    }
);
server.start(() => console.log(`Server is running on http://localhost:4000`));

実行

5/クエリ
query($textCondition: TextCondition!) {
  text(textCondition: $textCondition) {
    textId
    textData
    length
    bot
    version
    rank
    createDate
    updateDate
  }
}
5/Variables
{
  "textCondition": {
    "textId": "T1000"
  }
}
5/結果
{
  "data": {
    "text": [
      {
        "textId": "T1000",
        "textData": "Hello GraphQL",
        "length": 13,
        "bot": true,
        "version": 1.1,
        "rank": "GOLD",
        "createDate": "2018-12-20T10:57:34",
        "updateDate": "2018-12-20T10:57:34"
      }
    ]
  }
}

GraphQLは型バリデートされるという点がメリットです。
わざとIntStringにしてみたりして、エラーになるか確かめてみるのが良いでしょう。
また、botの情報とかいらないといった場合は削ることも可能です。
フィールドを削ってみたりして遊んでみてください。

Mutation

ようやくMutationになります。
MutationとはRESTful APIでいうところのCreate、Update、Deleteにあたるものです。
Mutationという名称の通り、変更をするために使います。

6. MutationでCreateとUpdate処理を作ってみる

GraphQLスキーマ

記述の仕方はQueryと変わりません。
ただ、graphql-yogaの仕様なのか、Mutationだけのスキーマは定義できないようです。

6/text.graphql
scalar DateTime

type Query {
    text(textCondition: TextCondition!): [Text]
}

type Mutation {
    createText(textCreateInput: TextCreateInput!): Text!
    updateText(textUpdateInput: TextUpdateInput!): Text!
}

type Text {
    textId: ID! # テキストID
    textData: String! # テキスト内容
    length: Int! # 長さ
    bot: Boolean! # BOTからならtrue
    version: Float! # バージョン
    rank: Rank # ランク
    createDate: DateTime! # 生成日時
    updateDate: DateTime! # 更新日時
}

enum Rank {
    GOLD
    SILVER
    COPPER
}

input TextCondition {
    textId: [ID]! # テキストID
}

# 登録インプット
input TextCreateInput {
    textData: String! # テキスト内容
}

# 更新インプット
input TextUpdateInput {
    textId: ID! # テキストID
    textData: String! # テキスト内容
}

GraphQLサーバ

例のごとく、GraphQLのサンプルに留めたので、実際にDBへデータをCreateしたりUpdateしたりはしないです。
脳内で補完をお願いします・・・

6/index.js
const {GraphQLServer} = require('graphql-yoga');

const resolvers = {
    Query: {
        text: (obj, args, context, info) => {
            console.log(args);
            let textData = 'Hello GraphQL';
            const text = [
                    {
                        textId: 'T1000',
                        textData: textData,
                        length: textData.length,
                        bot: true,
                        version: 1.1,
                        rank: 'GOLD',
                        createDate: "2018-12-20T10:57:34",
                        updateDate: "2018-12-20T10:57:34",
                    }
                ]
            ;
            return text;
        },
    },
    Mutation: {
        // create
        createText: (root, args) => {
            console.log(args);
            const text = {
                textId: 'T2000',
                textData: args.textCreateInput.text,
                length: args.textCreateInput.text.length,
                bot: true,
                version: 1.1,
                rank: 'GOLD',
                createDate: "2018-12-20T10:57:34",
                updateDate: "2018-12-20T10:57:34",
            };
            return text
        },
        // update
        updateText: (root, args) => {
            console.log(args);
            let text;
            if (args.textUpdateInput.textId === 'T2000') {
                text = {
                    textId: 'T2000',
                    textData: args.textUpdateInput.text,
                    length: args.textUpdateInput.text.length,
                    bot: true,
                    version: 1.1,
                    rank: 'GOLD',
                    createDate: "2018-12-20T10:57:34",
                    updateDate: "2018-12-20T10:57:34",
                };
            }
            return text
        },
    }
};

const server = new GraphQLServer(
    {
        typeDefs: './src/6/text.graphql',
        resolvers,
    }
);
server.start(() => console.log(`Server is running on http://localhost:4000`));

Create実行

Mutationのクエリ文に関してもQueryとほぼ変わらないです

6/クエリ
mutation($textCreateInput: TextCreateInput!) {
  createText(textCreateInput: $textCreateInput) {
    textId
    textData
    length
    bot
    version
    rank
    createDate
    updateDate
  }
}
6/Variables
{
  "textCreateInput": {
    "textData": "Mutationを使ってみる"
  }
}
結果
{
  "data": {
    "createText": {
      "textId": "T2000",
      "textData": "Mutationを使ってみる",
      "length": 14,
      "version": 1.1,
      "rank": "GOLD",
      "createDate": "2018-12-20T10:57:34",
      "updateDate": "2018-12-20T10:57:34"
    }
  }
}

Update実行

6/クエリ
mutation($textUpdateInput: TextUpdateInput!) {
  updateText(textUpdateInput: $textUpdateInput) {
    textId
    textData
    length
    bot
    version
    rank
    createDate
    updateDate
  }
}
6/Variables
{
  "textUpdateInput": {
    "textId": "T2000",
    "textData": "Updateみたいなことをしてみる"
  }
}
6/結果
{
  "data": {
    "updateText": {
      "textId": "T2000",
      "textData": "Updateみたいなことをしてみる",
      "length": 17,
      "bot": true,
      "version": 1.1,
      "rank": "GOLD",
      "createDate": "2018-12-20T10:57:34",
      "updateDate": "2018-12-20T10:57:34"
    }
  }
}

7. Deleteを作りつつ、入れ子のスキーマ定義を学ぶ

私が記事を書こうと思った理由が、オブジェクトの中にオブジェクトがある場合のクエリってどう書くんだろと思ったことにあります。
シンプルなクエリは公式ドキュメントを読めば事足りたのですが、入れ子の場合のクエリまで書かれているものが探せず・・・
Playgroundの補完機能を知らずに勘でクエリ書いていたのは内緒

GraphQLスキーマ

deleteTextMutationを追加しました。
また、MutationResultとValidationErrorを定義してエラー内容を返します。
GraphQLは通信の失敗以外で失敗してもHTTPステータスは200OKになるため、このような実装が必要になります。

7/text.graphql
scalar DateTime

type Query {
    text(textCondition: TextCondition!): [Text]
}

type Mutation {
    createText(textCreateInput: TextCreateInput!): Text!
    updateText(textUpdateInput: TextUpdateInput!): Text!
    deleteText(textDeleteInput: TextDeleteInput!): MutationResult!
}

type Text {
    textId: ID! # テキストID
    textData: String! # テキスト内容
    length: Int! # 長さ
    bot: Boolean! # BOTからならtrue
    version: Float! # バージョン
    rank: Rank # ランク
    createDate: DateTime! # 生成日時
    updateDate: DateTime! # 更新日時
}

# Mutaion Result
type MutationResult {
    errorCode: String! # エラーコード
    validationError: [ValidationError] # Validationエラー情報
}

# Validationエラー情報
type ValidationError {
    fieldName: String! # フィールド名
    validationCode: String! # Validationエラー情報
}

enum Rank {
    GOLD
    SILVER
    COPPER
}

input TextCondition {
    textId: [ID]! # テキストID
}

# 登録インプット
input TextCreateInput {
    textData: String! # テキスト内容
}

# 更新インプット
input TextUpdateInput {
    textId: ID! # テキストID
    textData: String! # テキスト内容
}

# 削除インプット
input TextDeleteInput {
    textId: [ID]! # テキストID(複数指定可)
}

GraphQLサーバ

7/index.js
const {GraphQLServer} = require('graphql-yoga');

const resolvers = {
    Query: {
        text: (obj, args, context, info) => {
            console.log(args);
            let textData = 'Hello GraphQL';
            const text = [
                    {
                        textId: 'T1000',
                        textData: textData,
                        length: textData.length,
                        bot: true,
                        version: 1.1,
                        rank: 'GOLD',
                        createDate: "2018-12-20T10:57:34",
                        updateDate: "2018-12-20T10:57:34",
                    }
                ]
            ;
            return text;
        },
    },
    Mutation: {
        // create
        createText: (root, args) => {
            console.log(args);
            const text = {
                textId: 'T2000',
                textData: args.textCreateInput.textData,
                length: args.textCreateInput.textData.length,
                bot: true,
                version: 1.1,
                rank: 'GOLD',
                createDate: "2018-12-20T10:57:34",
                updateDate: "2018-12-20T10:57:34",
            };
            return text
        },
        // update
        updateText: (root, args) => {
            console.log(args);
            let text;
            if (args.textUpdateInput.textId === 'T2000') {
                text = {
                    textId: 'T2000',
                    textData: args.textUpdateInput.textData,
                    length: args.textUpdateInput.textData.length,
                    bot: true,
                    version: 1.1,
                    rank: 'GOLD',
                    createDate: "2018-12-20T10:57:34",
                    updateDate: "2018-12-20T10:57:34",
                };
            }
            return text
        },
        // delete
        deleteText: (root, args) => {
            console.log(args);
            const mutationResult = {
                errorCode: '200',
                validationError: [
                    {
                        fieldName: 'fieldNameString',
                        validationCode: 'validationCodeString'
                    },
                    {
                        fieldName: 'fieldNameString2',
                        validationCode: 'validationCodeString2'
                    }
                ]
            };
            return mutationResult
        }
    }
};

const server = new GraphQLServer(
    {
        typeDefs: './src/7/text.graphql',
        resolvers,
    }
);
server.start(() => console.log(`Server is running on http://localhost:4000`));

実行

正解さえわかればどうってことはないですね。

7/クエリ
mutation($textDeleteInput: TextDeleteInput!) {
  deleteText(textDeleteInput: $textDeleteInput) {
    errorCode
    validationError {
      fieldName
      validationCode
    }
  }
}
7/Variables
{
  "textDeleteInput": {
    "textId": ["T3000", "T3001"]
  }
}
7/結果
{
  "data": {
    "deleteText": {
      "errorCode": "200",
      "validationError": [
        {
          "fieldName": "fieldNameString",
          "validationCode": "validationCodeString"
        },
        {
          "fieldName": "fieldNameString2",
          "validationCode": "validationCodeString2"
        }
      ]
    }
  }
}

サンプルについては以上になります。

実際にフロント側で使うときどうするの?

今まで、Playgroundを使ってクエリを実行していましたが、COPY CURLでコピーできるcurlコマンドのように、GraphQLで作られたAPIはcurlコマンド叩けます。

curl 'http://localhost:4000/' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: http://localhost:4000' --data-binary '{"query":"mutation($textDeleteInput: TextDeleteInput!) {\n  deleteText(textDeleteInput: $textDeleteInput) {\n    errorCode\n    validationError {\n      fieldName\n      validationCode\n    }\n  }\n}","variables":{"textDeleteInput":{"textId":["T3000","T3001"]}}}' --compressed

curlコマンドで叩けるということは、各言語で利用することも可能というわけです。
公式の説明はこちら
現時点でのGraphQLのクライアントライブラリでの有名所はApollo Clientでしょうか。
各言語でライブラリが充実するのを期待しています。

もっと踏み込んだチュートリアルがやりたい

英語ですが、以下のチュートリアルが充実しています。
The Fullstack Tutorial for GraphQL

やっぱり公式ドキュメント読んだほうが良いの?

はい。とてもわかりやすいです。
https://graphql.org/learn

まとめ

  • GraphQLはRESTの置き換えではない
    • ただし、RESTっぽく実装できないわけでもない
  • スキーマファースト開発ができる
  • マイクロサービスとも相性が良い
  • 事例はまだ少ない
  • ライブラリの充実度や知見もこれから

おわりに

参考にさせていただいた記事にこの場を借りて感謝を。

412
393
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
412
393

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?