Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
187
Help us understand the problem. What are the problem?
@shotashimura

GraphQLの基礎の基礎

はじめに

GraphQLについての知見が溜まってきたので、これから何回かに分けて記事としてまとめていきたいと思います。

GraphQLはWEB APIのクエリ言語であり、既存のデータに対してクエリ(データ取得の命令)を実行するためのランタイム(実行時に必要なもの)です。
REST APIと異なり、GraphQLではエンドポイントが1つだけであり、処理ごとに増やしていく必要もないので、管理しやすいことが大きな特徴です。

本記事では、GraphQL公式チュートリアルを参考に、実際にスキーマ言語を記述した上でGraphQL IDEにてクエリを書き、データの取得を実践します。
記事内で使用する言語はJavaScriptです。

GraphQLの歴史

GraphQLは2012年頃Facebook社が開発をスタートしたこときっかけに、2015年にはオープンソース化、2018年にはGraphQL Foundationが設立されました。
2020年秋現在、Facebookはもちろん、GitHubやPinterest、Cousereなどによる採用実績があります。

GraphQLの構成

GraphQLは大きく分けて「クエリ言語(フロント側、リクエスト)」と「スキーマ言語(サーバー側、レスポンス)」の2つによって構成されています。

クエリ言語とは?

GraphQLサーバーに対してリクエストをするための言語(合計3種類)

クエリの種類 意味 効果
query データ取得系 GET
mutation データ更新系 POST/PUT/DELETE...etc
subscription イベントの通知 Websocket

スキーマ言語とは?

・GraphQL APIの仕様を記述するための言語(本記事で主にエディターに書くのはこちら)
・リクエストされたクエリは、スキーマ言語で記述したスキーマに従ってGraphQL処理系により実行されて、レスポンスを生成する

リゾルバ
スキーマ言語はあくまでGraphQL APIの仕様の定義のみで実際のデータ操作は行いません。
実際のデータ操作を行うのがリゾルバという、特定のフィールドのデータを返す関数(メソッド)になります。

GraphQLのアーキテクチャ例

GraphQLを使ったアーキテクチャの例はこちらのページが参考になりました。
クライアントとサーバーの間にGraphQLサーバーを挟み、データの整形と受け渡しをするのが通例のようです。

下準備

プロジェクトを作った後に、npm経由でGraphQL.jsをインストールします。

mkdir GraphQLPrac
cd GraphQLPrac
touch server.js
npm init
npm install express express-graphql graphql --save

GraphQL APIサーバーを実行

GraphQL、Node.jsのフレームワークであるExpressを作業ディレクトリへインストールした状態でスタートします。

server.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

// GraphQLスキーマ言語を記述してスキーマを構築する
// スキーマはあくまで定義のみで実際のデータ操作は行わない
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// ルートは、APIエンドポイントごとにリゾルバ関数を提供します
// リゾルバとは特定のフィールドのデータを返す関数(メソッド)であり、実際のデータ操作を行う部分
const root = {
  hello: () => {
    return 'Hello world!';
  },
};

// Expressでサーバーを立てます
// graphiql: true としたので、GraphQLを利用できる
const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');

ターミナルからサーバーを立ち上げます。

node server.js
Running a GraphQL API server at http://localhost:4000/graphql

http://localhost:4000/graphql 
にて、GraphQLのIDEが利用出来るようになっています。
こちらで、クエリを書きます。
スクリーンショット 2020-10-12 20.40.41.png

このように、GraphQLでは、
①スキーマ定義にてクライアントが操作できるクエリや様々な型を定義し、
②リゾルバにてデータを返す操作を行い、
③クエリを書くことによって、
RESTAPIでいうところのCRUD処理が可能になります。
本例ではhelloと定義したスキーマの情報を取得しました。

Basic Types

GraphQLのスキーマ言語には下記の5つのスカラー型が存在します。スカラー型とは、プログラミング言語におけるデータ型のひとつであり、この型に属するデータは、大小を比較して順番に並べることが可能です。

・String(文字列型)
・Int(整数型)
・Float(浮動小数点型)
・Boolean(論理型)
・ID(ID型)

また、それぞれの型はリスト(順序つきのデータコンテナ)として定義することも可能であり、型を角かっこで囲み、 [String]や[Int]のように定義します。

server.js
const express = require('express');
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
const schema = buildSchema(`
  type Query {
    quoteOfTheDay: String
    random: Float!
    rollThreeDice: [Int]
  }
`);

//リゾルバ関数
const root = {
//quoteOfTheDay: String をスキーマで定義
  quoteOfTheDay: () => {
    return Math.random() < 0.5 ? "Take it easy" : "Salvation lies within";
  },
//random: Float! をスキーマで定義
  random: () => {
    return Math.random();
  },
//rollThreeDice: [Int] をスキーマで定義
  rollThreeDice: () => {
    return [1, 2, 3].map((_) => 1 + Math.floor(Math.random() * 6));
  },
};

const app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);
app.listen(4000);
console.log("Running a GraphQL API server at localhost:4000/graphql");
node server.js
Running a GraphQL API server at http://localhost:4000/graphql

スクリーンショット 2020-10-12 21.43.17.png

以上、様々なTypeを返すAPIの呼び出しを確認出来たかと思います。

引数の受け渡し

GraphQLではREST APIと同様に、GraphQLAPIのエンドポイントに引数を渡すのが一般的です。スキーマ言語で引数を定義することにより、タイプチェックが自動的に行われます。

exameple.js
type Query {
  rollThreeDice: [Int]
}

以下の例では実際に引数を渡すスキーマを構築します。

server.js
const express = require('express');
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
// スキーマに引数を指定している
const schema = buildSchema(`
  type Query {
    rollDice(numDice: Int!, numSides: Int): [Int]
  }
`);

//リゾルバ関数
const root = {
  //クライアント側のクエリから引数の値を受け取る
  rollDice: ({numDice, numSides}) => {
    let output = [];
    for (var i = 0; i < numDice; i++) {
      output.push(1 + Math.floor(Math.random() * (numSides || 6)));
    }
    return output;
  }
};

const app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

node server.js
Running a GraphQL API server at http://localhost:4000/graphql

スクリーンショット 2020-10-12 22.09.41.png

クエリで引数を渡すことにより、スキーマとリゾルバで定義した通りの結果が返ってくることを確認しました。

オブジェクトタイプ

1つ以上のスキーマで定義されているフィールドの集合をオブジェクト型といい、JSONのように入れ子にすることが可能です。

example.js

//type RandomDieの定義を「getDie」で利用
const schema = buildSchema(`
  type RandomDie {
    numSides: Int!
    rollOnce: Int!
    roll(numRolls: Int!): [Int]
  }

  type Query {
    getDie(numSides: Int): RandomDie
  }
`);

リゾルバも含めたコード。

server.js
const express = require('express');
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
const schema = buildSchema(`
  type RandomDie {
    numSides: Int!
    rollOnce: Int!
    roll(numRolls: Int!): [Int]
  }

  type Query {
    getDie(numSides: Int): RandomDie
  }
`);

//リゾルバ関数内の処理をクラス化することも可能です
class RandomDie {
  constructor(numSides) {
    this.numSides = numSides;
  }

  rollOnce() {
    return 1 + Math.floor(Math.random() * this.numSides);
  }

  roll({numRolls}) {
    let output = [];
    for (var i = 0; i < numRolls; i++) {
      output.push(this.rollOnce());
    }
    return output;
  }
}

//リゾルバ関数
const root = {
  getDie: ({numSides}) => {
    return new RandomDie(numSides || 6);
  }
}

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));

node server.js
Running a GraphQL API server at http://localhost:4000/graphql

スクリーンショット 2020-10-12 23.01.21.png

オブジェクト型を使うと、JSONのような整った構造で返ってくることが分かります。

MutationとInput Types

データの作成や更新、削除はQueryではなくMutatisonという異なるルート型を使用します。
Mutatisonとして利用するスキーマの引数にはtypeキーワードの代わりにinputキーワード使うことが多く、オブジェクト型を簡単に渡すことが可能になります。

example.js
input MessageInput {
  content: String
  author: String
}

type Message {
  id: ID!
  content: String
  author: String
}

type Query {
  getMessage(id: ID!): Message
}

// データの追加と更新をスキーマ定義
// 引数にはinputキーワードで定義したMessageInputを使用
type Mutation {
  createMessage(input: MessageInput): Message
  updateMessage(id: ID!, input: MessageInput): Message
}

リゾルバも含めたコード。

server.js
var express = require('express');

const express = require('express');
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
const schema = buildSchema(`
input MessageInput {
    content: String
    author: String
  }

  type Message {
    id: ID!
    content: String
    author: String
  }

  type Query {
    getMessage(id: ID!): Message
  }

  type Mutation {
    createMessage(input: MessageInput): Message
    updateMessage(id: ID!, input: MessageInput): Message
  }
`);

class Message {
  constructor(id, {content, author}) {
    this.id = id;
    this.content = content;
    this.author = author;
  }
}

// データの入れ物
let fakeDatabase = {};

const root = {
  getMessage: ({id}) => {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    return new Message(id, fakeDatabase[id]);
  },
  createMessage: ({input}) => {
    // ランダムなIdを生成
    var id = require('crypto').randomBytes(10).toString('hex');

    fakeDatabase[id] = input;
    return new Message(id, input);
  },
  updateMessage: ({id, input}) => {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    // 古いデータの書き換え
    fakeDatabase[id] = input;
    return new Message(id, input);
  },
};

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000, () => {
  console.log('Running a GraphQL API server at localhost:4000/graphql');
});

node server.js
Running a GraphQL API server at http://localhost:4000/graphql

createMessage.png

新しくメッセージを作成

updateMessage.png

メッセージの内容を更新

message-wuery.png
内容の確認(ここはQueryを利用)

Mutationを利用して、メッセージデータの作成と更新を確認することが出来ました!!

おわりに

以上、GraphQLの基本部分について網羅的に掲載する事が出来たと思います。
はじめはクエリ言語とスキーマ言語の違いや書き方、それぞれの役割が整理出来ず混乱しておりましたが、公式を読み返しつつ手を動かすことで理解が深まりました。

次の記事ではGraphQLのサブスクリプションについて書いています!🚀
で、実際にどう使うの?という方は、こちらの記事にて、フロントにVueを使った上で、実際のアプリケーションの中でGraphQLを使う方法についてまとめていますので、宜しければ参考にしてください!!

GraphQLを基礎から体型的に学びたい方へご案内

11月の末に基礎からはじめるGraphQLという技術書をウェブ出版しました。
GraohQLの基礎文法や概念を一から学び、実際にGraohQLを利用したアプリケーションの完成を目指す内容となっております。
Amazon Kindle Unlimitedにご加入の方は無料でご覧頂けますので、宜しければ参考にしてみてください!😆

それでは、また😊

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
187
Help us understand the problem. What are the problem?