

プロ太先生、前回GraphQLがすごく便利そうだって分かりました!
実際に作ってみたいです!

よし!今日は実際にGraphQLサーバーを作ってみよう。Node.jsとApollo Serverを使うから、まずは環境を準備しよう。
事前準備

まず、Node.jsがインストールされているか確認しよう。ターミナルで以下のコマンドを実行してみて。
node --version
npm --version

Node.jsは入ってます!バージョンも新しいものでした。

それじゃあ新しいプロジェクトを作成しよう。
# 新しいディレクトリを作成
mkdir my-first-graphql
cd my-first-graphql
# package.jsonを作成
npm init -y
# 必要なパッケージをインストール
npm install apollo-server-express express graphql
npm install --save-dev nodemon
最初のGraphQLサーバーを作成

それでは、まず簡単なGraphQLサーバーを作ってみよう。server.js
というファイルを作成して、以下のコードを書いてみて。
// server.js
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');
// GraphQLのスキーマを定義(どんなデータがあるかを決める設計図)
const typeDefs = gql`
# ユーザーという型を定義
type User {
id: ID! # IDは必須項目(!マークで必須を表す)
name: String! # 名前は文字列で必須
email: String! # メールアドレスも文字列で必須
age: Int # 年齢は整数(必須ではない)
}
# クエリ(データを取得する処理)を定義
type Query {
# すべてのユーザーを取得する
users: [User!]! # User型の配列を返す、必須
# 特定のIDのユーザーを取得する
user(id: ID!): User # IDを受け取って、Userを返す(見つからない場合はnull)
}
`;
// サンプルデータ(本来はデータベースから取得)
const sampleUsers = [
{ id: '1', name: '山田太郎', email: 'yamada@example.com', age: 25 },
{ id: '2', name: '田中花子', email: 'tanaka@example.com', age: 30 },
{ id: '3', name: '佐藤次郎', email: 'sato@example.com', age: 22 }
];
// リゾルバー(実際にデータを取得する処理)
const resolvers = {
Query: {
// すべてのユーザーを返す関数
users: () => {
console.log('全ユーザーが要求されました');
return sampleUsers;
},
// 特定のユーザーを返す関数
user: (parent, args) => {
console.log(`ID: ${args.id} のユーザーが要求されました`);
// 配列から指定されたIDのユーザーを探す
return sampleUsers.find(user => user.id === args.id);
}
}
};
// サーバーを起動する関数
async function startServer() {
// Apollo Serverを作成
const server = new ApolloServer({
typeDefs, // スキーマ定義
resolvers // データ取得処理
});
// Expressアプリを作成
const app = express();
// Apollo Serverを開始
await server.start();
// ExpressアプリにApollo Serverを統合
server.applyMiddleware({ app });
const PORT = 4000;
// サーバーを起動
app.listen(PORT, () => {
console.log(`🚀 GraphQLサーバーが起動しました!`);
console.log(`📍 GraphQL Playground: http://localhost:${PORT}${server.graphqlPath}`);
});
}
// サーバーを起動
startServer().catch(error => {
console.error('サーバー起動エラー:', error);
});

うわー、結構長いコードですね!

そうだね!実は構造はシンプルなんだ。大きく分けて3つの部分があるよ。
- typeDefs:「こんなデータがありますよ」という設計図
- resolvers:「実際にデータを取得する処理」
- サーバー起動処理:「サーバーを立ち上げる」
1.必要なライブラリの読み込み
// server.js
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');

この最初の2行は何をしているんですか?

