この記事は Hamee Advent Calendar 2019 21日目の記事です。
先日、とある管理画面を実装後に GraphQL で実装すれば良かったなーと思う事があり、改めて GraphQL に興味が沸き前々から触ってみようと思っていたので、実際に触ってみました。
本当は簡単なTodoアプリを作る予定だったのですが、GraphQLサーバーの触りだけをまとめる感じになってしまいました ( ˆ꒳ˆ; )
ライブラリの紹介
-
apollographql/apollo-server: GraphQLサーバー
- 実際にGraphQLのリクエストを受け付けてデータを返すサーバー
- この記事ではラップされた apollo-server-express を利用していきます。
-
prisma/prisma: ORM
- GraphQLサーバーとデータベースを繋ぐORM
- prisma自体はライブラリではなくCLIツール
プロジェクトのセットアップ
$ yarn init -y
$ yarn add typescript
$ yarn add -D ts-node
$ yarn tsc --init
apollo server をインストール
$ yarn add apollo-server-express express @types/express
Hello World
実装
$ mkdir src
$ touch src/index.ts
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => 'Hello GrapQL!',
},
};
const server = new ApolloServer( { typeDefs, resolvers });
const app = express();
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () => {
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
});
実行する
package.json に実行スクリプトを追加して実行します。
"scripts": {
"dev": "ts-node src/index.ts"
}
$ yarn dev
http://localhost:4000/graphql にアクセスして、次のクエリを実行すれば結果が返ってきます。
query {
hello
}
GraphQLのインターフェースを実装してみる
次に簡単なTodoを取得するGraphQLのQueryを自分で実装してみます。
スキーマを定義
Todoオブジェクトの型定義とそれを取得する Query の型定義を書いていきます。
※ 型定義という言い方が正しいのか不明
const typeDefs = gql`
type Todo {
id: Int!
title: String
}
type Query {
todo(id: Int): Todo
todoes(title: String): [Todo]
}
`;
リゾルバを実装
リゾルバとは実際にリクエストを受け取りスキーマ定義に合わせてデータを返す部分です。
仮で変数を定義して値を返してみます。
const todoes = [
{
id: 1,
title: '簡単なタスク',
},
{
id: 2,
title: '気持ち簡単なタスク',
},
{
id: 3,
title: 'ヤバいタスク',
},
{
id: 4,
title: '気づかないフリをしているタスク',
}
]
const resolvers = {
Query: {
todo: (_: any, { id }: { id: number}) => {
return todoes.find(todo => todo.id === id);
},
todoes: (_: any, { title }: { title: string }) => {
return todoes.filter(todo => todo.title.includes(title))
},
},
};
実行する
それではサーバーを起動して、ここまでの変更を確認してみます。
$ yarn dev
ts-node src/index.ts
🚀 Server ready at http://localhost:4000/graphql
おー!無事に結果が取得できています。(*゚▽゚ノノ゙☆パチパチ
何か新鮮な体験で凄い楽しいですね!
DBと連携する
変数のデータを取得するだけだと味気ないので、折角なら実践的にDBにあるデータを取得するようにしましょう。
prismaを導入
prisma/prisma とは GraphQLサーバー と データーベース を繋ぐ ORM です。
モデルの定義を書けば、テーブルのマイグレーションやデータベースへアクセスするためのコードを自動生成をしてくれます。
※ この記事だとTypeORM とかを利用する場合と大差がなく、メリットを全く享受出来ていない気がしてます...
prisma自体はライブラリではなく、CLIツールなので開発用パッケージとしてインストールします。
$ yarn add -D prisma
データベースとprismaのセットアップ
ローカルで開発するだけであれば、initコマンドを実行するだけでデータベースのDocker環境をスキャフォールドしてくれます。
今回の設定は Create new database
、MySQL
、Prisma TypeScript Client
を選択しました。
$ yarn prisma init .
yarn run v1.19.1
$ /Users/tomohiro/workspace/graphql/graphql-todo-app/node_modules/.bin/prisma init .
? Set up a new Prisma server or deploy to an existing server? Create new database
? What kind of database do you want to deploy to? MySQL
? Select the programming language for the generated Prisma client Prisma TypeScript Client
Created 3 new files:
prisma.yml Prisma service definition
datamodel.prisma GraphQL SDL-based datamodel (foundation for database)
docker-compose.yml Docker configuration file
Next steps:
1. Open folder: cd .
2. Start your Prisma server: docker-compose up -d
3. Deploy your Prisma service: prisma deploy
4. Read more about Prisma server:
http://bit.ly/prisma-server-overview
Generating schema... 17ms
Saving Prisma Client (TypeScript) at /Users/tomohiro/workspace/graphql/graphql-todo-app/generated/prisma-client/
Dockerの起動
Prismaサーバーを起動するために、自動生成された docker-compose.yml から Dockerコンテナを起動します。
デフォルトではデータベースコンテナのポートはホスト側に紐づけられていないので、DBにアクセスするために次の箇所をコメントアウトしておきます。
# Uncomment the next two lines to connect to your your database from outside the Docker environment, e.g. using a database GUI like Workbench
ports:
- "3306:3306"
$ docker-compose up -d
データモデルの定義
データモデルを定義していきます。
prismaはこのモデル定義をDBにテーブルとしてマイグレーションしてくれます。
type Todo {
id: Int! @id(strategy: AUTO)
title: String!
}
マイグレーションの実行
マイグレーションを実行する前に prisma.yml を変更して、クライアントコードの生成場所とhooksの設定をしておきます。
generate:
- generator: typescript-client
output: ./src/generated/prisma-client/ # 生成先を src/ に変更
# マイグレーション実行のタイミングでクライアントコードの生成も実行する
hooks:
post-deploy:
- prisma generate
ではマイグレーションを実行します。
$ yarn prisma deploy
yarn run v1.19.1
$ /Users/tomohiro/workspace/graphql/graphql-todo-app/node_modules/.bin/prisma deploy
Creating stage default for service default ✔
Deploying service `default` to stage `default` to server `local` 1.3s
Changes:
Todo (Type)
+ Created type `Todo`
+ Created field `id` of type `Int!`
+ Created field `title` of type `String!`
...
実際にMySQLに接続すれば、default@default/Todo とテーブルが生成されているのが確認できます。
host: 127.0.0.1
user: root
password: prisma
テスト用にデータを追加しておきます。
INSERT INTO Todo(title) values
('簡単なタスク'),
('気持ち簡単なタスク'),
('ヤバいタスク'),
('気づかないフリをしているタスク');
DBのデータを返す実装に変更
クライアントライブラリが src/generated/prisma-client に自動生成されているので、DBアクセスはそれを利用します。
import { prisma } from './generated/prisma-client'
...
const resolvers = {
Query: {
todo: (_: any, { id }: { id: number}) => {
return prisma.todo({ id });
},
todoes: (_: any, { title }: { title: string }) => {
return prisma.todoes({ where: { title_contains: title }});
},
},
};
スキーマ定義を別ファイルに切り出す
テンプレートリテラルでスキーマ定義を書いていくのは見づらくメンテナンス性も悪そうなので、別ファイルに分割します。
graphql-import のインストール
Urigo/graphql-import はGraphQLのスキーマ定義ファイルの import を可能にしてくれるライブラリです。
$ yarn add graphql-import
スキーマ定義を分割
スキーマ定義を配置するディレクトリを新しく作ります。
$ mkdir src/schema
type Todo {
id: Int!
title: String
}
# import Todo from "todoes.graphql"
type Query {
todo(id: Int): Todo
todoes(title: String): [Todo]
}
スキーマ定義をimportする
スッキリしました。
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import { prisma } from './generated/prisma-client';
import { importSchema } from 'graphql-import';
const typeDefs = importSchema(`${__dirname}/schema/schema.graphql`);
const resolvers = {
Query: {
todo: (_: any, { id }: { id: number}) => {
return prisma.todo({ id });
},
todoes: (_: any, { title }: { title: string }) => {
return prisma.todoes({ where: { title_contains: title }});
},
},
};
const server = new ApolloServer( { typeDefs, resolvers });
おわりに
少し触った感じだと、実装の大枠は意外と REST API と変わらない感じがしました。
ただ、本格的に実装を始めたら色々と大きな違いは出てきそうだと思います。
今までモヤモヤしていたGraphQLのサーバー実装の概要が理解できて良かったです。
prisma-labs/graphqlgen というライブラリを使えばスキーマ定義からリゾルバの自動生成もできたり?と、prisma をちゃんと使えばスキーマ定義を起点にした新しい開発体験が出来ると期待しているので、もっと色々と試してみたいです。