🚀

GraphQLのモックAPIがほしい (ApolloServer)

2023/04/06に公開

背景

現在Next.jsでSPA開発するプロジェクトに参画中なんですが、バックエンド開発にGraphQLが採用されることになりました。
本番のAPI叩く前に、ローカルでの開発中に自分でよしなにいじれるモックAPIがほしいと思った次第です。

主な使用技術

  • TypeScript
  • Apollo Server
  • GraphQL

結論

下記のコードで実装しました。
公式のサンプルコードをヒントにアレンジしたものです。

package.json

{
  "type": "module",
  "scripts": {
    "compile": "tsc",
    "start": "npm run compile && node ./dist/index.js"
  },

  "devDependencies": {
    "@types/node": "^18.15.11",
    "typescript": "^5.0.3"
  },

  "dependencies": {
    "@apollo/server": "^4.6.0",
    "cors": "^2.8.5",
    "graphql": "^16.6.0"
  }
}

index.ts

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import http from 'http';
import cors from 'cors';

const typeDefs = `#graphql
  # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

  # This "Book" type defines the queryable fields for every book in our data source.
  type Book {
    title: String
    author: String
  }

  # The "Query" type is special: it lists all of the available queries that
  # clients can execute, along with the return type for each. In this
  # case, the "books" query returns an array of zero or more Books (defined above).
  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 server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});

await server.start();

app.use(
  '/graphql',
  // cors<cors.CorsRequest>({ origin: ['http://localhost:3001', 'http://localhost:4000'], optionsSuccessStatus: 200 }),
  cors(),
  express.json(),
  expressMiddleware(server),
);

await new Promise<void>((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at http://localhost:4000/graphql`);

tsconfig.json

{
    "compilerOptions": {
      "rootDirs": ["src"],
      "outDir": "dist",
      "lib": ["es2020"],
      "target": "es2020",
      "module": "esnext",
      "moduleResolution": "node",
      "esModuleInterop": true,
      "types": ["node"]
    }
  }

これで npm start 実行すればport:4000でモックサーバーが立ち上がります。

なおReactやNext.jsで呼び出す際にはフロントエンド側はこんな感じのコードでできます。

index.tsx

import { useEffect } from 'react';
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql',
  cache: new InMemoryCache(),
});

const BOOKS_QUERY =  gql`
  query {
    books {
      title
      author
    }
  }
`;

const Home = () => {

  useEffect(() => {
    client
      .query({ query: BOOKS_QUERY })
      .then((result) => console.log(result));
  }, []);
  
  return <div></div>
}

export default Home

ポイント

expressを使う

ApolloにはstartStandaloneServer関数というものが用意されており、これを使ってモックAPIはさくっと作れるんですが公式曰くCORS設定がいじれないようです。
今回の用途だとCORSエラーになってしまうので、expressにて対応。

body-parserの使い方

Apolloの公式含め、body-parserの実装例でこういう書き方をよく見かけます。

import { json } from 'body-parser';

...(中略)...

app.use(
  ...(中略)...
  json(),
);

しかしこれだとエラーになります。

SyntaxError: Named export 'json' not found. The requested module 'body-parser' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'body-parser';
const { json } = pkg;

そもそもexpressを使う場合、標準でbody parserが搭載されているのでそっちを使います。

import express from 'express';

...(中略)...

app.use(
  ...(中略)...
  express.json(),
);

Discussion