LoginSignup
19
13

More than 1 year has passed since last update.

シンプルなGraphQLサーバーでCRUDする(Express.js, Objection.js, MySQL, Docker)

Last updated at Posted at 2021-12-21

はじめに

僕が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する

package.json
{
  "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サーバーをたてる

docker-compose.yml
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の接続情報を記載する

knexfile.js
module.exports = {
  development: {
    client: 'mysql2',
    connection: {
      database: "dev_db",
      user: "root",
      password: "password",
    }
  }
};

yarn knex migrate:make usersコマンド叩くと migrations/〇〇_users.js が生成される

生成されたmigrationファイルを書き換える

migrations/20211225000000_users.js
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ファイルを書き換える

seeds/users.js
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
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
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
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;

GraphiQLサイトでデータ取得してみる

例: createUserを実行する場合(左上の再生マークでクエリ実行)
スクリーンショット 2021-12-22 5.47.48.png

read
query readUsers {
  readUsers {
    id
    name
    age
  }
}
create
mutation createUser($name: String!, $age: Int) {
  createUser(name: $name, age: $age) {
    ... userFields 
  }
}
fragment userFields on User {
    name
    age
}

# 以下はQUERY VARIABLESに入力
{
 "name": "Johnny",
 "age": 1
}
update
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
}
delete
mutation deleteUser($id: Int!) {
  deleteUser(id: $id) {
    ... userFields 
  }
}
fragment userFields on User {
 name
 age
}

# 以下はQUERY VARIABLESに入力
{
 "id": 5
}

おわりに

簡単な機能しかありませんが、GraphQLに触れるきっかけになれば幸いです。
最後まで読んでいただきありがとうございました!

もしよければ弊社アドベントカレンダーの他記事もみていってくださいー🙋‍♂️

19
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13