GraphQL入門:TypeScriptでスキーマ・Query・Mutationを実装する
GraphQLとは
GraphQLはFacebookが開発したAPI用クエリ言語です。RESTと異なり、クライアントが必要なフィールドだけを指定して取得できます。
# REST
GET /users/1 → { id, name, email, createdAt, ... } ← 不要なデータも返る
# GraphQL
query { → { id, name } ← 必要なフィールドだけ
user(id: "1") {
id
name
}
}
| REST | GraphQL | |
|---|---|---|
| エンドポイント | リソースごとに複数 | /graphql の1つ |
| データ取得 | サーバーが返す形式に依存 | クライアントが指定 |
| 操作の種類 | HTTPメソッド(GET/POST/PUT/DELETE) | Query / Mutation / Subscription |
| 型定義 | OpenAPI等で別途定義 | スキーマとして組み込み |
セットアップ
mkdir graphql-server && cd graphql-server npm init -y npm install @apollo/server graphql npm install -D typescript @types/node tsx npx tsc --init
tsconfig.json を以下に書き換えます。
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "moduleResolution": "node", "outDir": "dist", "strict": true, "esModuleInterop": true } }
スキーマ定義(SDL)
GraphQLはスキーマファースト。まずデータの型と操作を定義します。
// src/schema.ts export const typeDefs = `#graphql type User { id: ID! name: String! email: String! } type Query { users: [User!]! user(id: ID!): User } type Mutation { createUser(name: String!, email: String!): User! updateUser(id: ID!, name: String, email: String): User deleteUser(id: ID!): Boolean! } `
| SDL構文 | 意味 |
|---|---|
! |
null不可 |
[User!]! |
null不可なUserの配列(配列自体もnull不可) |
ID |
一意識別子(内部的にはString) |
String |
文字列(null許容) |
TypeScriptの型定義
スキーマと対応するTypeScriptの型を定義します。
// src/types.ts export type User = { id: string name: string email: string }
Resolver実装
Resolverはスキーマの各フィールドをどう解決するかを実装する関数です。今回はインメモリの配列をデータストアとして使います。
// src/resolvers.ts import type { User } from './types' const users: User[] = [ { id: '1', name: '田中太郎', email: 'tanaka@example.com' }, { id: '2', name: '鈴木花子', email: 'suzuki@example.com' }, ] export const resolvers = { Query: { users: () => users, user: (_: unknown, { id }: { id: string }) => users.find(u => u.id === id) ?? null, }, Mutation: { createUser: ( _: unknown, { name, email }: { name: string; email: string } ): User => { const user: User = { id: String(users.length + 1), name, email } users.push(user) return user }, updateUser: ( _: unknown, { id, name, email }: { id: string; name?: string; email?: string } ): User | null => { const user = users.find(u => u.id === id) if (!user) return null if (name) user.name = name if (email) user.email = email return user }, deleteUser: (_: unknown, { id }: { id: string }): boolean => { const index = users.findIndex(u => u.id === id) if (index === -1) return false users.splice(index, 1) return true }, }, }
Resolverの第1引数(_)はparent(親フィールドの値)です。ルートのQueryとMutationでは使わないため _ と書きます。
サーバー起動
// src/index.ts import { ApolloServer } from '@apollo/server' import { startStandaloneServer } from '@apollo/server/standalone' import { typeDefs } from './schema' import { resolvers } from './resolvers' const server = new ApolloServer({ typeDefs, resolvers }) const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }) console.log(`Server ready at ${url}`)
npx tsx src/index.ts # Server ready at http://localhost:4000/
動作確認(Apollo Sandbox)
http://localhost:4000 をブラウザで開くとApollo Sandboxが起動します。GUI上でQuery・Mutationをそのまま実行できます。
Query(取得)
# 全ユーザー取得 query { users { id name email } } # 特定ユーザー取得 query { user(id: "1") { name email } }
Mutation(作成・更新・削除)
# 作成 mutation { createUser(name: "佐藤次郎", email: "sato@example.com") { id name } } # 更新 mutation { updateUser(id: "1", name: "田中二郎") { id name } } # 削除 mutation { deleteUser(id: "2") }
まとめ
| 操作 | 種類 | 説明 |
|---|---|---|
| データ取得 | Query | RESTのGETに相当 |
| 作成 | Mutation | RESTのPOSTに相当 |
| 更新 | Mutation | RESTのPUT/PATCHに相当 |
| 削除 | Mutation | RESTのDELETEに相当 |
| ファイル | 役割 |
|---|---|
schema.ts |
スキーマ定義(SDL) |
types.ts |
TypeScriptの型定義 |
resolvers.ts |
データの取得・更新ロジック |
index.ts |
サーバー起動 |
- スキーマファーストで設計 → TypeScriptの型 → Resolverの順に実装する
!はnull不可。APIの契約として重要- Resolverの第1引数(parent)はルートでは使わないため
_と書く - Apollo Sandbox(
localhost:4000)でQuery・Mutationをすぐに試せる - Mutationでメール送信・画像変換などの重い処理を非同期で実行したい場合は「BullMQ入門:Node.js(TypeScript)でジョブキューを実装する」も参照
クライアント側(React + Apollo Client)からCRUDを実装する方法は「Apollo Client + React入門:TypeScriptでGraphQL CRUDを実装する」を参照してください。