RDBでGraphQL使いたい

# はじめに
タイトルの通り、RelationalDatabase（MySQL）でもGraphQL使いたいなぁってなったので普段使ってないけどNodeで書いてみた。

**！！ただし読み取りのみ！！**

だって書込みは普通にORMのメソッド呼んだ方が楽だから。



**環境**

- MySQL 5.7
- Node 10.11
- NPM 6.4


**成果物** https://github.com/lightstaff/graphql-with-sequelize

# 準備
適当に`npm init`等でプロジェクトを作って必要なライブラリをインストールします。

```console
$: npm install --save apollo-server dataloader-sequelize graphql mysql2 sequelize
```

- apollo-server...GraphQLサーバーを簡単に立ち上げてくれる。
- graphql...主役
- sequelize...ORMライブラリ
- dataloader-sequelize...N+1を解決してくれるらしい
- mysql2...MySQLドライバ

# RDB側
ORMに[Sequelize](https://github.com/sequelize/sequelize)を使用しますが、公式見ながら初めて使ってるレベルなので詳細な解説はしません。詳しいことははSequelizeの[公式ドキュメント](http://docs.sequelizejs.com/)か[公式EXAMPLE](https://github.com/sequelize/express-example)を見てください。

`models`とディレクトリを作って、その中に`User`と`EMail`というモデルを作ります。

`User` ○--< `EMail`的な関係です。

```js:models/user.js
module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define(
    'user',
    {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true,
      },
      name: DataTypes.STRING,
    },
    {
      timestamps: false,
      tableName: 'user',
    }
  );

  User.associate = models => {
    User.hasMany(models.email);
  };

  return User;
};
```

```js:models/email.js
module.exports = (sequelize, DataTypes) => {
  const EMail = sequelize.define(
    'email',
    {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true,
      },
      address: DataTypes.STRING,
    },
    {
      timestamps: false,
      tableName: 'email',
    }
  );

  EMail.associate = models => {
    EMail.belongsTo(models.user);
  };

  return EMail;
};
```

これらにアクセスできるようにデータベースも定義します。

```js:database.js
const Sequelize = require('sequelize');

const sequelize = new Sequelize('test', 'root', '1234', {
  dialect: 'mysql',
  host: 'localhost',
  port: 3306,
});

module.exports = sequelize;
```

```js:models/index.js
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');

const sequelize = require('../database');

const db = {
  sequelize,
  Sequelize,
};

fs.readdirSync(__dirname)
  .filter(file => path.extname(file) === '.js' && file !== 'index.js')
  .forEach(file => {
    const model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if ('associate' in db[modelName]) {
    db[modelName].associate(db);
  }
});

module.exports = db;
```

# GraphQL側
やっと本題に辿り着いた。

[ApolloServer](https://github.com/apollographql/apollo-server)を利用することでお手軽に構築できるみたいです。

非常に単純なので先にソースから載せてしまいます。

```js:server.js
const apolloServer = require('apollo-server');
const dataLoaderSequelize = require('dataloader-sequelize');

const db = require('./models');

const { ApolloServer, gql } = apolloServer;
const { createContext, EXPECTED_OPTIONS_KEY } = dataLoaderSequelize;

// GraphQL schema
const typeDefs = gql`
  type User {
    id: ID!
    name: String
    emails: [Email!]!
  }

  type Email {
    id: ID!
    address: String
    user: User!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }
`;

// GraphQL <-> ORM
const resolvers = {
  User: {
    emails: (parent, args, context, info) => parent.getEmails(),
  },
  Email: {
    user: (parent, args, context, info) => parent.getUser(),
  },
  Query: {
    users: (parent, args, { db, eok }, info) =>
      db.user.findAll({ [EXPECTED_OPTIONS_KEY]: eok }),
    user: (parent, { id }, { db, eok }, info) =>
      db.user.findById(id, { [EXPECTED_OPTIONS_KEY]: eok }),
  },
};

// Apollo server
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: {
    db: db,
    eok: createContext(db.sequelize),
  },
});

// Entry point
async function start() {
  await db.sequelize.sync({ force: true });

  // Setup dummy
  const userA = await db.user.create({ name: 'A' });
  const userB = await db.user.create({ name: 'B' });
  await userA.createEmail({ address: 'A1@e-mail.com' });
  await userA.createEmail({ address: 'A2@e-mail.com' });
  await userB.createEmail({ address: 'B1@e-mail.com' });

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

start();
```

非常に単純なので解説するほどでもないのですが・・・。

`const typeDefs = ...`でGraphQLのスキーマを定義しています。

`const resolvers = ...`でGraphQlのスキーマに対してデータベースを呼び出して解を定義しています。

`const server = ...`でApolloServerを作成し、`context`にデータベース等を与えています。

`async function start()`はエントリポイントです。サーバー起動前にデータベースの初期化とデータの追加を行っています。

# 実行
```console
$ node server.js
```

で実行です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/94252/57d2e6e2-2929-9886-65d7-e85d79c02245.png)

問題なければ上記のようにGraphQLが構築できているはずです。

# おわりに
割と簡単にできて良かったと思います。