【Vite / Vue / Apollo / GraphQL Code Generator】GraphQLモックサーバーの開発環境を作る

# はじめに

本記事では、GraphQL 導入時に Vite / Vue / Apollo / GraphQL Code Generator を使った GraphQL モックサーバ環境の作り方の一例を説明します。

# 利用するツール/技術要素について

## Vite ⚡️

[Vite](https://vitejs.dev/guide/) は、Vue の開発者である Evan You さんが中心となって開発したフロントエンドのビルドツールです。Vue の開発者が作ったものですが、Vue 以外のフレームワークを適用することも可能です。Vite の dev-server は、ES Module で動作するため、バンドル処理しないことから、起動やHot Module Replacement (HMR) が高速です。

余談ですが、Vite はフランス語で `quick` という意味だそうです。Vue もフランス語で `view` という意味なので、フランス語で命名する傾向があるのかもしれません。

## Vue

[Vue](https://v3.vuejs.org/guide) は、フロントエンドのフレームワークです。賛否両論あるかと思いますが、単一ファイルコンポーネントとして、HTML と Javascript を分けやすいので、React と比較してデザインのプロトタイピングなどがやりやすい印象です。Vueの記法を覚えると、Javascript が得意でなくても簡単なものは作れます。最近では、Vue composition API という React の Hooks のような機能も追加されています。

今回は検討の前提が Vue だったため、Vue を利用します。

## Apollo

[Apollo](https://www.apollographql.com/docs/) は、GraphQL によるアプリ/サービス間のコミュニケーションを簡単にするための吸収層をプラットフォームとして提供しています。プラットフォームとして色々提供されているようですが、その中でも今回は以下を使用します。

### Apollo Client

[Apollo Client](https://www.apollographql.com/docs/react) は、フロントエンドで、GraphQL Query Client として利用します。キャッシュ機構や状態管理を肩代わりしてくれることが、Apollo Client を利用する主なモチベーションです。Apollo 以外にも、Vue で利用できる GraphQL Query Client として、[uqrl](https://github.com/FormidableLabs/urql) や [SWRV](https://github.com/Kong/swrv)、[Villus](https://github.com/logaretm/villus) などがありますが、今回は一番メジャーな Apollo を使用します。

### Apollo Server

[Apollo Server](https://www.apollographql.com/docs/apollo-server/) は、フロントエンドで Apollo を利用するのでセットで採用しました。笑
モックサーバーで GraphQL のバックエンド処理 (リゾルバー) を書くのは大変ですが、Apollo Server はモックモードがあり、クエリに対する単純な結果を返してくれます。ただし、フィルタやページングのような制御はモックモードでは確認できません。

### Apollo Studio

[Apollo Studio](https://www.apollographql.com/docs/studio) を起動すると、ブラウザ上で Apollo Studio により、スキーマ定義やエクスプローラーでクエリ実行結果を確認できます。そのため、フロントエンド開発時にスキーマの検証に使えることはもちろん、フロントエンド側で GraphQL スキーマを設計する場合に、バックエンド側にスキーマやクエリを共有しやすいかなと思います。

## GraphQL

[GraphQL](https://graphql.org/learn) は、クエリ言語仕様です。Meta 社 (旧: Facebook) が[仕様](https://spec.graphql.org) として明確に定義しています。フロントエンドの表示に重きを置いており、クエリとクエリ実行結果が対になっていることが特徴です。Facebook はもちろん、他の有名どころだと [Shopify](https://shopify.dev/apps/tools/graphiql-admin-api) や [Github](https://docs.github.com/en/graphql/overview/explorer) など採用されており、REST と異なり、API 利用者側が必要なものをクエリとして定義して柔軟に結果を取得できます。

## GraphQL Code Generator

[GraphQL Code Generator](https://www.graphql-code-generator.com/docs/getting-started) は、GraphQL スキーマを Typescript のコードに変換するためのツールです。フロントエンド開発時に GraphQL スキーマの型はそのまま使えないので、このツールを使って自動生成した型を利用して開発します。

Apollo でも Code Generator が存在するようですが、GraphQL Code Generator の方が機能が豊富・安定しているようなので、採用しました。GraphQL Code Generator で生成したコードを GraphQL Client の SDK としても利用できるようですが、キャッシュなどのハンドリングは自前になりそうなので、そこは Apollo と棲み分けて利用します。

# モックサーバー開発環境

環境構築の流れを以下に書きますが、不要な方は、[vue-graphql-demo](https://github.com/uedaeita/vue-graphql-demo) に構築結果を置いていますので、見ていただければと思います。

## 前提

今回は、Node のパッケージマネージャーとして [yarn](https://yarnpkg.com/getting-started) を使います。(npm を使う方は適宜読み替えてください)

## ディレクトリ構成

最終的には、以下のようなディレクトリ構成になります。

```bash
.
├── README.md
├── backendmock  # モックサーバー
│   ├── README.md
│   ├── node_modules
│   ├── nodemon.json
│   ├── package.json
│   ├── src
│   │   ├── index.ts
│   │   ├── resolvers
│   │   └── utils
│   ├── tsconfig.json
│   └── yarn.lock
└── frontend  # フロントエンド
    ├── README.md
    ├── codegen.yml
    ├── graphql.schema.json
    ├── index.html
    ├── node_modules
    ├── package.json
    ├── public
    ├── schema.graphql
    ├── src
    │   ├── App.vue
    │   ├── assets
    │   ├── components
    │   ├── env.d.ts
    │   ├── generated
    │   └── main.ts
    ├── tsconfig.json
    ├── vite.config.ts
    └── yarn.lock
```

## 環境構築

適当にディレクトリを掘る

```bash
$ mkdir vue-graphql-demo
$ cd vue-graphql-demo
```

### フロントエンド

#### Vite プロジェクトの作成

[Viteのガイド](https://vitejs.dev/guide/#scaffolding-your-first-vite-project)を参考にプロジェクトを作ります。

```bash
$ yarn create vite frontend --template vue-ts
$ cd frontend
$ yarn
```

この時点で、 `yarn run dev` を実行すると Vite プロジェクトの起動が確認できます。
次に GraphQL と Apollo Client をインストールします。Apollo Client は、Vue3 に対応した [apollo-composable](https://v4.apollo.vuejs.org/guide-composable/setup.html) を使用します。

#### GraphQL + Apollo Client のインストール

```bash
$ yarn add graphql graphql-tag @apollo/client @vue/apollo-composable
```

`main.ts` は、以下のようにすることで、 Vue コンポーネント内で Apollo を使えるようになります。

```typescript:frontend/src/main.ts
import { createApp, provide, h } from 'vue';
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core';
import { DefaultApolloClient } from '@vue/apollo-composable';
import App from './App.vue';

const httpLink = createHttpLink({ uri: 'http://localhost:4000' }); // 後で構築するモックサーバーのエンドポイントを指定
const cache = new InMemoryCache();
const apolloClient = new ApolloClient({ link: httpLink, cache });

const app = createApp({
  setup () {
    provide(DefaultApolloClient, apolloClient)
  },
  render: () => h(App)
});
app.mount('#app');
```

#### GraphQL Code Generator のインストール

以下で、GraphQL Code Generator で利用するモジュールをインストールします。各モジュールの説明は今回は割愛します。

```
$ yarn add -D @graphql-codegen/cli @graphql-codegen/introspection @graphql-codegen/typescript @graphql-codegen/typescript-document-nodes @graphql-codegen/typescript-graphql-request @graphql-codegen/typescript-operations
```

GraphQL Code Generator は、以下のように定義します。

```yaml:frontend/codegen.yml
overwrite: true
schema: schema.graphql  # GraphQL スキーマの置き場所
generates:
  src/generated/graphql.ts:  # 自動生成結果格納先
    plugins:
      - typescript
      - typescript-operations
      - typescript-graphql-request
      - typescript-document-nodes
      - fragment-matcher
  ./graphql.schema.json:
    plugins:
      - "introspection"
# 自動生成時に結果をきれいにしたい場合は、Prettier / Eslint などをインストールした上で Hooks に登録
# hooks:
#   afterAllFileWrite:
#     - prettier --write
#     - eslint --fix
```

コード生成するためのコマンドを `package.json` に追加しておきます。以下の例では、`yarn run gql:gen` を実行することで、GraphQL スキーマからコード生成ができるようになります。

```jsonc:frontend/package.json(scripts部分抜粋)
"scripts": {
  "dev": "vite",
  "build": "vue-tsc --noEmit && vite build",
  "preview": "vite preview",
  "gql:gen": "graphql-codegen --config codegen.yml"  // 追加
}
```

これで、GraphQL 含めたフロントエンド環境が、`yarn run dev` で起動できるようになりました。

### モックサーバー

フロントエンドのディレクトリと並列になるようにモックサーバー用のディレクトリを適当に掘る。

```bash
$ mkdir backendmock
```

#### モックサーバーのプロジェクト作成

以下のコマンドでプロジェクトを作成します。

```bash
$ yarn init  # type は module。main は、src/index.ts。他は任意で OK
$ yarn add -D @types/node typescript ts-node
$ yarn add -D nodemon  # スキーマを変えたら都度読み込み直すためにインストール
```

`tsconfig` は以下のように定義

```jsonc:backendmock/tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "lib": ["esnext"],
    "module": "esnext",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
    },
    "sourceMap": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "strict": true,
  },
  "include": ["src"]
}
```

#### GraphQL + Apollo Server のインストール

以下で GraphQL と Apollo Server に必要なモジュールをインストールします。

```bash
$ yarn add -D graphql apollo-server @graphql-tools/graphql-file-loader @graphql-tools/load @graphql-tools/schema
```

バックエンドのメインファイルは以下のようになります。`resolvers` や `utils/mock` は必要に応じて用意すれば良く、必須ではないです。

```typescript:backendmock/src/index.ts
import { loadSchemaSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '@graphql-tools/schema';
import { ApolloServer } from 'apollo-server';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import resolvers from './resolvers';
import {
  genRandomDate,
  genRandomDateTime,
  genRandomFloat,
  genRandomInt,
  genRandomString,
} from './utils/mock';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const schema = loadSchemaSync(join(__dirname, '../../frontend/schema.graphql'), {
  loaders: [new GraphQLFileLoader()],
});

// 自前で型ごとに返す内容をコントロールしたい場合に定義
const mocks = {
  Int: genRandomInt,
  Float: genRandomFloat,
  String: genRandomString,
  Date: genRandomDate,
  DateTime: genRandomDateTime,
};

const schemaWithResolvers = addResolversToSchema({
  schema,
  resolvers, // モックでは特に必要ないので `resolvers: { Query: {} }` とかでも OK
});

const server = new ApolloServer({
  schema: schemaWithResolvers,
  mocks, // mocks: true とした場合は、Apollo が型ごとに適当な値を返す。
});

server.listen().then(({ url }) => {
  console.log(`🚀 Mock GraphQL Server is ready at ${url}`);
});
```

#### nodemon の設定

[nodemon](https://github.com/remy/nodemon) で、ファイルの watch 対象と起動コマンドを定義します。

```jsonc:backendmock/nodemon.json
{
  "watch": ["../frontend/schema.graphql", "src"],
  "ext": "graphql,ts",
  "exec": "node --experimental-specifier-resolution=node --loader ts-node/esm src/index.ts"
}
```

```jsonc:backendmock/package.json(scripts部分抜粋)
"scripts": {
  "start": "nodemon"  // 追加
}
```

これで、`yarn run start` で GraphQL モックサーバーが起動できるようになりました。

# 開発環境の起動

とりあえず開発環境を起動してみます。

## モックサーバーの起動

```bash
$ cd vue-graphql-demo/backendmock
$ yarn run start

🚀 Mock GraphQL Server is ready at http://localhost:4000/
```

![2021-12-25 16.52.14.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364457/36895579-11b3-ecfd-e7ea-063419b485fd.gif)

こんな感じの画面に辿り着けられれば成功です。`Query your server` をクリックすると、Apollo Studio に Sandbox でアクセスできます。

## フロントエンドの起動

```
$ cd vue-graphql-demo/frontend
$ yarn run dev

  vite v2.7.6 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 307ms.
```

![Screen Shot 2021-12-25 at 16.41.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364457/510024f1-f5f3-298c-f043-88b203b1f963.png)

こんな感じの画面が見られれば成功です。

今のままだと、まだそれぞれ独立されて立ち上げただけで疎通が確認できていない状態なので、次に疎通するための作業を行います。

# GraphQL スキーマの作成

まずは、疎通する際に利用する GraphQL スキーマを試しに作ってみます。
今回は、GraphQL スキーマは、フロントエンド側の持ち物として明確にするために、フロントエンドのディレクトリ配下に置いています。

```graphql:frontend/schema.graphql
"""
The query root of GraphQL interface.
"""
type Query {
  # Look up a human
  human(
    # Select the human that matches this name.
    name: String!
  ): Human
}

type Human implements Node {
    id: ID!
    name: String!
    age: Int!
    birthDay: Date
    hobbies: [String!]!
}

"""
An object with an ID.
"""
interface Node {
  # ID of the object.
  id: ID!
}

"""
An ISO-8601 encoded date string.
"""
scalar Date

# NOTE:
# `#` のコメントは `"""` に置き換えると Apollo Studio で説明として認識されます。
# Snippet 上おかしくなってしまったので、`#` で書いています。
```

保存時にブラウザをリロードして Apollo Studio を表示すると、以下のようにクエリエクスプローラーでスキーマ定義に沿ったクエリを書けるようになります。

![Screen Shot 2021-12-25 at 16.42.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364457/9543a4a2-1c32-ac0c-5005-4c8d423854e7.png)

またフロントエンドで型定義を利用するために、先ほど環境構築時に用意した以下のコマンドを実行します。

```bash
$ yarn run gql:gen
```

成功すると、`codegen.yml` に定義したとおり、`src/generated/graphql.ts` に Typescript のコードが生成されているはずです。

# クエリの実装

作成したスキーマに沿って、GraphQL クエリを `App.vue` に以下のように実装してみます。

```vue:frontend/src/App.vue
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable';
import gql from 'graphql-tag';
import HelloWorld from './components/HelloWorld.vue';

const { result, loading } = useQuery(gql`
  query getHuman($name: String!) {
    human(name: $name) {
      id
      name
      age
      birthDay
      hobbies
    }
  }
`,
{
  name: 'Me'
})
</script>

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
  <div v-if="loading">
    GraphQL loading
  </div>
  <div v-else>
    {{ JSON.stringify(result) }}
  </div>
</template>
```

保存した後に、画面を確認してみると、クエリの実行結果が画面に反映されていることが確認できました。(すごい雑だけど)

![Screen Shot 2021-12-25 at 16.48.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364457/b68cca27-8e98-66fd-9695-d95ad012c224.png)

以上で、フロントエンドとモックサーバー間で GraphQL での疎通が確認できました :tada:

# まとめ

本記事では、Vite / Vue / Apollo / GraphQL Code Generator を使って GraphQL モックサーバーを作り方を説明しました。上にも書いてますが、[vue-graphql-demo](https://github.com/uedaeita/vue-graphql-demo) に最終的な結果を置いています。

記事が長くなるので (十分長い)、細かなところは割愛しましたが、やってみて割とフロントエンドの開発は快適にできるかなという印象を受けました。

本記事では触れていませんが、GraphQL を使うにあたって、リゾルバーの実装などがハードルが高い & 肝になってくるところだと思うので、その辺りを引き続き勉強していければと思っています。

今回の経験が、一部ではありますが、GraphQL 導入時の一助/参考になれば幸いです。

Happy Coding! :santa_tone2: