サイトのAPI図鑑B版
掲載情報が正確でない可能性があります。
開発者向けAPIツール

GraphQLサーバーの構築ガイド【Apollo Server・Hasura・Strawberry Python】

Apollo Server(Node.js)・Hasura(PostgreSQL自動生成)・Strawberry(Python)を使ったGraphQLサーバーの構築方法、スキーマ設計・認証・N+1問題の解決策を解説します。

#GraphQL#Apollo Server#Hasura#スキーマ設計

GraphQLサーバー構築の概要

GraphQLはクライアントが必要なデータのみを指定して取得できる柔軟なAPIクエリ言語です。REST APIと比較してオーバーフェッチ・アンダーフェッチを防ぎ、フロントエンド開発の効率を大幅に向上させます。

Apollo Server(Node.js)の基本構築

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { makeExecutableSchema } from '@graphql-tools/schema';

const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }
  
  type Post {
    id: ID!
    title: String!
    body: String!
    author: User!
    createdAt: String!
  }
  
  type Query {
    users(page: Int, limit: Int): UserConnection!
    user(id: ID!): User
    posts: [Post!]!
  }
  
  type UserConnection {
    nodes: [User!]!
    totalCount: Int!
    pageInfo: PageInfo!
  }
  
  type PageInfo {
    hasNextPage: Boolean!
    endCursor: String
  }
  
  type Mutation {
    createUser(input: CreateUserInput!): User!
    updateUser(id: ID!, input: UpdateUserInput!): User!
  }
  
  input CreateUserInput {
    name: String!
    email: String!
  }
  
  input UpdateUserInput {
    name: String
    email: String
  }
`;

const resolvers = {
  Query: {
    users: async (_, { page = 1, limit = 10 }, { db }) => {
      const total = await db.users.count();
      const users = await db.users.findMany({
        skip: (page - 1) * limit,
        take: limit
      });
      return {
        nodes: users,
        totalCount: total,
        pageInfo: { hasNextPage: page * limit < total }
      };
    },
    user: async (_, { id }, { db }) => db.users.findUnique({ where: { id } })
  },
  
  Mutation: {
    createUser: async (_, { input }, { db }) => {
      return db.users.create({ data: input });
    }
  }
};

const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => ({
    db: prisma,
    user: await authenticate(req.headers.authorization)
  }),
  listen: { port: 4000 }
});

DataLoaderでN+1問題を解決する

import DataLoader from 'dataloader';

// ユーザーをバッチで取得するDataLoader
const createUserLoader = (db) => new DataLoader(async (userIds) => {
  const users = await db.users.findMany({
    where: { id: { in: userIds as string[] } }
  });
  // userIdsの順序に合わせてマッピング
  return userIds.map(id => users.find(u => u.id === id) || null);
});

// コンテキストにDataLoaderを追加
context: async () => ({
  db: prisma,
  loaders: {
    user: createUserLoader(prisma)
  }
})

// リゾルバーでDataLoaderを使用
Post: {
  author: async (post, _, { loaders }) => {
    return loaders.user.load(post.authorId); // 自動的にバッチ処理
  }
}

Hasuraで自動GraphQL生成

HasuraはDockerで数分で起動し、PostgreSQLのテーブルを選択するだけでCRUD操作・フィルタリング・ページネーション・サブスクリプションを含む完全なGraphQL APIが自動生成されます。

まとめ

GraphQLサーバーの構築にはApollo Serverが最も広く使われています。DataLoaderでN+1問題を解決し、コンテキストで認証・DBを管理することで堅牢なGraphQL APIが構築できます。Hasuraは既存のPostgreSQLテーブルから即座にGraphQL APIを生成したい場合の強力な選択肢です。

よくある質問

Q.GraphQLのN+1問題とはどういう問題ですか?

GraphQLでリストを取得し各要素の関連データを取得する場合、1件のリスト取得 + N件の関連クエリ = 合計N+1回のDBクエリが発生する問題です。DataLoaderライブラリを使ってバッチ処理にまとめることで解決できます。

Q.HasuraはREST APIの代替として使えますか?

HasuraはPostgreSQLのスキーマから自動的にGraphQL APIを生成します。CRUD操作・フィルタリング・ページネーション・リアルタイムサブスクリプションが自動で利用可能になります。カスタムビジネスロジックはREST APIアクションかEvent Triggersで追加します。

Q.GraphQLのサブスクリプションとREST APIのWebSocketはどう違いますか?

GraphQLサブスクリプションはクライアントが特定のGraphQLクエリの変更をWebSocketで受け取る仕組みです。REST APIのWebSocketよりも型安全で、どのフィールドを購読するかを柔軟に指定できます。

関連記事