こんにちは、Sansan Engineering Unit の渡邉です🦐
直近で Apollo GraphQL の Express 向けライブラリを利用して API を開発していたのですが、
Playground ページに個別の URL パスを設定する方法
が調べても良いページが見つからなく困ったので記事にまとめてみました🙋
背景
要件は次のとおりです。
- `/graphql` の API は特定の IP アドレス以外からのアクセスを制限できること
- 社内からは API のドキュメント(スキーマ定義など)が閲覧でき、動作確認のために Playground ページで API をクエリできること
これらの要件を満たすアイデアとして「Playground ページは `/playground` という URL パスでアクセスできるようにする」という方法を思いつきました。
しかし、Apollo GraphQL の公式ページに記載されている方法で Playground ページを実装すると、Playground ページに個別の URL パスを設定できないという問題に直面しました。
解決方法
解決方法の概要は次のとおりです。
- Playground ページを無効化した Apollo Server インスタンスと Playground ページを有効化した Apollo Server インスタンスの 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 アプリ開発だけではなくデータエンジニアリングなど、幅広い業務を経験できます!