GraphQLガイド - GraphQLのプロトコルと特徴

GraphQLは、Facebook(現Meta)が2012年に開発し、2015年に公開したクエリ言語およびAPI仕様です。REST APIの代替として設計され、クライアントがサーバーから必要なデータを正確に取得できるようにすることを目的としています。

この記事では、GraphQLのプロトコルと特徴について解説し、理解を深めていきます。

GraphQLとは

GraphQLは、クライアントがサーバーに対して必要なデータを正確に指定して取得できるクエリ言語です。従来のREST APIでは、サーバーが決めたエンドポイントから固定のデータ構造を取得する必要がありましたが、GraphQLではクライアントが自由にデータの形を指定できます。

主な特徴

1. 単一エンドポイント

GraphQLでは通常、/graphqlのような単一のエンドポイントを使用します。すべてのクエリ、ミューテーション、サブスクリプションがこのエンドポイントを通じて処理されます。

1
2
// すべての操作が同じエンドポイントを使用
POST /graphql

2. クエリ言語

GraphQLは独自のクエリ言語を提供し、クライアントが必要なデータを正確に指定できます。

1
2
3
4
5
6
7
8
9
10
query {
user(id: "123") {
name
email
posts {
title
content
}
}
}

3. 型システム

GraphQLは強力な型システムを持ち、APIの仕様が明確になります。また、イントロスペクション機能により、スキーマ情報を動的に取得できます。

4. リアルタイム通信

Subscriptions機能により、WebSocketを使用したリアルタイム通信をサポートしています。

プロトコルの詳細

HTTPプロトコルでの実装

GraphQLは主にHTTPプロトコル上で動作します。リクエストボディはJSONです。
以下が基本的なリクエスト形式です。

1
2
3
4
5
6
7
8
POST /graphql
Content-Type: application/json

{
"query": "query { user(id: \"123\") { name email } }",
"variables": {},
"operationName": "GetUser"
}

レスポンス形式

レスポンス形式はJSONです。

1
2
3
4
5
6
7
8
9
{
"data": {
"user": {
"name": "John Doe",
"email": "john@example.com"
}
},
"errors": null
}

WebSocketプロトコルでの実装

リアルタイム通信が必要な場合は、WebSocketプロトコルを使用します。

接続確立

1
2
3
4
5
GET /graphql HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: <key>
Sec-WebSocket-Protocol: graphql-ws

サブスクリプション例

1
2
3
4
5
6
7
subscription {
userUpdated(userId: "123") {
id
name
email
}
}

GraphQLプロトコルレイヤー

GraphQLは以下のプロトコルレイヤーで動作します。

  1. HTTP/HTTPS: 主なトランスポートプロトコル
  2. WebSocket: Subscriptions用のリアルタイム通信
  3. GraphQL: アプリケーションレベルのクエリ言語

プロトコルスタック

1
2
3
4
5
6
7
┌─────────────────┐
│ GraphQL │ ← クエリ言語
├─────────────────┤
│ HTTP/HTTPS │ ← トランスポート
├─────────────────┤
│ TCP/IP │ ← ネットワーク
└─────────────────┘

実装例

基本的なクエリ

データの取得にはクエリを利用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// リクエスト
{
"query": `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
}
}
}
`,
"variables": {
"id": "123"
}
}

ミューテーション

データの作成、更新にはミューテーションを利用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// リクエスト
{
"query": `
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`,
"variables": {
"input": {
"name": "Jane Doe",
"email": "jane@example.com"
}
}
}

サブスクリプション

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// WebSocket接続後のメッセージ
{
"type": "start",
"id": "1",
"payload": {
"query": `
subscription {
userUpdated {
id
name
email
}
}
`
}
}

エラーハンドリング

GraphQLでは、エラーが発生した場合でも部分的なデータを返すことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"data": {
"user": {
"name": "John Doe",
"email": null // エラーにより取得できなかった
}
},
"errors": [
{
"message": "Cannot return null for non-nullable field User.email",
"locations": [
{
"line": 3,
"column": 7
}
],
"path": ["user", "email"]
}
]
}

パフォーマンス最適化

1. クエリの最適化

必要なフィールドのみを指定することで、ネットワーク転送量を削減できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 良い例:必要なフィールドのみ
query {
user(id: "123") {
name
email
}
}

# 悪い例:不要なフィールドも含む
query {
user(id: "123") {
name
email
posts {
title
content
comments {
text
author {
name
}
}
}
}
}

2. バッチ処理

複数のクエリを一度に実行することで、ネットワークリクエスト数を削減できます。

1
2
3
4
5
6
7
8
9
{
"query": `
query {
user1: user(id: "1") { name email }
user2: user(id: "2") { name email }
user3: user(id: "3") { name email }
}
`
}

セキュリティ

1. 認証・認可

Authorizationヘッダーに認証・認可トークンを付与します。

1
2
3
4
5
6
7
8
// ヘッダーにトークンを含める
{
"query": "...",
"variables": {},
"headers": {
"Authorization": "Bearer <token>"
}
}

2. クエリの複雑度制限

悪意のあるクエリによる攻撃を防ぐため、クエリの複雑度を制限することもできます。

1
2
3
4
5
6
7
// サーバー側での実装例
const depthLimit = require('graphql-depth-limit');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(7)]
});

まとめ

GraphQLは、モダンなWebアプリケーション開発において、REST APIの代替として広く採用されており、REST APIと比較して通信量の削減が見込めます。
今後もGraphQLの普及は進むことが予想され、API設計の標準的なアプローチの一つとして確立されていくと思われます。