NodeJS 速習チュートリアル

GraphQL

1. GraphQLとは?

GraphQLは、APIのためのクエリ言語であり、既存のデータに対してそれらのクエリを実行するためのランタイムです。2012年にFacebook(現Meta)によって開発され、2015年に公開されました。

1.1 主な特徴

  • クライアント指定のクエリ: 必要なデータだけを正確にリクエストでき、それ以上のデータは取得しません。
  • シングルエンドポイント: すべてのリソースに単一のエンドポイントからアクセス可能です。
  • 強力な型付け: 明確なスキーマによって、利用可能なデータと操作が定義されます。
  • 階層構造: クエリはデータの形状と一致します。
  • 自己文書化: スキーマ自体がドキュメントとして機能します。

注意: RESTとは異なり、GraphQLはクライアントが必要なデータを正確に指定できるため、データの「オーバーフェッチ(取得しすぎ)」や「アンダーフェッチ(不足)」を削減できます。

2. Node.jsでGraphQLを始める

2.1 前提条件

  • Node.jsがインストールされていること(v14以降を推奨)
  • JavaScriptおよびNode.jsに関する基本的な知識
  • npmまたはyarnパッケージマネージャー

2.2 ステップ 1:新しいプロジェクトのセットアップ

新しいディレクトリを作成し、Node.jsプロジェクトを初期化します:

mkdir graphql-server
cd graphql-server
npm init -y

2.3 ステップ 2:必要なパッケージのインストール

依存関係をインストールします:

npm install express express-graphql graphql

各パッケージの役割:

  • express: Node.js用のWebフレームワーク
  • express-graphql: GraphQL HTTPサーバーを作成するためのミドルウェア
  • graphql: GraphQLのJavaScript参照実装

3. 基本的なGraphQLサーバーの作成

3.1 データモデルの定義

server.js ファイルを作成し、GraphQLのスキーマ定義言語(SDL)を使用してデータモデルを定義することから始めます。

const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

// サンプルデータ
const books = [
  {
    id: '1',
    title: 'The Great Gatsby',
    author: 'F. Scott Fitzgerald',
    year: 1925,
    genre: 'Novel'
  },
  {
    id: '2',
    title: 'To Kill a Mockingbird',
    author: 'Harper Lee',
    year: 1960,
    genre: 'Southern Gothic'
  }
];

3.2 GraphQLスキーマの定義

server.js にスキーマ定義を追加します:

// GraphQLスキーマ言語を使用してスキーマを定義
const schema = buildSchema(`
  # 本(Book)はタイトル、著者、出版年を持ちます
  type Book {
    id: ID!
    title: String!
    author: String!
    year: Int
    genre: String
  }

  # "Query" 型はすべてのGraphQLクエリのルートです
  type Query {
    # すべての本を取得
    books: [Book!]!
    # IDで特定の1冊を取得
    book(id: ID!): Book
    # タイトルまたは著者で本を検索
    searchBooks(query: String!): [Book!]!
  }
`);

3.3 リゾルバー(Resolvers)の実装

実際のデータを取得するためのリゾルバー関数を追加します:

// スキーマのフィールドに対応するリゾルバーを定義
const root = {
  // すべての本を取得するリゾルバー
  books: () => books,
  
  // IDで1冊の本を取得するリゾルバー
  book: ({ id }) => books.find(book => book.id === id),
  
  // 本を検索するリゾルバー
  searchBooks: ({ query }) => {
    const searchTerm = query.toLowerCase();
    return books.filter(
      book =>
        book.title.toLowerCase().includes(searchTerm) ||
        book.author.toLowerCase().includes(searchTerm)
    );
  }
};

3.4 Expressサーバーのセットアップ

サーバーの設定を完了させます:

// Expressアプリの作成
const app = express();

// GraphQLエンドポイントの設定
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  // テスト用のGraphiQLインターフェースを有効化
  graphiql: true,
}));

