Prismaを使ってMySQLに接続するGraphQL APIサーバーを構築

## はじめに
基本的にPrismaの公式チュートリアル [Build a GraphQL server from Scratch](https://www.prisma.io/docs/1.10/tutorials/build-graphql-servers/development/build-a-graphql-server-from-scratch-nahgaghei6) に倣って、`Prisma`、`graphql-yoga`を使ってGraphQLサーバーを書いていきます。

本記事では上記チュートリアルとは少し異なるアプローチ

- 言語：TypeScript
- DB：ローカルに立てるDocker上のMySQL

でご紹介します。

チュートリアルを進める前に、`Prisma`って何？という方は、

- [prisma - 最速 GraphQL Server実装](https://qiita.com/mizchi/items/74fb8b1177e83fe10273)
- [Prisma.ioでGraphQL APIサーバーを楽して作る](https://qiita.com/yoshi_ksk/items/6d51703f648533bdbfc8)

辺りを一読してPrismaをざっくりと理解し、公式の 

- [Prisma Introduction: What, Why & How](https://www.prisma.io/docs/understand-prisma/prisma-introduction-what-why-how-j9ff/)

を確認してみるのがオススメです。

### 対象読者
- GraphQLに関する知識をある程度有している方
- GraphQL APIを提供するサーバーを簡単に素早く作りたい方
- データベースは自前のMySQLを使いたい方
- 言語はTypeScriptで書きたい方

## 実行環境
- Mac OS Mojave: v10.14.2
- node: v11.2.0
- npm: v6.5.0
- yarn: v1.12.3
- docker: v17.09.1-ce
- prisma: v1.23.4
- graphql: v3.0.4

## GraphQL APIサーバー（Backends For Frontends）構築
はじめに、外部に向けてGraphQL APIを提供するためのBFFを構築していきます。
後に、外部には公開しない内部向けのPrismaサーバーをラップするように構築します。

### NPMプロジェクト用ディレクトリ作成〜初期化
```sh
mkdir prisma-yoga-mysql-typescript-sample
cd prisma-yoga-mysql-typescript-sample
npm init -y
```

### src/index.tsの作成
```sh
mkdir src
touch src/index.ts
```

### `graphql-yoga`のインストール
```sh
yarn add graphql-yoga
```

### `nodemon`、`ts-node`およびTypeScriptのインストール
ここでチュートリアルではsrc/index.jsにコードを書き込んで実行しますが、TypeScriptで同じことを実行するために、コンパイル無しでNode.js上で直接TSファイルを実行できるライブラリ[ts-node](https://github.com/TypeStrong/ts-node)と、ファイルを監視し、変更があればNodeのプロセスを再起動してくれる[nodemon](https://github.com/remy/nodemon)をインストールします。

```sh
yarn add -D nodemon ts-node typescript 
```

### package.jsonのアップデート
`yarn start`でスクリプトを実行できるように、package.jsonの`scripts`の値を次のように書き換えます。

```package.json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon --ext ts,yaml,graphql --exec 'ts-node' src/index.ts"
  },
```
（拡張子が`ts`、`yaml`、`graphql`のファイルを監視対象とする）

### src/index.tsへの新規書込
```src/index.ts
import { GraphQLServer } from 'graphql-yoga';

// GraphQL SDLに沿ってschemaを定義
const typeDefs = `
  type Query {
    description: String
  }
`;

// GraphQL APIのリクエストに応えるための実装
const resolvers = {
  Query: {
    description: () => `This is the API for a simple blogging application`,
  },
};

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

server.start(() =>
  console.log(`The server is running on http://localhost:4000`),
);

```

ここまで保存した時点で、`yarn start`をコマンドラインで実行すると、`http://localhost:4000`でサーバーが立ち上がるはずです。

http://localhost:4000 にアクセスして、次のクエリを実行して結果が返ってくれば成功です。

```.graphql
{
  description
}
```

<img width="1157" alt="スクリーンショット 2019-01-07 3.44.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/199911/90d1e811-05f7-5e2d-0c6a-4d0db9f69295.png">


### src/schema.graphqlの作成
続けて、src/index.tsの`typeDefs`に代入したものとは別に、GraphQLスキーマをあらためて定義するために、次のようにschema.graphqlを作成します。

```.sh
touch src/schema.graphql
```

```src/schema.graphql
type Query {
  posts: [Post!]!
  post(id: ID!): Post
  description: String!
}

type Mutation {
  createDraft(title: String!, content: String): Post
  deletePost(id: ID!): Post
  publish(id: ID!): Post
}

type Post {
  id: ID!
  title: String!
  content: String!
  published: Boolean!
}

```

### src/index.tsのアップデート
上記で作成したスキーマを読み込むために、src/index.tsを次のように書き換えます。

```src/index.ts
import { GraphQLServer } from 'graphql-yoga';

// GraphQL SDLに沿ってschemaを定義
// const typeDefs = `
//   type Query {
//     description: String
//   }
// `;

// GraphQL APIのリクエストに応えるための実装
const resolvers = {
  Query: {
    description: () => `This is the API for a simple blogging application`,
  },
};

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  resolvers,
});

server.start(() =>
  console.log(`The server is running on http://localhost:4000`),
);

```

それぞれ保存した後、nodemonによってプロセスが再起動された場合はそのまま http://localhost:4000 にアクセスして、schemaが次のように反映されていればOKです。

<img width="1156" alt="スクリーンショット 2019-01-07 4.13.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/199911/cfd57e7f-8db4-9f18-c694-05228ef6ef6c.png">

## Prismaサーバー（内部向け）構築
ここからPrismaを使って、内部向けにサーバーを構築していきます。
これから作る内部向けGraphQL APIサーバーは、DBに対して直接クエリを実行します。

### Prisma CLIのグローバルインストール
`prisma`コマンドが未だ使えない場合は、グローバルインストールしてください。
（追記： 本記事はPrismaのバージョンが1.23.4であることを前提にしてありますので、CLIも1.23.4をインストールしないとうまく動きません）

```sh
npm install -g prisma@1.23.4
```

### databaseディレクトリの作成
次の`prisma`コマンドをプロジェクトのルートで実行すると
 
```sh
prisma init database
```

REPLで実装を選ぶことになりますので、`Create new database`、`MySQL`、`Prisma TypeScript Client `をそれぞれ選択します。
（[本家のチュートリアル Step 13](https://www.prisma.io/tutorials/build-a-graphql-server-from-scratch-ct01/#connecting-a-database)とは別の選択を取っていますので、ご注意ください）

```sh
 % prisma init database
? Set up a new Prisma server or deploy to an existing server? 
                         
  Set up a new Prisma server for local development (based on docker-compose):
  Use existing database      Connect to existing database 
❯ Create new database        Set up a local database using Docker 
                         
  Or deploy to an existing Prisma server:
  Demo server                Hosted demo environment incl. database (requires login) 
  Use other server           Manually provide endpoint of a running Prisma server
   
? What kind of database do you want to deploy to? (Use arrow keys)
❯ MySQL             MySQL compliant databases like MySQL or MariaDB 
  PostgreSQL        PostgreSQL database 
  MongoDB           Mongo Database 
  
? Select the programming language for the generated Prisma client 
❯ Prisma TypeScript Client 
  Prisma Flow Client 
  Prisma JavaScript Client 
  Prisma Go Client 
  Don't generate 
```

コマンド実行後は、`database`ディレクトリが作成され、次のような構成になっているはずです。
（本家チュートリアルでは`database/datamodel.graphql`が作成されていることになっていますが、prisimaのバージョン1.23.4で実行すると、代わりに`database/datamodel.prisma`が作成されます）

```sh
├── database
│   ├── datamodel.prisma // DBのテーブル構造元となる型定義
│   ├── docker-compose.yml
│   ├── generated
│   │   └── prisma-client
│   │       ├── index.ts
│   │       └── prisma-schema.ts
│   └── prisma.yml // Prismaサーバーの設定ファイル
├── package.json
├── src
    ├── index.ts
    └── schema.graphql
```

### datamodel.prismaのアップデート
本家チュートリアルに沿って、次の内容でdatamodel.prismaを書き換えてください。

```database/datamodel.prisma
type Post {
  id: ID! @unique
  title: String!
  content: String!
  published: Boolean! @default(value: "false")
}

```

上記ではどんな定義になっているか、大体想像がつくかと思いますが、デプロイされると`Post`テーブルにマッピングされ、`id`〜`published`までのフィールドと呼ばれる部分は各カラムにマッピングされます。
`!`はNot nullを指し、`@unique`は値がテーブルの中で一意なフィールドとなるよう指定しています。
`@default`は、レコードをインサートする際、`published`の値指定が無い場合は`false`が自動的に割り当てられる指定となっています。

ここで登場する`@unique`や`@default`などの`ディレクティブ`と呼ばれるものについて詳しく知りたい方は、[GraphQLの公式](https://graphql.org/learn/queries/#directives)などをご参照ください。

### DB接続用ポートの設定
こちらの設定は任意ですが、DBに直接、接続したい場合は database/docker-compose.ymlのservices.mysql.portsの値が次のようになるよう、追記してください。

```.yml
  mysql:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: prisma
    volumes:
      - mysql:/var/lib/mysql
    ports:
      - "3333:3306"
```

### Dockerコンテナの起動
Prismaをデプロイさせるには、DBに接続できる状態にする必要があります。
コマンドラインで、docker-compose.ymlが存在するdatabaseディレクトリに移動し、`docker-compose up`を実行し、Dockerコンテナを起動してください。

```sh
cd database 
docker-compose up
```

### Prismaのデプロイ
上記でDockerコンテナを起動した後、別のコマンドラインで再度databaseディレクトリに移動し、`prisma deploy`コマンドを実行して、database/prisma.ymlに記載されているendpoint: http://localhost:4466 に対してデプロイさせます。

```sh
cd database
prisma deploy
```

デプロイと同時に、DBに対してmigrationが実行されます。
デプロイ完了後、http://localhost:4466 にアクセスし、`DOCS`を開くと、`Post`テーブルに対するCRUDが実装されているのが分かります。

<img width="1162" alt="スクリーンショット 2019-01-07 5.53.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/199911/6214559a-9089-f073-0fb7-517818778dfc.png">

また、上記でDB接続用ポートの設定を行なっていた場合は、Sequer ProなどのDBクライアントツールで次のような設定でDBにアクセスできます。
（ユーザ名やパスワード、ポートはdocker-compose.ymlで変更可能）

<img width="453" alt="スクリーンショット 2019-11-07 12.58.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199911/8c8a48f5-b633-264b-bc0f-485afc0cb140.png">

## PrismaサーバーとBFFとの紐付け
最後に、DBを直接操作するPrismaサーバーと、BFF（外部向けに公開するGraphQL APIサーバー）との紐付けを行い、公開するGraphQL APIを通じてPrismaサーバーのGraphQL APIを実行し、DBに対して操作できるようにします。

### Prismaサーバーとの紐付け用設定ファイルの作成
プロジェクトのルートに移動し、`.graphqlconfig.yml`を次のように作成します。

```sh
touch .graphqlconfig.yml 
```

```.graphqlconfig.yml
projects:
  database:
    schemaPath: src/generated/prisma.graphql
    extensions:
      prisma: database/prisma.yml
  app:
    schemaPath: src/schema.graphql
    extensions:
      endpoints:
        default: http://localhost:4000

```

### Prismaのスキーマファイルの作成
次のコマンドを実行して、上記Yamlのprojects.database.schemaPathで指定されているパスに、スキーマファイルを生成します。

```sh
graphql get-schema
```

### prisma-bindingのインストール
紐付けに必要なライブラリ[prisma-binding](https://github.com/prisma/prisma-binding)をインストールします。

```sh
yarn add prisma-binding
```

### src/index.tsのアップデート

インストールした`prisma-binding`の`Prisma`クラスを使って、上記で作成した`src/generated/prisma.graphql`を利用するように、src/index.tsを書き換えます。

```src/index.ts
import { GraphQLServer } from 'graphql-yoga';
import { Prisma } from 'prisma-binding';
import { IResolvers } from 'graphql-middleware/dist/types';

const resolvers: IResolvers = {
  Query: {
    posts(parent, args, ctx, info) {
      return ctx.db.query.posts({}, info);
    },
    post(parent, args, ctx, info) {
      return ctx.db.query.post({ where: { id: args.id } }, info);
    },
  },
  Mutation: {
    createDraft(parent, { title, content }, ctx, info) {
      return ctx.db.mutation.createPost(
        {
          data: {
            title,
            content,
          },
        },
        info,
      );
    },
    deletePost(parent, { id }, ctx, info) {
      return ctx.db.mutation.deletePost({ where: { id } }, info);
    },
    publish(parent, { id }, ctx, info) {
      return ctx.db.mutation.updatePost(
        {
          where: { id },
          data: { published: true },
        },
        info,
      );
    },
  },
};

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  resolvers,
  context: req => ({
    ...req,
    db: new Prisma({
      typeDefs: 'src/generated/prisma.graphql', // the generated Prisma DB schema
      endpoint: 'http://localhost:4466', // the endpoint of the Prisma DB service
      // secret: "mysecret123", // specified in database/prisma.yml
      debug: true, // log all GraphQL queries & mutations
    }),
  }),
});

