はじめに
タイトルの通り、RelationalDatabase(MySQL)でもGraphQL使いたいなぁってなったので普段使ってないけどNodeで書いてみた。
!!ただし読み取りのみ!!
だって書込みは普通にORMのメソッド呼んだ方が楽だから。
環境
- MySQL 5.7
- Node 10.11
- NPM 6.4
成果物 https://github.com/lightstaff/graphql-with-sequelize
準備
適当にnpm init
等でプロジェクトを作って必要なライブラリをインストールします。
$: 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を使用しますが、公式見ながら初めて使ってるレベルなので詳細な解説はしません。詳しいことははSequelizeの公式ドキュメントか公式EXAMPLEを見てください。
models
とディレクトリを作って、その中にUser
とEMail
というモデルを作ります。
User
○--< EMail
的な関係です。
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;
};
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;
};
これらにアクセスできるようにデータベースも定義します。
const Sequelize = require('sequelize');
const sequelize = new Sequelize('test', 'root', '1234', {
dialect: 'mysql',
host: 'localhost',
port: 3306,
});
module.exports = sequelize;
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を利用することでお手軽に構築できるみたいです。
非常に単純なので先にソースから載せてしまいます。
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()
はエントリポイントです。サーバー起動前にデータベースの初期化とデータの追加を行っています。
実行
$ node server.js
で実行です。
問題なければ上記のようにGraphQLが構築できているはずです。
おわりに
割と簡単にできて良かったと思います。