これは「分割代入」という書き方だね。
// これは以下と同じ意味
// const apolloServerExpress = require('apollo-server-express');
// const ApolloServer = apolloServerExpress.ApolloServer;
// const gql = apolloServerExpress.gql;
const { ApolloServer, gql } = require('apollo-server-express');
●ApolloServer:GraphQLサーバーを作るためのクラス
●gql:GraphQLスキーマを書くためのテンプレートリテラル関数
●express:WebサーバーのためのNode.jsフレームワーク
2.GraphQLスキーマの定義
// GraphQLのスキーマを定義(どんなデータがあるかを決める設計図)
const typeDefs = gql`
# ユーザーという型を定義
type User {
id: ID! # IDは必須項目(!マークで必須を表す)
name: String! # 名前は文字列で必須
email: String! # メールアドレスも文字列で必須
age: Int # 年齢は整数(必須ではない)
}
# クエリ(データを取得する処理)を定義
type Query {
# すべてのユーザーを取得する
users: [User!]! # User型の配列を返す、必須
# 特定のIDのユーザーを取得する
user(id: ID!): User # IDを受け取って、Userを返す(見つからない場合はnull)
}
`;

このgql
の部分、変わった書き方ですね!

これは「テンプレートリテラル」という書き方だよ。
バッククォート(`)で囲むことで、複数行の文字列を書けるんだ。
// 普通の文字列だとこうなる(読みにくい!)
const typeDefs = "type User { id: ID! name: String! }";
// テンプレートリテラルなら読みやすい
const typeDefs = gql`
type User {
id: ID!
name: String!
}
`;

[User!]!
の部分が分からないです…

GraphQLの型表記だね!詳しく見てみよう
// 型の表記方法
String // 文字列(nullの可能性あり)
String! // 文字列(必須、nullは不可)
[String] // 文字列の配列(配列自体がnullの可能性あり)
[String!] // 文字列の配列(配列内の要素はnull不可、配列自体はnullの可能性あり)
[String!]! // 文字列の配列(配列内の要素もnull不可、配列自体もnull不可)
// 具体例
users: [User!]!
// ↑ これは「User型の配列で、配列内にnullは入らず、配列自体もnullではない」という意味

えぇ!?待ってください!
User型の中でage: Int
って必須じゃないのに、[User!]!
って書いて大丈夫なんですか?

これは多くの人が混乱するポイントなんだ。
実は全然問題ないよ。理由を説明するね。
// User型の定義
type User {
id: ID! // 必須
name: String! // 必須
email: String! // 必須
age: Int // オプショナル(nullの可能性あり)
}
// この場合、以下のデータは全て有効なUser型
const validUsers = [
{ id: "1", name: "山田", email: "yamada@example.com", age: 25 }, // ageあり
{ id: "2", name: "田中", email: "tanaka@example.com", age: null }, // ageがnull
{ id: "3", name: "佐藤", email: "sato@example.com" } // ageがundefined
];
// でも、以下は無効(User型ではない)
const invalidData = [
null, // これはUser型ではない
undefined, // これもUser型ではない
"文字列" // これもUser型ではない
];

あー!つまりUser!
は「User型のオブジェクトであることは保証する」けど、「User型の中身のフィールドがnullかどうかは別問題」ってことですね!

完璧な理解だ!もう少し具体的に見てみよう!
// [User!]! の意味を分解すると...
// 1. User! → 「User型のオブジェクトで、nullではない」
// ✅ { id: "1", name: "山田", email: "yamada@example.com", age: 25 }
// ✅ { id: "2", name: "田中", email: "tanaka@example.com", age: null }
// ❌ null
// ❌ undefined
// 2. [User!] → 「User型のオブジェクトの配列で、配列内にnullは含まない」
// ✅ [user1, user2, user3]
// ❌ [user1, null, user3] // 配列内にnullがある
// ✅ [] // 空配列はOK
// 3. [User!]! → 「上記の配列で、配列自体もnullではない」
// ✅ [user1, user2, user3]
// ✅ [] // 空配列
// ❌ null // 配列自体がnull

なるほど!「型の保証」と「フィールドの値の保証」は別次元の話なんですね!

そういうこと!GraphQLでは、こんな風に段階的に型安全性を設計できるんだ!
// 様々なパターンの例
type Query {
// パターン1: 配列もUser型も必須
allUsers: [User!]! // 必ず配列が返る、中身は必ずUser型
// パターン2: 配列は必須だが、User型はnullの可能性あり
someUsers: [User]! // 必ず配列が返る、中身はUser型かnull
// パターン3: 配列自体がnullの可能性あり
maybeUsers: [User!] // 配列かnull、配列の場合は中身は必ずUser型
// パターン4: 全てがnullの可能性あり
optionalUsers: [User] // 配列かnull、配列の場合は中身もnullの可能性あり
}
// 実際のレスポンス例
{
"allUsers": [
{ "id": "1", "name": "山田", "email": "yamada@example.com", "age": 25 },
{ "id": "2", "name": "田中", "email": "tanaka@example.com", "age": null }
],
"someUsers": [
{ "id": "1", "name": "山田", "email": "yamada@example.com", "age": 25 },
null, // これはOK
{ "id": "3", "name": "佐藤", "email": "sato@example.com", "age": null }
],
"maybeUsers": null, // 配列自体がnullでもOK
"optionalUsers": [
{ "id": "1", "name": "山田", "email": "yamada@example.com", "age": 25 },
null // これもOK
]
}
3.サンプルデータの準備
// サンプルデータ(本来はデータベースから取得)
const sampleUsers = [
{ id: '1', name: '山田太郎', email: 'yamada@example.com', age: 25 },
{ id: '2', name: '田中花子', email: 'tanaka@example.com', age: 30 },
{ id: '3', name: '佐藤次郎', email: 'sato@example.com', age: 22 }
];

今回は簡単にするために、メモリ上に配列でデータを用意したよ。本格的なアプリではデータベースを使うけどね。
4.リゾルバーの実装
// リゾルバー(実際にデータを取得する処理)
const resolvers = {
Query: {
// すべてのユーザーを返す関数
users: () => {
console.log('全ユーザーが要求されました');
return sampleUsers;
},
// 特定のユーザーを返す関数
user: (parent, args) => {
console.log(`ID: ${args.id} のユーザーが要求されました`);
// 配列から指定されたIDのユーザーを探す
return sampleUsers.find(user => user.id === args.id);
}
}
};

(parent, args)
って何ですか?

GraphQLのリゾルバー関数は、4つの引数を受け取るんだ。
// リゾルバー関数の引数
function myResolver(parent, args, context, info) {
// parent: 親のリゾルバーからの結果(今回は使わない)
// args: クエリで渡された引数(user(id: "1") の "1" など)
// context: 全リゾルバーで共有するデータ(認証情報など)
// info: クエリの詳細情報(高度な使い方で使用)
}
// 実際の使用例
user: (parent, args) => {
console.log('受け取った引数:', args); // { id: "1" }
return sampleUsers.find(user => user.id === args.id);
}

でも待ってください!スキーマではuser(id: ID!): User
って書いてるけど、なんでリゾルバーでは(parent, args)
って書くんですか?user(id)
じゃダメなんですか?

これはGraphQLの仕組みを理解する上で重要なポイントなんだ。詳しく説明するね。
スキーマ vs リゾルバーの関係
// 【スキーマ】= 「外部への約束・インターフェース」
type Query {
user(id: ID!): User // 「IDを受け取ってUserを返しますよ」という約束
}
// 【リゾルバー】= 「約束を実現する実際の処理」
const resolvers = {
Query: {
user: (parent, args) => {
// args.id でスキーマで定義した引数にアクセス
return sampleUsers.find(user => user.id === args.id);
}
}
};

あー!スキーマは「約束」で、リゾルバーは「実装」って感じのイメージですね!

その通り!もう少し具体的に見てみよう!
// スキーマで色々な引数パターンを定義できる
type Query {
# 引数なし
users: [User!]!
# 引数1つ
user(id: ID!): User
# 引数2つ
userByEmail(email: String!, domain: String!): User
# オプショナル引数
searchUsers(name: String, minAge: Int): [User!]!
}
// リゾルバーでは全部同じパターン
const resolvers = {
Query: {
// 引数なし → args は空オブジェクト {}
users: (parent, args) => {
console.log(args); // {}
return sampleUsers;
},
// 引数1つ → args は { id: "値" }
user: (parent, args) => {
console.log(args); // { id: "1" }
return sampleUsers.find(user => user.id === args.id);
},
// 引数2つ → args は { email: "値", domain: "値" }
userByEmail: (parent, args) => {
console.log(args); // { email: "test@example.com", domain: "example.com" }
return sampleUsers.find(user =>
user.email === args.email && user.email.endsWith(args.domain)
);
},
// オプショナル引数 → 指定されなかった引数はundefined
searchUsers: (parent, args) => {
console.log(args); // { name: "山田", minAge: undefined } など
let result = sampleUsers;
if (args.name) {
result = result.filter(user => user.name.includes(args.name));
}
if (args.minAge !== undefined) {
result = result.filter(user => user.age >= args.minAge);
}
return result;
}
}
};

なるほど!引数の数に関係なく、リゾルバーは常に(parent, args)
で受け取るんですね!
どうしてこういった仕組みなの?

GraphQLがこの仕組みを採用している理由は、統一性と柔軟性だよ。
// もしJavaScriptの関数のように直接引数を受け取る場合...
const badExample = {
Query: {
user: (id) => { /* ... */ }, // 引数1つ
userByEmail: (email, domain) => { /* ... */ }, // 引数2つ
searchUsers: (name, minAge) => { /* ... */ } // オプショナル引数は?
}
};
// 問題点:
// 1. 引数の数がバラバラで統一性がない
// 2. オプショナル引数の扱いが難しい
// 3. 将来的に引数を追加する時に既存コードが壊れる
// GraphQLの方式なら...
const goodExample = {
Query: {
// 全て同じパターン!統一性がある
user: (parent, args) => { /* args.id */ },
userByEmail: (parent, args) => { /* args.email, args.domain */ },
searchUsers: (parent, args) => { /* args.name, args.minAge */ }
}
};
// メリット:
// 1. 全てのリゾルバーが同じ形
// 2. 引数の追加/削除が簡単
// 3. ツールでの自動生成がしやすい

統一性があることで、コードが書きやすくなるんですね!
でもparent
って何に使うんですか?

parent
は、ネストしたクエリで威力を発揮するんだ。例えば、、
// こんなクエリが来た場合
query {
user(id: "1") {
name
posts { # ←ここでparentが活躍!
title
}
}
}
// スキーマ定義
type User {
id: ID!
name: String!
posts: [Post!]! # ←このpostsリゾルバーでparentを使う
}
// リゾルバー
const resolvers = {
Query: {
user: (parent, args) => {
return sampleUsers.find(user => user.id === args.id);
}
},
User: {
// User型のpostsフィールドのリゾルバー
posts: (parent, args) => {
// parent は上のuserリゾルバーが返したUserオブジェクト
console.log(parent); // { id: "1", name: "山田太郎", email: "..." }
// そのユーザーの投稿を取得
return samplePosts.filter(post => post.authorId === parent.id);
}
}
};

あー!parent
があることで、関連するデータを芋づる式に取得できるんですね!
5.サーバー起動処理
// サーバーを起動する関数
async function startServer() {
// Apollo Serverを作成
const server = new ApolloServer({
typeDefs, // スキーマ定義
resolvers // データ取得処理
});
// Expressアプリを作成
const app = express();
// Apollo Serverを開始
await server.start();
// ExpressアプリにApollo Serverを統合
server.applyMiddleware({ app });
const PORT = 4000;
// サーバーを起動
app.listen(PORT, () => {
console.log(`🚀 GraphQLサーバーが起動しました!`);
console.log(`📍 GraphQL Playground: http://localhost:${PORT}${server.graphqlPath}`);
});
}

