Lento con forza

大学生気分のIT系エンジニアが色々書いてく何か。ブログ名決めました。

Next.jsのアプリをGraphQLでスキーマファーストで開発する

最近Next.jsを使って開発することが多いです。ちょっとした物を作る時はFirebaseと組み合わせることが多いのですが、ガッツリAPIを作り込みたい時はGraphQLで書くこともあります。

GraphQLを使う時はGraphQL SDLでスキーマを定義することになると思います。特に、GraphQLを書く場合はスキーマを決めてからサーバーやクライアントの実装に着手することが多いのではないでしょうか。スキーマを書くことでコード生成を行うことができたり、型チェックによる恩恵を受けることができます。

スキーマがあるなら、クライアント側のアプリケーションでは、サーバーサイドのアプリケーション開発を待たずにモックサーバーを作って開発を進めることができるはずです。今回はNext.jsとGraphQLを組み合わせて、スキーマからモックサーバーを作り、開発中はそこのモックサーバーにアクセスすることで

  • サーバーサイドの開発完了を待たずにNext.jsアプリを開発できる
  • インターネット接続がない状態でもアプリ開発を続けることを可能にする
  • モックサーバーで任意のデータを返すことをできるようにし、サーバーサイドに依存しない表示の確認を可能にする

ことを目的にします。

サンプルアプリ

github.com

サンプルアプリを作ったので、こちらのpackage.jsonあたりをみていただければ何をしているかわかると思います。

以下では何をどのように行ったかを解説します。

やったこと

プロジェクト作成

まずは、create-next-appでプロジェクトを作ります。

npx create-next-app --example with-apollo graphql-next-app

この状態で実行すると、すでにGraphQLが使えるNext.jsアプリができています。

cd graphql-next-app
yarn dev
open http://localhost:3000

f:id:kouki_dan:20200331161729p:plain

ただ、実際のGraphQLサーバーに依存している状態なので、インターネット接続のない環境などではアクセスすることができません。試しにWi-Fiを切断した状態でリクエストを送ると以下のような表示になります。

f:id:kouki_dan:20200331161750p:plain

これを解決していきましょう。

スキーマを準備する

モックサーバーを作成するためにスキーマを使います。このアプリで使っているGraphQLサーバーは以下のURLからアクセス可能で、スキーマもこちらからダウンロード可能です。

api.graph.cool

右上にあるボタンからSDL形式でダウンロードしましょう。

f:id:kouki_dan:20200331161957p:plain

ここでダウンロードしたファイルは、./schema.graphql という名前でプロジェクト直下に配置しておきます。

Apollo Serverを使ってモックサーバーを作る

先ほどダウンロードしたスキーマを使ってモックサーバーを作りましょう。これはApollo Serverを使うと簡単に行えます。単純なモックサーバーは以下の10行のコードで実現可能です。

// ./mock/index.js
const { ApolloServer } = require("apollo-server");
const { makeExecutableSchema } = require("graphql-tools");
const { importSchema } = require("graphql-import");
const typeDefs = importSchema("../schema.graphql");
const schema = makeExecutableSchema({
  typeDefs
});
const server = new ApolloServer({
  schema,
  mocks: {}
});
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

ただ、今回使っているGraphQLのスキーマでは、DateTimeを扱うために独自のスカラ値を定義しています。Apollo Serverはこのスカラ値をモックとしてどう扱えば良いかわかりません。今回のGraphQLスキーマでモックサーバーを作るためには、DateTimeの情報を追加する必要があります。独自のスカラ値の定義は makeExecutableSchema() メソッドで渡します。

DateTimeの場合は以下のようなリゾルバを生成すると良いでしょう。

const resolvers = {
  DateTime: new GraphQLScalarType({
    name: "DateTime",
    description: "DateTime",
    serialize(data) {
      return data.toISOString();
    },
    parseValue(data) {
      return new Date(data);
    },
    parseLiteral(ast) {
      return new Date(ast.value);
    }
  })
};

また、モックとして返す値も追加しましょう。

const server = new ApolloServer({
  schema,
  mocks: {
    DateTime: () => {
      return new Date(); // It returns current Date for all response.
    }
  }
});

ファイル全体はこのようになりました。

