Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Apollo GraphQL× Express で Playground ページの URL パスを変更する方法

こんにちは、Sansan Engineering Unit の渡邉です🦐
直近で Apollo GraphQL の Express 向けライブラリを利用して API を開発していたのですが、

 Playground ページに個別の URL パスを設定する方法

が調べても良いページが見つからなく困ったので記事にまとめてみました🙋

背景

要件は次のとおりです。

  • `/graphql` の API は特定の IP アドレス以外からのアクセスを制限できること
  • 社内からは API のドキュメント(スキーマ定義など)が閲覧でき、動作確認のために Playground ページで API をクエリできること

これらの要件を満たすアイデアとして「Playground ページは `/playground` という URL パスでアクセスできるようにする」という方法を思いつきました。
しかし、Apollo GraphQL の公式ページに記載されている方法で Playground ページを実装すると、Playground ページに個別の URL パスを設定できないという問題に直面しました。

解決方法

解決方法の概要は次のとおりです。

  1. Playground ページを無効化した Apollo Server インスタンスと Playground ページを有効化した Apollo Server インスタンスの 2 つを作成する
  2. 1 で作った 2 つのインスタンスをそれぞれ Express ミドルウェアとしてロードする

上記を具体的なコードを交えて説明します。

1. Playground ページを無効化した Apollo Server インスタンスと Playground ページを有効化した Apollo Server インスタンスの 2 つを作成する

// Playground ページを無効化した Apollo Server インスタンスを作成
const apiServer = new ApolloServer({
  typeDefs,
  resolvers,
  // Playground ページを無効化
  plugins: [ApolloServerPluginLandingPageDisabled()],
});

// Playground ページを有効化した Apollo Server インスタンスを作成
const playgroundServer = new ApolloServer({
  typeDefs,
  resolvers,
  // Playground ページを有効化し、ポーリングを無効化
  plugins: [
    ApolloServerPluginLandingPageLocalDefault({
      embed: { initialState: { pollForSchemaUpdates: false } },
    }),
  ],
});

2. 2 つの ApolloServer インスタンスをそれぞれ Express ミドルウェアとしてロードする

// Playground ページを無効化した Apollo Server インスタンスを Express ミドルウェア関数としてロード
app.use("/graphql", bodyParser.json(), expressMiddleware(apiServer));

// Playground ページを有効化した Apollo Server インスタンスを Express ミドルウェア関数としてロード
app.use("/playground", bodyParser.json(), expressMiddleware(playgroundServer));


今回のコードの全体像は以下のようになります。

import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import { ApolloServerPluginLandingPageDisabled } from "@apollo/server/plugin/disabled";
import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
import express from "express";
import http from "http";
import bodyParser from "body-parser";

const typeDefs = `#graphql
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

const books = [
  {
    title: "The Awakening",
    author: "Kate Chopin",
  },
  {
    title: "City of Glass",
    author: "Paul Auster",
  },
];

const resolvers = {
  Query: {
    books: () => books,
  },
};

const app = express();

const httpServer = http.createServer(app);

const apiServer = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginLandingPageDisabled()],
});
await apiServer.start();

const playgroundServer = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    ApolloServerPluginLandingPageLocalDefault({
      embed: { initialState: { pollForSchemaUpdates: false } },
    }),
  ],
});
await playgroundServer.start();

app.use("/graphql", bodyParser.json(), expressMiddleware(apiServer));
app.use("/playground", bodyParser.json(), expressMiddleware(playgroundServer));

await new Promise<void>((resolve) =>
  httpServer.listen({ port: 3333 }, resolve)
);

console.log(
  `🚀 API Server ready at [POST] http://localhost:3333/graphql, Playground Server ready at [GET] http://localhost:3333/playground`
);

注意事項

今回はあくまで社内向けの API だったので、可用性よりもコストやスピードを重視して

 Playground ページを無効化した ApolloServer と Playground ページを有効化した ApolloServer を同一のアプリケーションで動かす

という手段を取りましたが、お客様向けの API など、可用性が重視される場面では十分注意する必要があります。

まとめ

この記事では、Apollo GraphQL を Express で扱う場合において、Playground ページの URL パスを変更する方法について、ApolloServer のインスタンスを 2 つ作成し、それぞれを Express ミドルウェアとして個別にロードすることで実現しました。

私たちのチームでは Web アプリ開発エンジニアを募集しています。Web アプリ開発だけではなくデータエンジニアリングなど、幅広い業務を経験できます!

open.talentio.com


© Sansan, Inc.