apollo-server + prisma + MySQL で GraphQLサーバを構築する

# はじめに

`apollo-server` + `prisma` + `MySQL` を使って、GraphQLサーバを構築する手順をまとめました。

`apollo-server` とは
https://www.apollographql.com/docs/

`prisma` とは
https://www.prisma.io/docs/concepts/overview/what-is-prisma

# 全体の手順

前提として `Node.js` が必須です。以降の手順は `npm` で作業していますが、お好みで `yarn` 等をご利用ください。
まずは `apollo-server` のみで GraphQLサーバを構築した後、`prisma` を使って `MySQL` と連携し、さらに両者を連携させて行きます。

# apollo-server の構築

公式の `Get started` の通りの手順で簡単に作成できてしまいます。
https://www.apollographql.com/docs/apollo-server/getting-started

この手順で各ファイルは以下の通りになるはずです。
※上記URLではTypeScriptをお勧めしているので、こちらも利用します。

```json:package.json
{
  "name": "graphql-server-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "compile": "tsc",
    "start": "npm run compile && node ./dist/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@apollo/server": "^4.0.4",
    "graphql": "^16.6.0"
  },
  "devDependencies": {
    "@types/node": "^18.11.7",
    "typescript": "^4.8.4"
  }
}
```

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

```typescript:src/index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// GraphQLサーバで利用するデータ形式（=スキーマ）を定義します。
const typeDefs = `
  # データソースのデータ形式です。
  type Book {
    title: String
    author: String
  }

  # 利用するクエリが返すデータ形式を定義します。
  # この場合、booksは0件以上のBookデータの配列を表します。
  type Query {
    books: [Book]
  }
`;

// データセットです。
// 今回は安価に作成するため、定数で定義します。
const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
  },
];

// クエリ実行時、どのようなデータを返すか設定（リゾルバ）をします。
// この場合、上記のデータセットをそのまま返します。
const resolvers = {
  Query: {
    books: () => books,
  },
};

// スキーマとリゾルバを指定し、apollo-server起動準備をします。
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// apollo-serverをポート番号:4000で起動します。
const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);
```

公式の通り、以下を起動し
```bash
% npm start
```

http://localhost:4000/ にアクセスすると以下のような、GraphQLサーバにクエリを投げることができる画面（Apollo Sandbox）が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55290/dcef1010-7267-d96b-6d32-40d632437a64.png)

# prisma と MySQL の連携

※ `MySQL` の構築については本投稿では説明しません。他の方が `Docker` 等で構築する手順を投稿しておりますので、そちらを参考にしてください。
※今回は既に `MySQL` にあるテーブルを元に `schema.prisma` を作成(prisma db pull)します。

テーブルやレコードは以下で準備しておきます。

```sql
CREATE TABLE `test` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL COMMENT '名前',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '日時',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='テスト';

INSERT INTO `test` (`id`, `name`) VALUES ('1', 'テスト1');
INSERT INTO `test` (`id`, `name`) VALUES ('2', 'テスト2');
INSERT INTO `test` (`id`, `name`) VALUES ('3', 'テスト3');
```

次に、`prismaクライアント` と `prisma CLI` をインストールします。
インストール後、 `prisma` のセットアップ(init)します。
https://www.prisma.io/docs/concepts/overview/what-is-prisma
https://www.prisma.io/docs/reference/api-reference/command-reference

```bash
% npm install @prisma/client prisma
% npx prisma init
```

次に、 `prisma/schema.prisma` と `.env` を `MySQL` に繋げるよう編集します。
https://www.prisma.io/docs/concepts/database-connectors/mysql

```text:.env
DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"
```

```text:prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}
```

次に、 `MySQL` からテーブルの情報を取得し、 `prisma` の `generate` コマンドを実行します。

```bash
% npx prisma db pull
% npx prisma generate
```

次に、`apollo-server` に連携するため、 `src/index.ts` を以下の通り編集します。
https://www.prisma.io/apollo
https://www.prisma.io/docs/concepts/components/prisma-client/crud

```typescript:src/index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { PrismaClient } from '@prisma/client';

// prismaの利用
const prisma = new PrismaClient();

const typeDefs = `
  # 上記SQLで作成したテーブルの定義
  type Test {
    id: Int
    name: String
    created_at: String
  }

  type Query {
    Test: [Test]
  }
`;

// testテーブルのレコードを返す
const resolvers = {
  Query: {
    Test: () => prisma.test.findMany()
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);
```

これで再び、 `apollo-server` を起動すると、
```bash
% npm start
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55290/0648918c-e892-a2f8-038a-b7d2a092db89.png)

上手く取れてると思いきや、一部エラーが発生してしまいます。
実は `GraphQL` ではデフォルトでは以下の型（Scalar types）しか準備されておらず、
`MySQL` の `BigInt` や `DateTime` を上手く取得できません。
https://www.apollographql.com/docs/apollo-server/schema/schema#scalar-types

そこで必要になるのが `custom scalar` で、
https://www.apollographql.com/docs/apollo-server/schema/custom-scalars

これらをいい感じにまとめてくれている　`graphql-scalars` を今回は利用します。
https://www.the-guild.dev/graphql/scalars/docs
https://www.the-guild.dev/graphql/scalars/docs/usage/apollo-server

# graphql-scalars の導入

今回はさまざまな `custom scalar` を提供している `graphql-scalars` の導入します。
https://www.the-guild.dev/graphql/scalars/docs

使い方はこちら
https://www.the-guild.dev/graphql/scalars/docs/usage/apollo-server

以上に沿って、 `src/index.ts` を以下の通り編集します。

```typescript:src/index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { PrismaClient } from '@prisma/client';
import {
  typeDefs as scalarTypeDefs,
  resolvers as scalarResolvers,
} from 'graphql-scalars';

const prisma = new PrismaClient();

const typeDefs = `
  type Test {
    id: BigInt
    name: String
    created_at: DateTime
  }

  type Query {
    Test: [Test]
  }
`;

const resolvers = {
  Query: {
    Test: () => prisma.test.findMany()
  },
};

const server = new ApolloServer({
  resolvers: {
    ...scalarResolvers,
    ...resolvers,
  },
  typeDefs: [
    ...scalarTypeDefs,
    typeDefs,
  ],
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);
```

これで再び、 `apollo-server` を起動すると、
```bash
% npm start
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55290/bd080e30-f39a-5c5c-079e-01d8b7c8418b.png)

今度は無事取得することができました。
が、日付項目がJSTでテーブルに保存しているにも関わらず、UTC形式かつ実際の時間にズレが生じています。
こちらは、 `prisma` が対応中ですが、あまり活発ではなさそうです…

https://github.com/prisma/prisma/discussions/4153
https://github.com/prisma/prisma/issues/3292

対応されるまでは、API呼び出し元で対応するしかなさそうです。

# おわりに

以上の手順で既存のMySQLを使って、割と簡単にGraphQLサーバを構築することが分かりました。
今回紹介した内容以外にレコードの抽出条件を指定する、レコード登録更新削除を行う、がありますがこちらも公式ドキュメントに沿って対応すれば実現できそうです。