✨ 短時間で分かる!GraphQLの基本とRESTとの違い
GraphQLとRESTの違い
GraphQLとRESTは、どちらもWeb上でデータを取得・操作するためのAPI設計手法ですが、それぞれ異なる特徴があります。
以下の表は要点を比較したものになります。
項目 | GraphQL | REST |
---|---|---|
設計思想 | クライアントが必要なデータを指定して取得 | サーバーが提供するリソースにアクセス |
エンドポイント | 1つのエンドポイントで対応 | リソースごとに複数のエンドポイントが必要 |
データ取得 | 必要なデータだけ取得できる | 一度に全てのデータが取得されることが多い |
オーバーフェッチ | なし | あり |
リクエスト数 | 一度のリクエストで複数のデータを取得可能 | 各リソースごとに個別のリクエストが必要 |
REST
RESTは、リソースを一意に識別するURLによって、データの取得や操作を行うAPIの設計手法です。一般的には、リソースごとに複数のエンドポイントが必要で、一度に全てのデータが取得されることが多いため、オーバーフェッチが発生しやすいです。また、各リソースごとに個別のリクエストが必要になるため、リクエスト数が多くなることがあります。
GraphQL
GraphQLは、Facebookが開発したAPIの設計手法で、クライアントが必要なデータ、データ構造を指定して取得できます。一つのエンドポイントで複数のリソースにアクセスすることができ、オーバーフェッチ(必要以上にデータを取得すること)を防ぐことができます。また、一度のリクエストで複数のデータを取得できるため、リクエスト数が少なくなります。
両者を比較して分かる通り、GraphQLはRESTの課題を解決することを目指した設計だということが見えますね。
RESTとの違いを理解するために、例を見てみましょう
Articleには、id、title、subTitle、contentがあります。そしてアプリケーションはidとtitleだけを取得する必要があるとします。 **/api/v1/articles
**のようなRESTエンドポイントを設計すると、すべてのフィールドのデータが取得されます。
/api/v1/articles
の場合
{
"data": {
"articles": [
{
"id": "A001",
"title": "GraphQL入門",
"subTitle": "RESTful APIとの比較",
"content": "GraphQLは、データ取得の効率性と柔軟性を向上させるために開発されたクエリ言語です。この記事では、GraphQLの基本概念とRESTful APIとの違いについて説明します。"
},
{
"id": "A002",
"title": "次世代データベース技術",
"subTitle": "NoSQLデータベースの利点",
"content": "NoSQLデータベースは、従来のリレーショナルデータベースとは異なるアプローチを採用しており、大規模なデータセットや高いパフォーマンスを求めるアプリケーションに適しています。この記事では、NoSQLデータベースの主な利点と種類について解説します。"
}
]
}
}
上記を見て分かる通り、subTitleやcontentなどの不要なデータも取得してしまっています。もちろんエンドポイントを増やして/api/v1/aritlces-id-and-title
などとする方法もあるかもしれませんが、エンドポイントが増えすぎて管理できなくなったり命名にも困ってしまいますね。
ではGraphQLクエリの場合はどのようになるのでしょうか。
{
articles {
id
firstName
}
}
上記のクエリを投げた場合、必要なデータのみが取得されオーバーフェッチされません
{
"data": {
"articles": [
{
"id": "A001",
"title": "GraphQL入門"
},
{
"id": "A002",
"title": "次世代データベース技術"
}
]
}
}
次に、RESTではリクエスト数が多くなるが、GraphQLでは一度のリクエストで済むとはどういうことでしょうか。
Articleオブジェクトには、属性id、title、subTitle、contentがあります。それに加えて別のrelatedArticleオブジェクトが存在し、属性id、titleがあるとします。Articleオブジェクトは、relatedArticleオブジェクトと関連付けられています。記事の詳細とそれらの関連記事の詳細を取得するためにREST APIを使用する場合、サーバーへの2つのリクエスト(/api/v1/articlesと/api/v1/relatedArticles)を行うことになります。一つのリクエストでは不十分であるため、必要なデータを得るためにサーバーへ複数の呼び出しを行わなければなりません。この課題点に関しては何度か出会したことがある人も多いかと思います。いわゆるN+1問題になりますね。
ただし、GraphQLを使用することで、モバイルアプリケーションは、ArticleオブジェクトとrelatedArticleオブジェクトの両方の詳細を1回のリクエストで取得できます。
GraphQLを使用する場合
以下は、データを取得するためのGraphQLクエリです
{
articles {
id
title
subTitle
content
relatedArticle {
id
title
}
}
}
このクエリにより、記事とそれらの関連記事の詳細が1つのリクエストで効率的に取得されます。
{
"data": {
"articles": [
{
"id": "A001",
"title": "GraphQL入門",
"subTitle": "RESTful APIとの比較",
"content": "GraphQLは、データ取得の効率性と柔軟性を向上させるために開発されたクエリ言語です。この記事では、GraphQLの基本概念とRESTful APIとの違いについて説明します。",
"relatedArticles": [
{
"id": "A003",
"title": "APIセキュリティのベストプラクティス"
},
{
"id": "A004",
"title": "マイクロサービスアーキテクチャ入門"
}
]
},
{
"id": "A002",
"title": "次世代データベース技術",
"subTitle": "NoSQLデータベースの利点",
"content": "NoSQLデータベースは、従来のリレーショナルデータベースとは異なるアプローチを採用しており、大規模なデータセットや高いパフォーマンスを求めるアプリケーションに適しています。この記事では、NoSQLデータベースの主な利点と種類について解説します。",
"relatedArticles": [
{
"id": "A005",
"title": "データウェアハウジング入門"
},
{
"id": "A006",
"title": "ビッグデータ分析の基本"
}
]
}
]
}
}
GraphQLとRESTの使い分け
GraphQLとRESTはそれぞれ利点と欠点があります。どちらを選ぶかは、プロジェクトの要件やチームの経験によって異なります。
一般的には以下のように捉えられているようです。
GraphQLを選ぶべきシチュエーション
- クライアント側で柔軟にデータ構造を変更したい場合
- リクエスト数を減らしたい場合
- オーバーフェッチやアンダーフェッチを防ぎたい場合
- モバイルアプリケーションのパフォーマンスを最適化したい場合
RESTを選ぶべきシチュエーション
- チームがRESTに慣れていて、学習コストをかけたくない場合
- シンプルなAPIを迅速に構築したい場合
- キャッシュや既存のHTTPインフラを活用したい場合
- APIのバージョン管理が重要な場合
GraphQLの主な特徴
上述した箇所と繰り返す部分はありますが、GraphQLの特徴としては以下のようなものがあります。
- データ指定: クライアントは必要なデータを明示的に指定することができます。これにより、サーバーから不要なデータを取得しないため、帯域幅を節約できます。
- 単一エンドポイント: 1つのエンドポイントを使用してすべてのデータを取得および操作します。これにより、APIの設計がシンプルになり、エンドポイントの管理が容易になります。
- スキーマ定義: データ構造と型をスキーマとして定義します。スキーマを用いることで、APIのドキュメントが自動生成され、データの整合性が保たれます。
- リアルタイム通信: GraphQLは、リアルタイムデータの取得をサポートしています。サブスクリプションを使用することで、クライアントはデータの変更をリアルタイムで受信できます。
スキーマやサブスクリプションなどの特徴が出てきましたが、どのようなものかに関しては後ほど説明していきます。
少し余談ですが、何故GraphQLという名前がつけられたのでしょうか?
GraphQLの名前は、「Graph」というデータ構造を利用することを強調する「Graph」と、データ取得・操作のための「QL」(Query Language)の2つの部分から成り立っています。
ここでの「グラフ」は、数学やコンピューターサイエンスで広く使われているグラフ理論におけるグラフ概念を指しています。グラフは、頂点(ノード)とそれらを結ぶ辺(エッジ)から構成されるデータ構造です。
GraphQLでは、データをグラフとして扱います。つまり、各データオブジェクト(ノード)がエッジで関連付けられた階層構造を持っています。クライアントは、このグラフ構造に沿ってデータを取得するため、このような名前になっているわけです。
GraphQLの基本概念
タイプ
型付けが可能で、クエリはフィールドとそれらに関連するデータ型に基づいています。GraphQLクエリで型の不一致がある場合、アプリケーションはエラーメッセージを返します。
これにより、スムーズなデバッグとバグの容易な検出が可能になります。
type Query {
article(id: ID!): Article
}
type Mutation {
createArticle(input: ArticleInput!): Article
}
type Subscription {
articleCreated: Article
}
type Article {
id: ID!
title: String!
author: Author!
}
type Author {
id: ID!
name: String!
}
input ArticleInput {
title: String!
authorId: ID!
}
この書き方はSDL(Schema Definition Language)と呼ばれ、GraphQLスキーマを定義するための言語です。SDLは、APIのタイプとフィールド、およびそれらの関係を明確に記述するためのシンプルで直感的な構文を提供します。開発者はデータ構造を定義し、クライアントとサーバー間でデータのやり取りが容易になるようにすることができます。
以下のようにしてコメントを追加することが可能です
"""
Triple "double quotes" allow you
to add line breaks for clearer formatting of lengthier comments.
"""
type Author {
"single line comments"
id: ID!
name: String!
}
クエリ
GraphQLクエリは、クライアントがサーバーから取得したいデータを指定する手段です。クエリでは、データの型とフィールドを指定することができます。例えば、以下のクエリはユーザーの名前とメールアドレスを取得します。
query {
article(id: "1") {
title
author {
name
}
}
}
ミューテーション
ミューテーションは、データを作成・更新・削除する操作を表します。ミューテーションはクエリと同様に、必要なデータを指定できます。例えば、以下のミューテーションは新しいユーザーを作成し、その名前とメールアドレスを返します。
mutation {
createArticle(input: { title: "New Article", authorId: "1" }) {
id
title
author {
name
}
}
}
ミューテーション (Mutation) という用語は、英語で「変化」や「変異」という意味があります。GraphQLのミューテーションは、データを作成、更新、削除する操作を表します。つまり、データの状態を変更する役割を果たしています。
ミューテーションは、データの変更を行うための手段であり、その変更によってデータの状態が変わることから、この用語が選ばれました。
サブスクリプション
サブスクリプションは、リアルタイムデータの取得をサポートする仕組みです。サーバー側でデータが変更された際に、クライアントにリアルタイムで通知されます。WebSocketプロトコルを用いて、サーバーとクライアント間で双方向通信が可能になります。例えば、以下のサブスクリプションは、新しいユーザーが追加されたときに名前とメールアドレスを取得します。
subscription {
articleCreated {
id
title
author {
name
}
}
}
クライアントはサーバーからのデータ更新を購読(加入)し、データが変更された際にリアルタイムで通知を受け取ります。この購読の概念が、リアルタイムデータの取得を表す用語として「サブスクリプション」となっています。
フラグメント
フラグメントは、クエリやミューテーションで再利用可能な部分的なデータ構造を定義する機能です。これにより、コードの重複を避けて、コードの可読性を向上させることができます。例えば、以下のフラグメントは、ユーザーの名前とメールアドレスのデータ構造を定義しています。
fragment ArticleFields on Article {
id
title
author {
name
}
}
query {
article(id: "1") {
...ArticleFields
}
}
この機能の目的は、コードの重複を避けることで、コードの可読性や保守性を向上させることです。そのため、「一部分」という意味がある「フラグメント」という言葉が、再利用可能な部分的なデータ構造を指す用語になります。
GraphQLのクエリ処理ステップを理解する
GraphQLのクエリ処理のステップについて詳しく説明します。
この記事ではGraphQLの概要のみに絞り、実際のアプリでの運用の仕方には言及しませんが、クエリ処理のステップを理解することは必須だと考えています。
【クエリ処理のステップ】
- クエリのパース:クエリをトークンに分割し、構造化された形式に変換する。
- 抽象構文木(AST)の構築:パースした結果をツリー構造に変換する。これにより、バリデーションと実行のプロセスが効率的に行われる。
- クエリのバリデーション:クエリが正しい形式であることを確認し、APIが提供できるデータに対するリクエストが正しいことを確認する。
- データの解決:リゾルバ関数を使用してデータソースからデータを取得し、クライアントが必要とする形式に変換する。
以下で詳細に説明していきます。
1. クエリのパース(Parse)
クエリがサーバーに到着すると、最初にパースされます。クエリをトークンに分割し、それらを構造化された形式に変換します。
入力クエリ:
query {
user(id: 1) {
name
age
}
}
2. 抽象構文木(AST)の構築
抽象構文木(AST)は、パースされたクエリを表現するための階層的なデータ構造です。ASTは、クエリを効率的に操作し、バリデーションおよび実行フェーズでのクエリの解析を容易にすることができます。
例えば、次のようなGraphQLクエリがあるとします:
{
user(id: 1) {
name
age
}
}
このクエリのASTは、次のような階層構造で表現できます(わかりやすいようにインデントで表現しています)
- Query
- Field: user
- Argument: id = 1
- Name: id
- Value: 1
- Selection Set
- Field: name
- Field: age
このツリー構造では、各ノードがクエリの各要素(フィールド、引数、値など)を表しています。この構造を使用することで、バリデーションおよび実行フェーズでクエリの解析が効率的に行われます。具体的には、リゾルバ関数の呼び出しやデータの取得に関連する情報を簡単に抽出することができます。
3. クエリのバリデーション
次に、GraphQLサーバーはクエリをバリデーションします。バリデーションは、クエリが正しい形式であり、APIが提供できるデータに対して適切なリクエストを行っていることを確認するプロセスです。これにより、クライアントが不正確なクエリを送信した場合に、エラーが速やかに返され、データの取得が正確に行われることが保証されます。
4. データの解決(Resolve)
最後のステップは、実際にデータを取得し、クライアントに返すプロセスです。リゾルバ関数は、データソースからデータを取得し、クライアントが必要とする形式に変換します。これにより、クライアントは、必要なデータだけを効率的に取得できます。
リゾルバ実装例:
const resolvers = {
Query: {
user: (_, { id }) => {
// データソースからユーザーを取得
const user = getUserById(id);
// 必要なデータのみ返す
return {
name: user.name,
age: user.age
};
}
}
};
クエリ結果:
{
"data": {
"user": {
"name": "Alice",
"age": 30
}
}
}
このように、GraphQLのクエリ処理ステップは連携して動作し、クライアントとサーバー間のデータのやり取りを効率的で柔軟なものにします。これにより、クライアントは必要なデータだけをリクエストし、サーバーはそれに応じたデータを効率的に取得・返すことができます。
GraphQLのライブラリ
GraphQLライブラリは、開発者がGraphQLを効果的に使用するためのツールとして機能します。これらのライブラリは、クライアントとサーバー間のデータのやりとりを簡単かつ効率的に行うために設計されており、フロントエンドやバックエンドの開発を容易にします。
主要なGraphQLライブラリには、以下のようなものがあります。
- Apollo
- Relay
- URQL
- graphql-ruby
それぞれのライブラリは、異なる要件や状況に応じて選択できます。また、それぞれのライブラリは、特定のフレームワークや言語に対応したクライアントを提供しています。
対応しているフレームワークの多いApolloを選択するケースが多いように思います。
そのため以降ではApolloについて解説します
Apolloの主要な機能
Apollo Client
Apollo Clientは、フロントエンドアプリケーションからGraphQLサーバーへのデータの取得・変更を簡単に行うためのライブラリです。主要な機能は以下の通りです。
- キャッシュ機能:効率的なデータ取得を可能にするために、結果を自動的にキャッシュします。
- データの正規化:データを正規化して、冗長なデータ取得を避けることができます。
- リアクティブなデータフェッチ:データの変更をリアルタイムで反映させることができます。
ReactアプリケーションでApollo Clientを使用する場合、以下のようにしてデータを取得できます。
import React from 'react';
import { useQuery, gql } from '@apollo/client';
const GET_BOOKS = gql`
query GetBooks {
books {
title
author
}
}
`;
const Books = () => {
const { loading, error, data } = useQuery(GET_BOOKS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.books.map(({ title, author }) => (
<div key={title}>
<p>{`${title} by ${author}`}</p>
</div>
));
};
-
gql
を使って、GraphQLクエリを定義します。この例では、GetBooks
クエリを定義して、books
のデータを取得しています。 -
Books
コンポーネントを定義し、useQuery
フックを使ってクエリを実行します。useQuery
は、loading
,error
,data
の3つの値を返します。これらの値を使って、データの取得状況に応じた表示を行います。 - データが正常に取得された場合、
data.books
を使って、各本の情報を表示します。map
メソッドを使用して、title
とauthor
を表示するHTML要素を生成しています。
Apollo Server
Apollo Serverは、GraphQL APIを構築するためのライブラリです。以下の機能が提供されています。
- 柔軟なスキーマ定義:スキーマ定義を簡単に行うことができます。
- リゾルバの設定:データソースからのデータ取得や変更を行うためのリゾルバを簡単に設定できます。
- 認証・認可:ユーザーの認証や認可を簡単に設定できます。
以下の例では、Apollo Serverをセットアップし、GraphQL APIを実行しています。
const { ApolloServer, gql } = require('apollo-server');
// スキーマ定義
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;
// リゾルバ定義
const resolvers = {
Query: {
books: () => [
{
title: 'The Awakening',
author: 'Kate Chopin',
},
{
title: 'City of Glass',
author: 'Paul Auster',
},
],
},
};
// サーバーの初期化
const server = new ApolloServer({ typeDefs, resolvers });
// サーバーの起動
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});
このコードは、簡単なGraphQL APIを定義し、起動しています。typeDefs
でスキーマを定義し、resolvers
でリゾルバを設定しています。その後、ApolloServer
インスタンスを初期化し、listen
メソッドでサーバーを起動しています。
-
gql
を使って、GraphQLスキーマを定義します。この例では、Book
型とQuery
型を定義しています。Book
型は、title
とauthor
のフィールドを持っており、Query
型は、books
フィールドを持っています。books
フィールドは、Book
型の配列を返すことが定義されています - リゾルバオブジェクトを定義します。この例では、
Query
タイプのbooks
フィールドのリゾルバを定義しています。books
リゾルバは、静的な書籍データの配列を返しています。実際のアプリケーションでは、ここでデータベースや外部APIからデータを取得する処理を実装します。 -
ApolloServer
インスタンスを初期化します。typeDefs
とresolvers
を引数として渡して、インスタンスを作成します。 -
listen
メソッドを使って、サーバーを起動します。
学習方法について
まずはじめに、公式ドキュメントに目を通すのがおすすめです
。以下のリンクから、それぞれの公式ドキュメントにアクセスできます。
公式ドキュメントでは、基本的な概念や用語の説明から、具体的な実装方法まで、幅広い情報が提供されています。
ちなみに日本語での良いチュートリアルを探しましたが、いまいち見つかりませんでした。私はなくなく英語のドキュメントやチュートリアルを読むことにしました。。
次にチュートリアルを実践することで、具体的な実装方法やベストプラクティスを学ぶことができるためおすすめです。以下のリンクから、公式チュートリアルにアクセスできます。
また、以下のリンクは、他の開発者が作成したチュートリアルやブログ記事です。これらも役立つ情報が詰まっています。
私はApolloのチュートリアルを進めましたが、図などが豊富でかつ理解度をチェックするテストもあるため、大変わかりやすかったです。
Discussion