はじめに
僕がGraphQLに取り掛かった際なんとなく敷居の高さを感じていました😓
「自分がもしもう一度GraphQLを1から勉強するならこんな記事あったら嬉しいな」という観点で、少ないコード量で動作するシンプルなGraphQLアプリを作成しました。
20分以内で出来るハンズオンだと思いますが、お忙しいかたは「ファイルを追加 -> サーバー」だけでも見ていっていただけると🙏
プロダクトでGraphQLを使う場合はもっと保守性が高いディレクトリ構造にしたり、N+1対策をしたりと考えることが色々あると思いますが、今回そこらへんはノータッチです。
開発環境と使用した技術
- OS: macOS Catalina 10.15.4
- サーバー: Express.js
- ORM: Objection.js(内部でKnex.jsを利用してる)
- DB: MySQL8系
- ツール
- node: 16.8.0
- yarn: 1.22.17
- docker: 20.10.7
- docker-compose: 1.29.2
ディレクトリ構造
.
├── graphql
│ └── user.gql
├── migrations
│ └── 20211225000000_users.js
├── models
│ └── User.js
├── seeds
│ └── users.js
├── docker-compose.yml
├── knexfile.js
├── package.json
├── server.js
└── yarn.lock
ハンズオン
環境構築
package.jsonを作成した後yarn install
する
{
"version": "1.0.0",
"dependencies": {
"express": "^4.17.1",
"express-graphql": "^0.12.0",
"graphql": "^15.7.2",
"knex": "^0.95.14",
"mysql2": "^2.3.3",
"objection": "^3.0.0",
"nodemon": "^2.0.15"
},
"scripts": {
"dev": "nodemon server.js"
}
}
docker-compose.ymlを作成した後、docker-compose up
してmysqlサーバーをたてる
version: '3.8'
services:
my-db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: password
ports:
- '3306:3306'
command: --default-authentication-plugin=mysql_native_password
volumes:
- ./mysql:/var/lib/mysql
お好みの方法でmy-dbコンテナ内にdev_dbというDBを作成する
例: DBコンテナにexecした後MySQLにログインし、SQL叩いてDBを作成
docker-compose exec my-db bash
root@1234567890:/#
root@1234567890:/# mysql -u root -p
Enter password:
〜 docker-compose.ymlで設定したMYSQL_ROOT_PASSWORDの値を入力する 〜
Welcome to the MySQL monitor. Commands end with ; or \g.
〜 省略 〜
mysql>
mysql> CREATE DATABASE dev_db;
Query OK, 1 row affected (0.05 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| dev_db |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.01 sec)
mysql> exit
Bye
root@123456789:/# exit
knexでテーブルとダミーデータを作成する
knexfile.jsを追加し、dbの接続情報を記載する
module.exports = {
development: {
client: 'mysql2',
connection: {
database: "dev_db",
user: "root",
password: "password",
}
}
};
yarn knex migrate:make users
コマンド叩くと migrations/〇〇_users.js が生成される
生成されたmigrationファイルを書き換える
exports.up = function(knex) {
return knex.schema.createTable('users', t => {
t.increments('id')
t.string('name')
t.integer('age')
})
};
exports.down = function(knex) {
return knex.schema.dropTable('users')
};
yarn knex migrate:latest
する
yarn knex seed:make users
してseedファイルを作る
生成されたseedファイルを書き換える
exports.seed = function(knex) {
// Deletes ALL existing entries
return knex('users').del()
.then(function () {
// Inserts seed entries
return knex('users').insert([
{id: 1, name: 'Catherine', age: 4},
{id: 2, name: 'Mike' , age: 8},
{id: 3, name: 'Gonzales' , age: 16},
{id: 4, name: 'Nancy' , age: 32},
{id: 5, name: 'Daniel' , age: 64},
]);
});
};
yarn knex seed:run
してseedを実行する
ファイルを追加
- サーバー: server.js
- 参考サイト: express-graphql
const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");
const fs = require('fs');
const schema = buildSchema(fs.readFileSync('./graphql/user.gql', 'utf8'));
const User = require('./models/User');
const readUsers = async () => {
const users = await User.query().select();
return users;
};
const createUser = async ({name, age}) => {
const user = await User.query().insert({name: name, age: age});
return user;
};
const updateUser = async ({id, name, age}) => {
await User.query().findById(id).update({name: name, age: age});
return User.query().findById(id);
};
const deleteUser = async ({id}) => {
await User.query().findById(id).delete();
};
const root = {
readUsers: readUsers,
createUser: createUser,
updateUser: updateUser,
deleteUser: deleteUser,
};
const app = express();
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
app.listen(4000, () =>
console.log("Running a GraphQL API server at localhost:4000/graphql")
);
- GraphQLの定義ファイル: graphql/user.gql
type Query {
readUsers: [User]
},
type Mutation {
createUser(name: String!, age: Int): User
updateUser(id: Int!, name: String, age: Int): User
deleteUser(id: Int!): User
}
type User{
id: Int
name: String
age: Int
}
- Objection.jsのmodelファイル: models/User.js
const { Model } = require('objection');
const knex = require('knex');
const KnexConfig = require('../knexfile');
Model.knex(knex(KnexConfig.development));
class User extends Model {
static get tableName() {
return 'users';
}
}
module.exports = User;
-
yarn dev
コマンドで起動
GraphiQLサイトでデータ取得してみる
例: createUserを実行する場合(左上の再生マークでクエリ実行)
query readUsers {
readUsers {
id
name
age
}
}
mutation createUser($name: String!, $age: Int) {
createUser(name: $name, age: $age) {
... userFields
}
}
fragment userFields on User {
name
age
}
# 以下はQUERY VARIABLESに入力
{
"name": "Johnny",
"age": 1
}
mutation updateUser($id: Int!, $name: String!, $age: Int) {
updateUser(id: $id, name: $name, age: $age) {
... userFields
}
}
fragment userFields on User {
id
name
age
}
# 以下はQUERY VARIABLESに入力
{
"id": 3,
"name": "Akira",
"age": 2
}
mutation deleteUser($id: Int!) {
deleteUser(id: $id) {
... userFields
}
}
fragment userFields on User {
name
age
}
# 以下はQUERY VARIABLESに入力
{
"id": 5
}
おわりに
簡単な機能しかありませんが、GraphQLに触れるきっかけになれば幸いです。
最後まで読んでいただきありがとうございました!
もしよければ弊社アドベントカレンダーの他記事もみていってくださいー🙋♂️