server.start(() => console.log('Server is running on http://localhost:4000'));

```

### src/schema.graphqlのアップデート
src/schema.graphqlについても、`src/generated/prisma.graphql`で定義されている`Post`の型を利用できるよう、次のように書き換えます。

```src/schema.graphql
# import Post from "./generated/prisma.graphql"

type Query {
  posts: [Post!]!
  post(id: ID!): Post
  description: String!
}

type Mutation {
  createDraft(title: String!, content: String): Post
  deletePost(id: ID!): Post
  publish(id: ID!): Post
}

```

## さいごに
結構なボリュームのチュートリアルでしたが、手を動かして使ってみると慣れてきて、Prismaの有り難さが分かります。
下記の記事によると、Prismaを使えばN+1問題も解決してくれるそうなので、実戦でも使えるようにしていきたいです。
[Do Prisma resolve N+1 problem?](https://www.prisma.io/forum/t/do-prisma-resolve-n-1-problem/2792)

### ソースコード
今回作成したサンプルは下記のGitHubから手に入ります。
https://github.com/galoi/prisma-yoga-mysql-typescript-sample

### その他、参考にさせていただいた記事
- [GraphQLの旅(2) Prismaで開発したGraphQL APIを公開](https://qiita.com/Hiroyuki_OSAKI/items/cabc086cac06787be18e)