Node.jsを用いて、ORMにSequelize, DBはRDS(MySQL)という構成でApollo Serverを用いて、GraphQL Serverを構成する【勉強メモ】

## 最初に
これはNode.js環境で, ORMにはSequelize, DBにはMySQLを使った構成で、Apollo Serverを用いてGraphQLサーバを構築してみた際の備忘録となります。

と言っても自身でイチから、これらの構成を構築していったわけではなく、下記のチュートリアルを参照しながらの勉強メモとなります。
(ちょうど同じ構成での例を探していたところ、丁寧に書かれていた下記のドキュメントを見つけました。ありがたい)

[How To Set Up a GraphQL Server in Node.js with Apollo Server and Sequelize](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-graphql-server-in-node-js-with-apollo-server-and-sequelize)

ちなみにこのドキュメントの中では、sqlite3を用いているので、そこはこちらでMySQLに置き換えて実践しています。
また細かなところで適宜アレンジを施しています。

大枠自体は変わらないので、この構成(`Node.js`, `Sequelize`などを `Apollo Server`と組み合わせる)に興味ある方は、直接ドキュメントを読まれることをおすすめします。
(というかこのポストを読み進めていく場合は、上の `DigitalOcean` のチュートリアル記事とセットで読んでいくことをおすすめします)

