GraphQL入門:TypeScriptでスキーマ・Query・Mutationを実装する

スポンサーリンク

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を実装する」を参照してください。