こんにちは、大阪事業所の北川です。
前回はGraphQLとはどういうものなのか、ということで概要と特徴についてご紹介させていただきました。
そして今回は実践編として、実際にGraphQLを使用してデータベースから値を取得、返却するAPIを作成していきましょう。
前回の記事はこちら
- 2020/6 GraphQLを使ってみた - 概要編:https://www.cview.co.jp/cvcblog/2020.06.29.FAUq5bQMn
事前準備
本件は環境構築にDockerを利用いたします。
そのため、事前にDockerのインストールを行ってください。
また、複数のコンテナを稼働させますので、Docker Composeを利用いたします。
ただし、Dockerのインストール方法や使用方法などは省かせていただきますので、必要な方は下記の記事をご参照ください。
- 2018/8 Docker入門:https://www.cview.co.jp/cvcblog/2018.08.21.170409.html
- 2018/9 DockerでRails環境構築:https://www.cview.co.jp/cvcblog/2018.09.25.103133.html
構成
今回構築する環境の最終構成は下記の図の通りとなります。
Node.jsのフレームワークであるExpressでサーバーを構成し、そこでApolloというライブラリを使用してGraphQLを扱えるよう調整します。
ここからPostgreSQLに接続し、データベース内のデータを取得して返却を行えるようにいたします。
Adminerは、PostgreSQLへのデータ登録を行うための管理ツールとなります。
データベースの準備
Dockerにてデータベースを稼働させます。
データベースについてはPostgreSQLを使用し、データベースを操作するツールとしてadminerというものを利用いたします。
どちらも、DockerHubにて提供されているものがありますので、そのまま利用いたします。
- PostgreSQL:https://hub.docker.com/_/postgres
- adminer:https://hub.docker.com/_/adminer
Dockerを準備
PostgreSQL, Adminerを動作させるためのDockerを作成いたします。
1. PostgreSQLのDockerを追加
- ディレクトリを作成
mkdir -p graphql_sample/postgresql/build mkdir -p graphql_sample/postgresql/env
- Dockerfileを作成
# postgresql/build/Dockerfile FROM postgres:12
- envを作成
# postgresql/env/.env POSTGRES_PASSWORD=password PGDATA=/var/lib/postgresql/data
2. adminerのDockerを追加
- ディレクトリを作成
mkdir -p graphql_sample/adminer/build mkdir -p graphql_sample/adminer/env
- Dockerfileを作成
# adminer/build/Dockerfile FROM adminer:4.7.6
- envを作成
# adminer/env/.env POSTGRES_PASSWORD=password
3.Docker Composeを作成
- docker-compose.ymlを作成
※それぞれのDockerfileにはDockerhubにて公開されているイメージを定義しているだけとなります。そのため、Dockerfileは作成せずにdocker-compose.ymlに直接イメージの定義を行うだけでも問題ありません。# graphql_sample/docker-compose.yml version: '3.7' services: # PostgreSQL graphqlsample_postgresql: build: context: ./postgresql/ dockerfile: build/Dockerfile image: graphqlsample_postgresql container_name: graphql_sample_postgresql env_file: - ./postgresql/env/.env volumes: - ./postgresql/postgresql_data:/var/lib/postgresql/data # adminer graphqlsample_adminer: build: context: ./adminer/ dockerfile: build/Dockerfile image: graphqlsample_adminer container_name: graphqlsample_adminer env_file: - ./adminer/env/.env ports: - "8001:8080" # Adminerポート
構成確認
現段階でのディレクトリ構成はこのようになります。
動作確認
PostgreSQLとadminerが、それぞれ問題なく動作しているか確認していきます。
1. 起動/サーバーにログイン
- docker-compose.ymlと同階層に移動する
- docker-compose コマンドでDockerを起動する
docker-compose up --build -d docker-compose exec graphqlsample_graphql bash
2. adminerにログイン
- localhost:8001にアクセスしてadminerのログイン画面を表示
- 表示されたログイン画面でDBのログイン情報を入力し、PostgreSQLにアクセス
データベース種類 PostgreSQL サーバー graphqlsample_postgres
※docker-composeにて定義したサービス名をホスト名として利用可能ユーザ名 postgres
※PostgreSQLのデフォルトのユーザー名パスワード password
※postgresql/env/.env にて定義したパスワードを利用データベース postgres
※PostgreSQLのデフォルトのデータベース
3. サンプルデータを作成
-
テーブルを作成
-
テーブル定義を入力して保存
テーブル名 Books 列名 型 長さ id integer (未指定) title character varying 100 author character varying 100 price integer (未指定) -
作成したテーブルにデータを追加
-
2件のデータをそれぞれ入力して保存
1件目 id 1 title title1 author author1 price 100
2件目 | |
---|---|
id | 2 |
title | title2 |
author | author2 |
price | 200 |
Expressの起動
GraphQLのサーバーライブラリはApolloを使用いたします。
他にはGraphQLの開発元であるFacebook社が用意したRelayがありますが、Web上での情報量が多いApolloといたしました。
このApolloをNode.jsのExpressで動作させるため、まずはDockerとExpressの起動までを行います。
- Apollo:https://www.apollographql.com/
- Relay:https://relay.dev/
Dockerを準備
1. GraphQL用のDockerを追加
- ディレクトリを作成
mkdir -p graphql_sample/graphql/build mkdir -p graphql_sample/graphql/env
- Dockerfileを作成
# graphql/build/Dockerfile FROM amazonlinux:2
yumのアップデート+キャッシュ削除
RUN yum -y update && yum clean all
RUN yum -y groupinstall "Development Tools"
RUN useradd express -md /home/express &&
echo 'express:express' | chpasswd &&
echo 'express ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN yum -y install
sudo
kernel-devel
kernel-headers
libyaml-devel
libffi-devel
tk-devel
zip
wget
tar
zlib
zlib-devel
bzip2
bzip2-devel
readline
readline-devel
openssl
openssl-devel
gdbm-devel
procps
nodejs install
RUN curl -sL https://rpm.nodesource.com/setup_12.x | bash -
RUN yum -y install nodejs
change user
USER express
ENV GOPATH="/home/express"
PATH="$PATH:/home/express/bin"
HOME="/home/express"
WORKDIR /home/express/app
3. 空のenvを作成
graphql/env/.env
**2. Docker Composeを作成**
1. docker-compose.ymlの最下部に追記
graphql_sample/docker-compose.yml
GraphQL
graphqlsample_graphql:
build:
context: ./graphql/
dockerfile: build/Dockerfile
image: graphqlsample_graphql
container_name: graphqlsample_graphql
env_file:
- ./graphql/env/.env
ports: - "3000:3000"
volumes: - "./graphql/app:/home/express/app"
tty: true
Dockerfile追加後の構成確認
現段階でのディレクトリ構成はこのようになります。
Express導入
GraphQLサーバーはNode.jsのフレームワークであるExpress上で稼働させますので、Expressを導入します。
1. Docker起動/サーバーにログイン
- docker-compose.ymlと同階層に移動する
- docker-compose コマンドでDockerを起動する
docker-compose up --build -d docker-compose exec graphqlsample_graphql bash
2. Express Generatorをインストール
- スケルトンを作成するexpress-generatorをグローバルインストール
sudo npm install -g express-generator
3. スケルトンを作成
- スケルトンを作成(今回viewはejsを選択)
express --view ejs
- スケルトンと同時に作成されたpackage.json内のライブラリをインストール
npm install
Express導入後の構成確認
動作確認
1. Expressを確認
- Expressを起動
npm run start
- 任意のブラウザで localhost:3000 へアクセスする
- npm run start は Ctrl + c で終了することが可能
Apollo GraphQLの起動
それでは、導入したExpressにApolloを組み込んでいきましょう。
インストール
ExpressにApollo GraphQLを組み込む
- apollo-server-expressをインストール
npm install --save apollo-server-express
コード修正
app.jsでApolloを使用するようコードを修正
参考:https://www.npmjs.com/package/apollo-server-expressvar createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); // 追記 var { ApolloServer, gql } = require('apollo-server-express');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// 追記 ここから---
// スキーマ
const typeDefs = gql`
クエリ
type Query {
hello: String
}
`
// リゾルバ(スキーマ内のクエリと返却値の紐付け)
const resolvers = {
Query: {
hello: () => 'Hello world!'
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
// 追記 ここまで---
### 動作確認
1. 再度Expressを起動し直す
npm run start
2. 任意のブラウザで localhost:3000/graphql へアクセスする
3. 左側のテキストエリアにクエリを入力、真ん中の右向き三角をクリックすると右側にクエリ結果が表示される。
hello クエリには "Hello world!" の固定文字列を返すようにしているので下記の結果となります。
## スキーマとリゾルバの外部ファイル化
実際に開発していくことになると、クエリやリゾルバは肥大化していきます。
そのため、どちらも app.js に記載してしまうと app.js が肥大してしまい、コードの見通しが悪くなってしまいます。
そこで、 app.js 内に記載したスキーマとリゾルバを外部ファイル化してみましょう。
### インストール
1. graphql-tools をインストール
GraphQLが提供しているツールをインストールします。
このツールに、ファイルに書き出したGraphQL文字列を読み込んだり、リゾルバを設定するためのモジュールが提供されています。
npm install --save graphql-tools
### コード修正(スキーマ)
**1. スキーマを別ファイルに書き出し**
1. appディレクトリ直下に schema.graphql を作成する
2. 作成したファイルに typeDefs 変数に格納した文字列を記載する
schema.graphql
クエリ
type Query {
hello: String
}
**2. 書き出したスキーマを読み込み**
1. app.js 内で使用するモジュールを追加します
loadSchemaSync, GraphQLFileLoader: スキーマを読み込む際に使用します
app.js
var { ApolloServer, gql } = require('apollo-server-express');
// 追記
var { loadSchemaSync, GraphQLFileLoader, addResolversToSchema } = require('graphql-tools');
2. app.js の内容を変更
var app = express();
// 変更
// const typeDefs = gql`
// # クエリ
// type Query {
// hello: String
// }
// `
const typeDefs = loadSchemaSync(path.resolve(__dirname, './schema.graphql'), { loaders: [new GraphQLFileLoader()] });
// 変更なし
const resolvers = {
Query: {
hello: () => 'Hello world!'
}
};
// 追記
const schema = addResolversToSchema({
schema: typeDefs,
resolvers: resolvers
});
// 変更
// const server = new ApolloServer({ typeDefs, resolvers });
const server = new ApolloServer({ schema });
server.applyMiddleware({ app });
### 動作確認(スキーマ)
1. npm run start
で再度Expressを起動
2. 任意のブラウザで localhost:3000/graphql へアクセス
3. クエリが正常に動作することを確認する
### コード修正(リゾルバ)
**1. リゾルバを別ファイルに書き出し**
1. appディレクトリ直下に resolvers.js を作成する
2. 作成したファイルに resolvers に格納した内容を返却するモジュールを作成する
// resolvers.js
module.exports = {
Query: {
hello: () => 'Hello world!'
}
};
**2. 書き出したリゾルバを読み込み**
1. app.js の内容を変更
// const resolvers = {
// Query: {
// hello: () => 'Hello world!'
// }
// };
const resolvers = require('./resolvers')
### 動作確認(リゾルバ)
1. npm run start
で再度Expressを起動
2. 任意のブラウザで localhost:3000/graphql へアクセス
3. クエリが正常に動作することを確認する
## リファクタリング
typeDefs と schema の変数名についての境界が曖昧になっているので、正しい変数名に直しましょう。
変数名についてはGraphQL ApolloやGraphQL Toolの公式ドキュメントに沿った形で修正しています。
### コード修正
var app = express();
// typeDefs を schema とする
// const typeDefs = loadSchemaSync(path.resolve(dirname, './schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const schema = loadSchemaSync(path.resolve(dirname, './schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const resolvers = require('./resolvers')
// schema を schemaWithResolvers とする
// const schema = addResolversToSchema({
const schemaWithResolvers = addResolversToSchema({
// schema: typeDefs,
schema: schema,
resolvers: resolvers
});
// const server = new ApolloServer({ schema });
const server = new ApolloServer({ schema: schemaWithResolvers });
server.applyMiddleware({ app });
sequelize をインストール
## PostgreSQLと連携
最後に、ApolloとPostgreSQLを連携させてみましょう。
### インストール
**Node.jsでのDB接続用のパッケージを組み込む**
npm install --save pg sequelize
### コード修正
**1. GraphQLへRDB接続用の環境変数を追加**
graphql/env/.env
ホストに接続
RDB_DB_ENDPOINT=graphqlsample_postgresql
RDB_DB_PORT=5432
RDB_DB_DATABASE=postgres
RDB_DB_USER=postgres
RDB_DB_PASS=password
**2. Expressのソース改修**
1. ディレクトリを作成
mkdir -p graphql_sample/graphql/app/db
mkdir -p graphql_sample/graphql/app/models
mkdir -p graphql_sample/graphql/app/queries/book
2. DB接続用のクラスを作成
app/db/sequelize.js
const Sequelize = require('sequelize');
const sequelize = new Sequelize(
process.env.RDB_DB_DATABASE,
process.env.RDB_DB_USER,
process.env.RDB_DB_PASS,
{
// 接続先ホストを指定
host: process.env.RDB_DB_ENDPOINT,
port: process.env.RDB_DB_PORT,
// logging: console.log,
maxConcurrentQueries: 100,
// 使用する DB 製品を指定
dialect: 'postgres',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000,
acquire: 20000
}
}
);
module.exports = sequelize;
3. モデルを追加
// graphql_sample/graphql/app/models/Book.js
const Sequelize = require('sequelize');
const sequelize = require('../db/sequelize');
/**
-
Books テーブルの Entity モデル
*/
const Book = sequelize.define('Books', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: Sequelize.STRING
},
author: {
type: Sequelize.STRING
},
price: {
type: Sequelize.INTEGER
}
}, {
// タイムスタンプの属性 (updatedAt, createdAt) が不要ならば次のプロパティは false
timestamps: false,// テーブル名を変更したくない場合は次のプロパティを true
// デフォルトでは sequelize はテーブル名を複数形に変更する
freezeTableName: true
});
module.exports = Book;
4. クエリを作成
// graphql_sample/graphql/app/queries/book/findBookById.js
const Book = require('../../models/Book')
const findBookById = async (obj, args, context, info) => {
const id = args.id
const book = await Book.findByPk(id, { raw: true });
if (!book) {
return null
}
return book
}
module.exports = findBookById
5. スキーマを更新
追加したBookとfindBookByIdの定義を行います。
schema.graphql
追加 Bookの定義
type Book {
id: ID,
title: String,
author: String,
price: Int
}
type Query {
hello: String,
追加 単一のBookを取得するクエリ定義
findBookById(id: ID): Book
}
6. リゾルバを更新
スキーマ定義と追加したクエリ findBookById の関連付けを行います。
// resolvers.js
const findBookById = require('./queries/book/findBookById')
module.exports = {
Query: {
hello: () => 'Hello world!',
findBookById: findBookById,
}
};
### 動作確認
1. 環境変数を追加したので、一度Dockerの再起動を行う
docker-compose up --build -d
2. npm run start
で再度Expressを起動
3. 任意のブラウザで localhost:3000/graphql へアクセス
4. クエリが正常に動作することを確認する
5. 存在しないデータを検索
6. AdminerでPostgreSQLにデータを追加して上記と同様のデータを検索
---
## まとめ
今回はGraphQLの実践編として、Docker上でのApollo GraphQLのインストールから動作確認、加えて若干のソース修正を加えてスキーマとリゾルバの外部ファイル化までを行いました。
下準備までは少し時間がかかりますが、ここまで作成してしまえばあとはクエリを記載してモデルにてDBからのデータ取得を追加、リゾルバにてクエリとモデルの関連付けを行うだけでAPIを拡張することができます。
また、モデルについてはクエリに沿った結果さえ返すことが出来ればどのような処理でも記載できます。
そのため、RDBだけではなくその他のデータストレージとの連携も可能となります。
実際に案件にて使用した感想としましては
- 入力値のチェックについては、Apolloがスキーマ通りに行ってくれるので基本的には考慮する必要がない
- ログ出力やエラー処理も全てApollo側にて一括で調整可能(別途調整が必要)
- 開発者はデータの取得から返却までの処理だけを考慮して開発するのみ
という感じでしょうか。
GraphQLのスキーマさえ決めてしまえば概要編で書かせていただいた通りで、考慮する点が最小で済むために追加や改修が非常に容易に行うことができました。
GraphQL(言語)というよりもApollo(ライブラリ)寄りの感想ですが...。
ログの出力やエラー処理などの共通処理については、実運用で使用する場合は事前にしっかりと調整することが必要になりますが、ログの出力タイミングや出力内容についてもApollo側がフォローしてくれる形となりますので、従来のAPIに比べると比較的楽に調整することが出来ました。
機会があれば、皆さんも一度GraphQLを用いたアプリケーションの作成を行ってみてはいかがでしょうか。
※今回はGraphQLの実装を体験するということに主軸を置いておりますので、ログの出力などについては一切考慮しておりません。
実際のアプリケーションを作成する際はリクエストとレスポンスのログ情報が重要になるかと思いますので、別途ご検討ください。
## 参考
### ブログ内リンク
- 2018/8 Docker入門:https://www.cview.co.jp/cvcblog/2018.08.21.170409.html
- 2018/9 DockerでRails環境構築:https://www.cview.co.jp/cvcblog/2018.09.25.103133.html
### 外部リンク
- PostgreSQL: https://hub.docker.com/_/postgres
- adminer: https://hub.docker.com/_/adminer
- Apollo: https://www.apollographql.com/
- Relay: https://relay.dev/
- npm apollo-server-express: https://www.npmjs.com/package/apollo-server-express