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

# はじめに
僕が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```する

```json: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を作成する



<details><summary>例: DBコンテナにexecした後MySQLにログインし、SQL叩いてDBを作成</summary><div>

```
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
```

</div></details>



### knexでテーブルとダミーデータを作成する

knexfile.jsを追加し、dbの接続情報を記載する

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

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

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

```js: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ファイルを書き換える

```js: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
  - 参考サイト: <a href="https://graphql.org/graphql-js/express-graphql/">express-graphql</a>

```js: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

```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

```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;
```

- ```yarn dev```コマンドで起動

- http://localhost:4000/graphql にアクセス

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

例: createUserを実行する場合（左上の再生マークでクエリ実行）
![スクリーンショット 2021-12-22 5.47.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605440/2099b2d3-57ee-fc7f-4c31-5b7a68f2d0e8.png)



```GraphQL:read
query readUsers {
  readUsers {
    id
    name
    age
  }
}
```

```GraphQL: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
}
```

```GraphQL: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
}
```

```GraphQL:delete
mutation deleteUser($id: Int!) {
  deleteUser(id: $id) {
    ... userFields 
  }
}
fragment userFields on User {
 name
 age
}

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

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

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

https://qiita.com/advent-calendar/2021/ca-advance