server.applyMiddleware({ app })
は何をしているんですか?

Apollo ServerをExpressアプリに組み込む処理だよ。
// applyMiddleware を実行すると...
server.applyMiddleware({ app });
// 以下のようなルートが自動的に作られる
// GET /graphql → GraphQL Playground画面
// POST /graphql → GraphQLクエリの実行
6.エラーハンドリング
// サーバーを起動
startServer().catch(error => {
console.error('サーバー起動エラー:', error);
});

async
関数はPromiseを返すから、エラーが起きた時のために.catch()
でキャッチしてるんだ。

うわー、一つ一つ説明してもらうと、すごく分かりやすいです!全体的にはシンプルな構造なんですね。

そうだね!実は構造はシンプルなんだ。もう一度言うけど、大きく分けて3つの部分があるのでしっかりと頭に入れておこう!!
- typeDefs:「こんなデータがありますよ」という設計図
- resolvers:「実際にデータを取得する処理」
- サーバー起動処理:「サーバーを立ち上げる」
package.jsonを更新

開発を楽にするために、package.json
にスクリプトを追加しよう。
{
"name": "my-first-graphql",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"apollo-server-express": "^latest",
"express": "^latest",
"graphql": "^latest"
},
"devDependencies": {
"nodemon": "^latest"
}
}
サーバーを起動してみよう!