// サーバーの起動
const PORT = 4000;
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}/graphql`);
});

4. GraphQLサーバーの実行とテスト

4.1 サーバーの起動

Node.jsでサーバーを実行します:

node server.js

Server running at http://localhost:4000/graphql というメッセージが表示されるはずです。

4.2 GraphiQLでのテスト

ブラウザで http://localhost:4000/graphql を開き、GraphiQLインターフェースにアクセスします。

クエリ例:すべての本を取得

{
  books {
    id
    title
    author
    year
  }
}

クエリ例:特定の1冊を取得

{
  book(id: "1") {
    title
    author
    genre
  }
}

クエリ例:本の検索

{
  searchBooks(query: "Gatsby") {
    title
    author
    year
  }
}

5. Mutation(ミューテーション)のハンドリング

Mutationは、サーバー上のデータを変更するために使用されます。本の追加、更新、削除機能を追加しましょう。

5.1 スキーマの更新

スキーマに Mutation 型を追加します:

const schema = buildSchema(`
  # ... (以前の型定義はそのまま) ...

  # 本の追加・更新用のインプット型
  input BookInput {
    title: String
    author: String
    year: Int
    genre: String
  }

  type Mutation {
    # 新しい本を追加
    addBook(input: BookInput!): Book!
    # 既存の本を更新
    updateBook(id: ID!, input: BookInput!): Book
    # 本を削除
    deleteBook(id: ID!): Boolean
  }
`);

5.2 Mutationリゾルバーの実装

ルートリゾルバーオブジェクトを更新して、Mutation用のリゾルバーを含めます:

const root = {
  // ... (以前のQueryリゾルバーはそのまま) ...

  // Mutationリゾルバー
  addBook: ({ input }) => {
    const newBook = {
      id: String(books.length + 1),
      ...input
    }
    books.push(newBook);
    return newBook;
  },

  updateBook: ({ id, input }) => {
    const bookIndex = books.findIndex(book => book.id === id);
    if (bookIndex === -1) return null;

    const updatedBook = {
      ...books[bookIndex],
      ...input
    }
    books[bookIndex] = updatedBook;
    return updatedBook;
  },

  deleteBook: ({ id }) => {
    const bookIndex = books.findIndex(book => book.id === id);
    if (bookIndex === -1) return false;

    books.splice(bookIndex, 1);
    return true;
  }
};

5.3 Mutationのテスト

本の追加

mutation {
  addBook(input: {
    title: "1984"
    author: "George Orwell"
    year: 1949
    genre: "Dystopian"
  }) {
    id
    title
    author
  }
}

本の更新

mutation {
  updateBook(
    id: "1"
    input: { year: 1926 }
  ) {
    title
    year
  }
}

本の削除

mutation {
  deleteBook(id: "2")
}

6. ベストプラクティス

6.1 エラーハンドリング

リゾルバー内では常に適切にエラーを処理してください。

const root = {
  book: ({ id }) => {
    const book = books.find(book => book.id === id);
    if (!book) {
      throw new Error('Book not found');
    }
    return book;
  },
  // ... 他のリゾルバー
}

6.2 データバリデーション

処理を行う前に、入力データを検証します。

const { GraphQLError } = require('graphql');

const root = {
  addBook: ({ input }) => {
    if (input.year && (input.year < 0 || input.year > new Date().getFullYear() + 1)) {
      throw new GraphQLError('Invalid publication year', {
        extensions: { code: 'BAD_USER_INPUT' }
      });
    }
    // ... 残りの処理
  }
};

6.3 N+1問題

DataLoader を使用して、データベースクエリをバッチ化およびキャッシュします。

npm install dataloader
const DataLoader = require('dataloader');

// 本のためのローダーを作成
const bookLoader = new DataLoader(async (ids) => {
  // 実際のアプリではここでデータベースクエリを実行します
  return ids.map(id => books.find(book => book.id === id));
});

const root = {
  book: ({ id }) => bookLoader.load(id),
  // ... 他のリゾルバー
};

7. 次のステップ

  • 実際のデータベース(MongoDB、PostgreSQLなど)に接続する
  • 認証(Authentication)と認可(Authorization)を実装する
  • リアルタイム更新のためのサブスクリプション(Subscriptions)を追加する
  • より高度な機能のために Apollo Server を検討する
  • マイクロサービスのためのスキーマステッチ(Schema Stitching)やフェデレーション(Federation)を学ぶ

       ヒント: 再利用性とセキュリティを高めるために、GraphQLの操作では常に変数(Variables)を使用するようにしましょう。

8. GraphQLのスキーマと型

GraphQLのスキーマは、APIの構造とリクエスト可能なデータの型を定義します。

8.1 型システム

GraphQLは、データの形状を定義するために型システムを使用します。以下は基本的なスカラー型です:

説明
Int符号付き32ビット整数42
Float符号付きダブル精度浮動小数点数3.14
StringUTF-8 文字列"Hello, GraphQL!"
Boolean真偽値true, false
ID一意の識別子(Stringとしてシリアライズされる)"5f8a8d8e8f8c8d8b8a8e8f8c"