// https://github.com/kouki-dan/GraphQL-schema-first-next-app/blob/3f4ac80123e461a62797bbc79136b59178c52545/mock/index.js
const { GraphQLScalarType } = require("graphql");
const { ApolloServer } = require("apollo-server");
const { makeExecutableSchema } = require("graphql-tools");
const { importSchema } = require("graphql-import");
const typeDefs = importSchema("../schema.graphql");
const resolvers = {
  DateTime: new GraphQLScalarType({
    name: "DateTime",
    description: "DateTime",
    serialize(data) {
      return data.toISOString();
    },
    parseValue(data) {
      return new Date(data);
    },
    parseLiteral(ast) {
      return new Date(ast.value);
    }
  })
};
const schema = makeExecutableSchema({
  typeDefs,
  resolvers
});
const server = new ApolloServer({
  schema,
  mocks: {
    DateTime: () => {
      return new Date();
    }
  }
});
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

これを ./mock/index.js に配置し、実行するための設定を ./mock/package.json として以下のようにします。

{
  "name": "mock",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "apollo-server": "^2.9.13",
    "graphql": "^14.5.8",
    "graphql-import": "^0.7.1"
  }
}

以下のコマンドで実際にモックサーバーが動作するかを確認しましょう。

cd mock
npm install
npm start

http://localhost:4000 を開くとPlaygroudが起動します。クエリを書くとモックデータが返ってくることが確認できます。

f:id:kouki_dan:20200331162809p:plain

これでモックサーバーが完成しました。実際に使う時はモックやリゾルバをカスタマイズして使うことになると思います。

Next.jsアプリとモックサーバーを接続する

GraphQLに接続するNext.jsアプリと、任意のデータを返すGraphQLサーバーが実現できたので、これを開発時に組み合わせることで最初に述べた3つの目的を満たすことができます。

npm-run-all を使うとこの2つのサーバーを簡単に同時に起動することができます。以下のコマンドで追加しましょう。

yarn add -D npm-run-all

プロジェクト直下のpackage.jsonを以下のように書き換えます。

"scripts": {
    "mock": "npm start --prefix mock",
    "dev-mock": "GRAPHQL_ENDPOINT=\"http://localhost:4000/\" run-p dev mock",
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "postinstall": "npm install --prefix mock"
},

mock, dev-mock, postinstall が追加されました。

mock は、先ほど作ったモックサーバーを起動します。 dev-mockは、npm-run-allを使ってNext.jsの開発サーバーと、GraphQLのモックサーバー(mock)を同時に起動します。後ほど説明しますが、GraphQLのエンドポイントを書き換えるために環境変数でモックサーバーのURLを渡しています。

postinstall では、npm install 時に ./mock 以下でもインストールを行うように設定しています。

dev-mockの説明で書いた通り、開発用のモックサーバーと、実際のサーバーを切り替えることができるようにしなければなりません。next.config.jsを編集して実現します。

module.exports = {
  publicRuntimeConfig: {
    graphqlEndpoint: process.env.GRAPHQL_ENDPOINT || "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn",
  }
};

lib/apollo.js を編集し、この値を利用できるようにします。

function createApolloClient(initialState = {}) {
  // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
  const { publicRuntimeConfig } = getConfig();
  return new ApolloClient({
    ssrMode: typeof window === 'undefined', // Disables forceFetch on the server (so queries are only run once)
    link: new HttpLink({
      uri: publicRuntimeConfig.graphqlEndpoint, // Server URL (must be absolute)
      credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
      fetch,
    }),
    cache: new InMemoryCache().restore(initialState),
  })
}

これで完成です!全体のdiffはこちらで見れます。

github.com

早速モックサーバーモードで起動してみましょう。先ほどpackage.jsonに定義した dev-mock スクリプトを使います。

npm run dev-mock

http://localhost:3000 を開くとモックサーバーに繋がってることを確認できます! f:id:kouki_dan:20200331190121p:plain

値をカスタマイズする

ここまでで目的は達成されました、最後に値をカスタマイズできるようにしましょう。./mock/index.js を編集し、mocksに返したい値を与えることで実現できます。以下はUserの名前を僕の名前(Kouki Saito)に変更する例です。

const server = new ApolloServer({
  schema,
  mocks: {
    DateTime: () => {
      return new Date();
    },
    User: () => {
      return {
        firstName: "Kouki",
        lastName: "Saito",
      }
    },
  }
});

Playgroundを見ると、このように変更できていることがわかります。 f:id:kouki_dan:20200331190310p:plain

まとめ

GraphQLは、Webアプリケーションを開発するための強力なツールです。 ApolloはGraphQLを使用するための優れたプラットフォームです。Apollo Serverでモックを作ることで、クライアントアプリを効率的に開発することにも役立ちます。 この記事で説明した内容は以下のサンプルリポジトリで確認できます。お役に立てば幸いです。

github.com