それでは、サーバーを起動してみよう!
npm run dev

おお!ターミナルに「GraphQLサーバーが起動しました!」って表示されました!

OK!じゃあ、ブラウザで http://localhost:4000/graphql
を開いてみて。
GraphQL Playgroundで実際にクエリを実行

すごい!なんか格好いい画面が開きました!

これが「GraphQL Playground」だよ。
ここで実際にクエリを書いて、データを取得してみよう。
クエリその1:全ユーザーを取得
# 左側のエディタに以下を入力
query {
users {
id
name
email
}
}

このクエリを入力して、真ん中の再生ボタン(▶)をクリックしてみて。

うわー!右側にデータが表示されました!
{
"data": {
"users": [
{
"id": "1",
"name": "山田太郎",
"email": "yamada@example.com"
},
{
"id": "2",
"name": "田中花子",
"email": "tanaka@example.com"
},
{
"id": "3",
"name": "佐藤次郎",
"email": "sato@example.com"
}
]
}
}
クエリその2:特定のユーザーを取得

今度は特定のユーザーだけを取得してみよう。
query {
user(id: "1") {
name
email
age
}
}

今度は山田太郎さんの情報だけが返ってきました!
{
"data": {
"user": {
"name": "山田太郎",
"email": "yamada@example.com",
"age": 25
}
}
}
クエリその3:欲しいフィールドだけ取得

