【第3回】GraphQLのスキーマを徹底解説!型、リゾルバ、モックサーバー
前回はNode.jsとApollo Serverで簡単なGraphQLサーバーを構築しました。その中で登場した「スキーマ」と「リゾルバ」は、GraphQLサーバー開発の核となる要素です。今回は、この2つをさらに深く掘り下げ、より実践的なAPIを設計するための知識を学びます。
スキーマ駆動開発という考え方
GraphQL開発は「スキーマ駆動開発 (Schema-Driven Development)」というアプローチを取ります。これは、まずAPIの仕様であるスキーマを定義し、そのスキーマを「唯一の信頼できる情報源 (Single Source of Truth)」として、フロントエンドとバックエンドの開発を並行して進める手法です。
スキーマさえ決まっていれば、バックエンドはリゾルバの実装に集中でき、フロントエンドは実際のAPIが完成する前から、モックデータを使ってUI開発を進めることができます。
スキーマ定義言語 (SDL) をマスターする
スキーマはSDL (Schema Definition Language) を使って記述します。主要な要素を見ていきましょう。
1. 型 (Type)
APIで扱えるデータの単位を定義します。最も基本的なのは type キーワードで定義するオブジェクト型です。
type Post {
id: ID!
title: String!
content: String
isPublished: Boolean!
}2. スカラー型 (Scalar Types)
これ以上分解できない最小単位のデータ型です。GraphQLにはデフォルトで5つのスカラー型が組み込まれています。
Int: 符号付き32ビット整数Float: 符号付き浮動小数点数String: UTF-8文字列Boolean:trueまたはfalseID: 一意な識別子を表す文字列。Stringと似ていますが、キャッシュ機構などで特別扱いされることがあります。
3. 修飾子 (Modifiers)
型の末尾につけることで、挙動を修飾します。
!(Non-Null): フィールドが必ず値を返さなければならないことを示します。nullを許容しません。[](List): フィールドがその型の配列(リスト)を返すことを示します。
# titleは必須(null不可)
# commentsはPostの配列を返す。配列自体はnullでも良い
type Post {
title: String!
comments: [Comment]
}
# commentsはPostの配列を返し、配列もその要素もnull不可
type Post {
comments: [Comment!]!
}4. クエリとミューテーション (Query & Mutation)
QueryとMutationは特別なオブジェクト型で、それぞれAPIの読み取りと書き込みの入り口(エントリーポイント)を定義します。
type Query {
# 投稿をIDで取得
post(id: ID!): Post
# 公開済みの投稿を全て取得
publishedPosts: [Post!]!
}
type Mutation {
# 新しい投稿を作成
createPost(title: String!, content: String): Post!
}リゾルバの役割と親子関係
リゾルバは、スキーマで定義されたフィールドの値を返す関数です。前回はQuery直下のフィールドに対するリゾルバのみを定義しましたが、スキーマの全てのフィールドは対応するリゾルバを持つことができます。
型のリレーションを解決する
前回作成したスキーマとリゾルバを拡張して、投稿(Post)と著者(User)の関連付けを実装してみましょう。
1. スキーマの更新 まず、User型を追加し、Post型にauthorフィールドを追加します。
# index.js (typeDefs)
const typeDefs = `#graphql
type Post {
id: ID!
title: String
content: String
author: User # Postに著者を追加
}
type User {
id: ID!
name: String
}
type Query {
allPosts: [Post]
}
`;2. ダミーデータの更新Userのダミーデータを追加します。
// index.js (ダミーデータ)
const users = [
{ id: '101', name: 'Alice' },
{ id: '102', name: 'Bob' },
];
// postsデータに `authorId` を追加しておく
const posts = [
{ id: '1', title: 'GraphQL入門', authorId: '101' },
{ id: '2', title: 'Apollo Server', authorId: '102' },
];3. リゾルバの更新Query.allPostsが呼ばれると、posts配列が返されます。しかし、posts配列にはauthorオブジェクトそのものは含まれていません。クライアントがauthorフィールドを要求したときに、どうやってUserオブジェクトを解決すればよいでしょうか?
ここで、Post型のためのリゾルバを定義します。
// index.js (resolvers)
const resolvers = {
Query: {
allPosts: () => posts,
},
// Post型のリゾルバを追加
Post: {
// Post.authorフィールドが要求されたときの処理
author: (parent) => {
// 親オブジェクト(この場合はPost)のauthorIdを使って、
// users配列から該当するユーザーを探して返す
return users.find(user => user.id === parent.authorId);
},
},
};リゾルバ関数は第一引数に parent を受け取ります。これは、親フィールドのリゾルバが返したオブジェクトです。Post.authorリゾルバの場合、parentはQuery.allPostsリゾルバが返したposts配列の各要素、つまり個々のPostオブジェクトになります。
このように、リゾルバをネストさせることで、GraphQLはグラフ構造のデータを効率的に解決していきます。
モックサーバーで開発を加速する
スキーマさえあれば、Apollo Serverは自動でモックデータ(ダミーデータ)を生成して返す機能を持っています。これは、バックエンドの実装が完了する前に、フロントエンドチームが開発を始めるのに非常に便利です。
Apollo Serverにモック機能を追加するには、addMocksToSchemaを使います。
// mock-server.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';
// スキーマ定義 (前回と同じ)
const typeDefs = `#graphql
type Post { ... }
type User { ... }
type Query { ... }
`;
// 1. 実行可能なスキーマを作成
const schema = makeExecutableSchema({ typeDefs });
// 2. スキーマにモックを追加
const schemaWithMocks = addMocksToSchema({ schema });
// 3. モック付きスキーマでサーバーを起動
const server = new ApolloServer({
schema: schemaWithMocks,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Mock Server ready at ${url}`);このサーバーを起動してクエリを投げると、Apollo Serverがスキーマの型情報(String, Intなど)に基づいて、もっともらしいダミーデータを自動で生成して返してくれます。
まとめ
今回は、GraphQL開発の中心であるスキーマとリゾルバについて、その詳細な機能と関連性を解説しました。
- スキーマはAPIの厳格な「契約」であること。
- リゾルバはスキーマのフィールドとデータソースを結びつける関数であること。
- 親子関係を持つリゾルバによって、グラフ構造のデータが解決されること。
- モックサーバーを使えば、スキーマ定義後すぐにフロントエンド開発を開始できること。
これらの概念を理解することで、より複雑で実用的なGraphQL APIを設計・実装する準備が整いました。次回は、フロントエンドに焦点を当て、ReactとApollo Clientを使ってGraphQL APIを利用する方法を学びます。