mizdra's blog

ぽよぐらみんぐ

JavaScript で GraphQL サーバーの技術選定をする際の登場人物

これは はてなエンジニア - Qiita Advent Calendar 2024 - Qiita 15日目の記事です。昨日は id:utgwkk さんの「ISUCONの感想戦を支えるEC2の自動開始・停止、そしてAWS Step Functions」でした。


はてなでフロントエンドエキスパートをしている id:mizdra です。この記事では、JavaScript で GraphQL サーバーの技術選定をする際に、どのようなツールやライブラリがあるのかを紹介します。

というのも、JavaScript で GraphQL サーバーを作ろうと思って検索してみると、「Next.js + Apollo Server + graphql-codegen で GraphQL サーバーを作ろう!」だとか、そういう記事が多数出てきます。ただ、複数のライブラリやツールを組み合わせると作れることは分かるのですが、どのライブラリ・ツールが何を担っていて、どういう代替ライブラリ・ツールに差し替え可能なのかが、正直調べても分かりづらいです。「Apollo Server」ってやつは「Pothos」ってやつに差し替えられるの? とか、そういった疑問が出てきがちです *1

そうした疑問に答えられるように、GraphQL サーバーを構成するライブラリやツールの整理をする、というのがこの記事の目的です。

はじめに要点だけ

  • GraphQL サーバーを作る際には、以下のようなレイヤーごとに技術選定すると良い
    • GraphQL Server
      • 「GraphQL クエリ」を受け取って、「GraphQL レスポンス」を返すことが仕事
      • スキーマ・Resolver は外で定義されたものを受け取って、それを使って GraphQL クエリを処理する
        • スキーマ・Resolver はユーザが好きなようなライブラリをツールを使って書ける
    • HTTP Server
      • 「GraphQL Server」には直接 HTTP リクエストを受け取って、HTTP レスポンスを返す機能がないので、そこを担う
    • スキーマ定義
    • Resolver
  • このレイヤーごとであれば、ライブラリやツールを差し替えできる
  • コードファーストでは、スキーマと Resolver が同時に定義される
    • Resolver の定義方法をどうするかを別個に検討しなくて良い
    • GraphQL Code Generator はなくてもやっていける

GraphQL Server

「GraphQL クエリ」を受け取って、「GraphQL レスポンス」を返すもののこと。有名どころだと以下のようなライブラリが存在する。

  • @apollo/server
    • Apollo Graph Inc. が作っている GraphQL Server ライブラリ *2
    • initial commit は 2016/4
    • この中ではもっとも有名で、使っている人も多い
    • 実装されている機能が豊富で、GraphQL のニッチな機能とかが、最も速く実装されている (Apollo Federation とか)
  • graphql-yoga
  • @hono/graphql-server
    • Hono が出しているやつ
    • @apollo/server や graphql-yoga に乗っからず、Hono のために専用のものを用意しているのは何故なんでしょうね

例えば @apollo/server は以下のようにして使います。ApolloServer というクラスに GraphQL スキーマと resolver を渡して初期化するだけです。

// https://www.apollographql.com/docs/apollo-server/getting-started より引用・一部改変
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = `
  type Book {
    title: String
    author: String
  }
  type Query {
    books: [Book]
  }
`;
const books = [
  { title: 'ゆゆ式 1', author: '三上小又' },
  { title: 'ゆゆ式 2', author: '三上小又' },
];
const resolvers = {
  Query: {
    books: () => books,
  },
};
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

この辺は graphql-yoga も全く同じインターフェイスになってます

HTTP Server

@apollo/server や graphql-yoga は「GraphQL クエリを受け取って GraphQL レスポンスを返す」ことはできますが、直接 HTTP リクエストを受け取って、HTTP レスポンスを返したりといったことはできません。そのため、実際に機能する GraphQL サーバーを実装するには、HTTP Server / Router と組み合わせないといけません。

有名どころだと、以下のようなものが使われることが多いです。

スキーマ定義

さっき見たように、@apollo/server や graphql-yoga はスキーマを外から受け取るだけで、そのスキーマがどう定義されているかは関知しません。ここはユーザが自由に定義できます。

スキーマの定義方法は、以下のようなものがあります。

  • 文字列
    • 「GraphQL Server」で紹介したやり方
    • サンプルコードに使うくらいで、プロダクションではまず採用されない
  • スキーマファイル
    • schema.graphql などで定義するやつ
    • fs.readFile で読み込んで、ApolloServer に渡したら良い
  • コードファーストライブラリ

コードファーストライブラリの場合はちょっと特殊で、大抵スキーマと resolver を一緒に定義する作りになってます。

// https://pothos-graphql.dev/docs より引用
import { createYoga } from 'graphql-yoga';
import { createServer } from 'node:http';
import SchemaBuilder from '@pothos/core';
 
const builder = new SchemaBuilder({});
 
builder.queryType({
  fields: (t) => ({
    hello: t.string({
      args: {
        name: t.arg.string(),
      },
      resolve: (parent, { name }) => `hello, ${name || 'World'}`,
    }),
  }),
});
 
const yoga = createYoga({
  schema: builder.toSchema(),
});

builder.queryType の中でスキーマと Resolver を一緒に定義しているのが分かると思います。そしてそのスキーマと resolver の情報を builder.toSchema() で抽出して、yoga に渡しています。yoga の schema オプションは、typeDefs オプション・resolvers オプションに相当する情報をまとめて渡せるオプションで、これで一度にスキーマと Resolver の情報を yoga に渡しています。

Resolver

スキーマと同様、Resolver についても @apollo/server や graphql-yoga はそれが定義されているか関知しません。よってスキーマファーストで開発する場合、resolver をどうやって定義するかも決めなければなりません。こちらについてもアプローチが色々あります。

  • 手で頑張って書く
    • const resolvers = { ... } の中を気合で書く
    • 型チェックも何もない (本来 String しか返せない field に TypeScript の number を返せてしまう)
    • 基本的にこれを採用することない
  • GraphQL Code Generator で resolver の型定義を生成して、型が付いた状態で書く
    • @graphql-codegen/typescript-resolvers という plugin があり、これを使うとスキーマファイルから TypeScript 向けの resolver の型定義を生成できる
    • Resolvers という型が生成されるので、const resolvers: Resolvers = { ... } のようにして使う
      • 後は { ... } の中を好きに書く
    • コードファースト開発では大体これが採用される (という認識。他にナウい手段があるなら教えてください!)

コードファーストアプローチでは、スキーマをコードで定義する時に一緒に resolver も定義することになるので、この部分の技術選定はスキップされます。そのため、GraphQL Code Generator は基本的に導入しなくても問題ありません。ただ、Resolvers 以外の型定義 (例えば で生成できる GraphQL Object の型など) を生成したくなったら GraphQL Code Generator を導入することになりますが、そうしたものが欲しくなるまでは、導入しなくて良いでしょう。

おわりに

この記事では JavaScript における GraphQL サーバーを構成するライブラリやツールの整理をしました。GraphQL サーバーの技術選定をする際の参考になれば嬉しいです。

はてなエンジニア - Qiita Advent Calendar 2024 - Qiita の明日の担当は id:bps_tomoya さんです。

*1:ちなみに答えは No です

*2:もともと Meteor Development Group という団体が作ったライブラリだった: https://zenn.dev/username/articles/2023-07-17-944941f57183b6#%E6%AD%B4%E5%8F%B2

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.