じゃあ、GraphQLの醍醐味を味わってみよう。名前だけが欲しい場合は?
query {
users {
name
# emailやageは取得しない
}
}

本当に名前だけが返ってきました!
これがオーバーフェッチングを防ぐってことですね!
{
"data": {
"users": [
{ "name": "山田太郎" },
{ "name": "田中花子" },
{ "name": "佐藤次郎" }
]
}
}
エラーハンドリングも確認

じゃあ、存在しないIDを指定するとどうなるか試してみよう。
query {
user(id: "999") {
name
email
}
}

結果がnullになりました!エラーにならずに、きちんと「見つからない」ことが分かりますね。
{
"data": {
"user": null
}
}
スキーマの自動補完機能

GraphQL Playgroundの右側にある「DOCS」タブをクリックしてみて。

おお!自動的にAPIの仕様書が作られてる!どんなクエリが使えるか、どんなデータが返ってくるかが全部書いてあります!

そうなんだ!これもGraphQLの大きな利点の一つ。APIの仕様が自動的に生成されるから、フロントエンドの開発者も迷わずに済むんだ。
今日のまとめ

今日学んだことをまとめてみよう!
作ったもの
- Node.js + Apollo Serverを使ったGraphQLサーバー
- User型のスキーマ定義
- 全ユーザー取得と特定ユーザー取得のクエリ
学んだ概念
- typeDefs:データの型を定義する設計図
- resolvers:実際にデータを取得する処理
- GraphQL Playground:クエリをテストできるツール
GraphQLの利点を実感
- 必要なフィールドだけ取得できる
- 自動的にAPIドキュメントが生成される
- 型安全でエラーが分かりやすい

実際に動くものが作れて、すごく楽しかったです!
でも、実際のアプリではデータベースを使いますよね?

その通り!
次回はデータベースと連携して、もっと実践的なGraphQLサーバーを作ってみよう。