自身で実際に実装したコードはGithubに置いています。
[shinshin86/graphql-recipe-server](https://github.com/shinshin86/graphql-recipe-server)

## Sequelizeを用いたDB関連のセットアップ

まずはDB関連のセットアップから行っていきます。
(ここらへん `Apollo Server` というよりは `Sequelize` の基本的なセットアップの流れになります)

### インストール&初期化

必要なライブラリをインストール

```sh
yarn add sequelize sequelize-cli mysql2
```


Sequelize関連の初期化処理を実施

```sh
yarn sequelize init
```

### テーブルの作成

次に必要なmodelとmigrationを作成・実施していきます。

まずはUserテーブルから作成

```sh
yarn sequelize model:create --name User --attributes name:string,email:string,password:string
```

作成した項目は空の入力を許可しないようにするなどの設定を行います。
※ここについては参照先の記述( `Step 2 — Creating Models and Migrations` )を参照してください。


次にRecipeテーブル

```bash
yarn sequelize model:create --name Recipe --attributes title:string,ingredients:text,direction:text
```

こちらも同じ用にmodelとmigrationの内容を編集していきます。
また、ここで `userId` の追加も実施しています。
※ここについても参照先の記述( `Step 2 — Creating Models and Migrations` )を参照してください。


```js
        userId: {
          allowNull: false,
          type: Sequelize.INTEGER.UNSIGNED,
        },
```

ここにはレシピを作成したユーザIDが格納され、後々レシピを作成したユーザ情報を取得するために使われます。

### associateの設定

modelも編集したら、UserとRecipeでそれぞれassociateの設定を行っていきます。

```js
  // models/user.js
  User.associate = function(models) {
    User.hasMany(models.Recipe)
  };
```

```js
  // models/recipe.jsから抜粋
  Recipe.associate = function(models) {
    Recipe.belongsTo(models.User, { foreignKey: 'userId' })
  };
```

### DBに対する文字コード関連の設定を記述する

また、上記 `migration` と `models` にはDBに対する文字コードの設定も忘れないように記述します。
これを忘れると、日本語で入力した場合に文字コード関連でエラーになります。
(実はすっかり忘れていて、日本語を使ってエラーになったりしていました)


`migration`ファイルの場合、 `queryInterface.createTable` の第３引数に下記の内容をセットします。

```js
{
  charset: 'utf8',
  collate: 'utf8_general_ci',
}
```

また `model`ファイルの場合は、`sequelize.define` の第３引数に下記の記述をセットします。

```js
{
  charset: 'utf8',
  collate: 'utf8_general_ci',
}
```

### migrationの実施

すでにローカルにMySQLは立ち上がっているものとします。

Sequelizeはセットアップしたデフォルトの状態だと `password: null` でアクセスするようになっているかと思いますが、流石にそれは現実味がない気がしたので、一応形だけですが、

* `root` というユーザ名、
* `password` というパスワード

で接続するように `config/config.json` に記述しました。
なので、DB自体もそのような設定で動かしています。
(これも現実味ないといえばないですが...)

ちなみに自身はローカルで動くDocker上に、最新のMySQLを立ち上げて、そちらを使っていきます。


下記のコマンドでDBの作成・migrationを実施していきます

```sh
yarn sequelize db:create
yarn sequelize db:migrate
```


## GraphQL Serverを作成する

ここからが本番です。
必要なライブラリをインストールしていきます。

`apollo-server` は `graphql` に依存するため、こちらも併せてインストールしています。
また `bcryptjs`はユーザのパスワードをハッシュ化するために使用します。


```bash
yarn add apollo-server graphql bcryptjs
```

次に `src` ディレクトリを作成し、必要なファイルを作成していきます。

```sh
mkdir src
nv src/index.js
```

ソース自体は参照元の `Step 3 — Creating the GraphQL Server` を参照してもらうとして、下記の `context: { models }` でmodels側とのつなぎ込みを行っているようでした。

```js
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: { models }
})
```

ちなみにGraphQLには `Query`, `Mutations`, `Subscriptions` がありますが、このチュートリアルでは`Query`, `Mutations`に焦点が当てられています。

### schemaを作成していく(GraphQL)

次に `src/schema.js` を作成していきます。
記述を見ると、設定したassociateを反映させた構成になっているのが分かります。

```js
  type User {
    id: Int!
    name: String!
    email: String!
    recipes: [Recipe!]!
  }

  type Recipe {
    id: Int!
    title: String!
    ingredients: String!
    direction: String!
    user: User!
  }
```

queryは３つ設定されているようです。

* user IDを引数にしてユーザ情報を取得するもの
* すべてのレシピを取得するもの
* recipe IDを引数にしてレシピ情報を取得するもの

```js
  type Query {
    user(id: Int!): User
    allRecipes: [Recipe!]!
    recipe(id: Int!): Recipe
  }
```

`mutation` は２つ設定されています。

* `name, email, password` を引数にしてユーザを作成するもの
* `userId, title, ingredients, direction` を引数にしてレシピを作成するもの

```js
  type Mutation {
    createUser(name: String!, email: String!, password: String!): User!
    createRecipe(
      userId: Int!
      title: String!
      ingredients: String!
      direction: String!
    ): Recipe!
  }
```

### resolverを設定していく(GraphQL)

上に書いたqueryに対応する実際の処理がresolverには書かれています。
実際にどういうロジックが動くのかはこちらを見れば、大体イメージがつくかと思います。
`resolver`内に実際に書かれるロジックは、特に`GraphQL`的なものというのはそれほどなく、実際の取得ロジックなどが書かれる形となるので(今回で言えば、Sequelizeを用いたデータ作成・取得処理など)、結構すぐに馴染める印象でした。

例えば `MutationのcreateUser`の場合ならば、下記のように実装されています。

```js
async createUser(root, { name, email, password }, { models }) {
  return models.User.create({
    name,
    email,
    password: await bcrypt.hash(password, 10),
  });
},
```

#### Sequelize v5ではfindByIdではなくfindByPkとなる(余談)
ちなみに現時点で `sequelize` の最新のversionをインストールした場合、`sequelizeのv5系` がインストールされるかと思います。
`v5`ではsequelizeに実装されていた `findById`は廃止され `findByPk` に移行しています。
参照しているドキュメントでは `findById` で書かれているので、 ここは `findByPk` に書き換える必要があります。
(`sequelize v4`系をインストールした場合は書き換える必要はありません)


```diff
diff --git a/src/resolvers.js b/src/resolvers.js
index 7372e74..0cacc9b 100644
--- a/src/resolvers.js
+++ b/src/resolvers.js
@@ -3,13 +3,13 @@ const bcrypt = require('bcryptjs');
 const resolvers = {
   Query: {
     async user(root, { id }, { models }) {
-      return models.User.findById(id);
+      return models.User.findByPk(id);
     },
     async allRecipes(root, args, { models }) {
       return models.Recipe.findAll();
     },
     async recipe(root, { id }, { models }) {
-      return models.Recipe.findById(id);
+      return models.Recipe.findByPk(id);
     },
   },
   Mutation: {
```

#### ApolloServerのcontextについて

第3引数として `models` が渡っていますが、これは `src/index`でcontextに `models` を指定すると渡せるようです。

```js
const models = require('../models');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: { models },
});
```

例えば下記のようなコードを書いて、該当の箇所のコードを動かすと、 `{ hoge: 'hogehoge' }` がログとして出力されます。

```diff
diff --git a/src/index.js b/src/index.js
index e1049b7..133f156 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,7 +6,7 @@ const models = require('../models');
 const server = new ApolloServer({
   typeDefs,
   resolvers,
-  context: { models },
+  context: { models, hoge: 'hogehoge' },
 });

 server
diff --git a/src/resolvers.js b/src/resolvers.js
index 0cacc9b..3bc1d24 100644
--- a/src/resolvers.js
+++ b/src/resolvers.js
@@ -8,7 +8,8 @@ const resolvers = {
     async allRecipes(root, args, { models }) {
       return models.Recipe.findAll();
     },
-    async recipe(root, { id }, { models }) {
+    async recipe(root, { id }, { models, hoge }) {
+      console.log({hoge})
       return models.Recipe.findByPk(id);
     },
   },
```

他に書き残しとくべき箇所というのも、あまりないのですが、
下記の `user.getRecipes()`, `recipe.getUser()` などは `Sequelize` 側での処理になります。
`associate` で設定しているゆえに、こういう形で取得ができます。

```js
  User: {
    async recipes(user) {
      return user.getRecipes();
    },
  },
  Recipe: {
    async user(recipe) {
      return recipe.getUser();
    },
  },
```


これで、動かすための必要な実装はすべて完了です。


## Apollo Playgroundで実際に試してみる

下記コマンドでsevrerを起動します。

```bash
node src/index.js
# もしくは "yarn start"
```

http://localhost:4000/
にアクセスすると、親しみやすいApolloのPlaygroundが表示されます。

とりあえず`user`を作成して見ようと思います。



```graphql
mutation {
  createUser (
    name: "テストユーザ１"
    email: "text@example.com"
    password: "password"    
  ) {
    id
    name
    email
  }
}
```

すると下記のような反応が返ってきます。

```graphql
{
  "data": {
    "createUser": {
      "id": 1,
      "name": "テストユーザ１",
      "email": "text@example.com"
    }
  }
}
```

サーバのログを見ると、SQLが発行されているのも確認できます。

```sql
Executing (default): INSERT INTO `Users` (`id`,`name`,`email`,`password`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?,?);
```


次にレシピを作成します。
先ほど作成したテストユーザ１に紐づくレシピを作成します。



```graphql
mutation {
  createRecipe(
    userId: 1
    title: "サンプルレシピ１"
    ingredients: "Salt, Pepper"
    direction: "Add salt, Add pepper"
  ) {
    id
    title
    ingredients
    direction
    user {
      id
      name
      email
    }
  }
}
```

下記のようなレスポンスが返ります。

```graphql
{
  "data": {
    "createRecipe": {
      "id": 1,
      "title": "サンプルレシピ１",
      "ingredients": "Salt, Pepper",
      "direction": "Add salt, Add pepper",
      "user": {
        "id": 1,
        "name": "テストユーザ１",
        "email": "text@example.com"
      }
    }
  }
}
```

サーバのログには下記のようなSQLが発行されているのが確認できます。

```sql
INSERT INTO `Recipes` (`id`,`title`,`ingredients`,`direction`,`createdAt`,`updatedAt`,`userId`) VALUES (DEFAULT,?,?,?,?,?,?);
```

また、同時レスポンス時に必要となるSQLが発行されているのも分かります。

```sql
SELECT `id`, `name`, `email`, `password`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 1;
```

User作成時は `SELECT` は発行されていませんでしたが、今回は紐づくユーザ情報も返す必要があるため、`SELECT` クエリを発行する必要があったということかと想像します
(ソースはまだ読んでいません)


queryについてはあまりここに書かなくても、結構情報はある気がしたので、ざっくりと。

```graphql
query {
  allRecipes {
    id
    title
    user {
      name
    }
  }
}
```

ちなみに上のようなqueryを発行した場合、SQL的には下記のように返ってくるようです。

```sql
SELECT `id`, `title`, `ingredients`, `direction`, `createdAt`, `updatedAt`, `userId`, `UserId` FROM `Recipes` AS `Recipe`;
SELECT `id`, `name`, `email`, `password`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 1;
```

テーブルjoinして取得するような動きではありませんが、別にそういうオプションがあるのか、実装的にそういう形になっているのかは今後調べていくこととします。
結果は下記の通り。

```graphql
{
  "data": {
    "allRecipes": [
      {
        "id": 1,
        "title": "サンプルレシピ１",
        "user": {
          "name": "テストユーザ１"
        }
      }
    ]
  }
}
```


ざっくりとではありますが、以上勉強メモとなります。

## 参照
[How To Set Up a GraphQL Server in Node.js with Apollo Server and Sequelize](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-graphql-server-in-node-js-with-apollo-server-and-sequelize)

[Model | Sequelize(findByPk)](https://sequelize.org/master/class/lib/model.js~Model.html#static-method-findByPk)