はじめに
基本的にPrismaの公式チュートリアル Build a GraphQL server from Scratch に倣って、Prisma
、graphql-yoga
を使ってGraphQLサーバーを書いていきます。
本記事では上記チュートリアルとは少し異なるアプローチ
- 言語:TypeScript
- DB:ローカルに立てるDocker上のMySQL
でご紹介します。
チュートリアルを進める前に、Prisma
って何?という方は、
辺りを一読してPrismaをざっくりと理解し、公式の
を確認してみるのがオススメです。
対象読者
- GraphQLに関する知識をある程度有している方
- GraphQL APIを提供するサーバーを簡単に素早く作りたい方
- データベースは自前のMySQLを使いたい方
- 言語はTypeScriptで書きたい方
実行環境
- Mac OS Mojave: v10.14.2
- node: v11.2.0
- npm: v6.5.0
- yarn: v1.12.3
- docker: v17.09.1-ce
- prisma: v1.23.4
- graphql: v3.0.4
GraphQL APIサーバー(Backends For Frontends)構築
はじめに、外部に向けてGraphQL APIを提供するためのBFFを構築していきます。
後に、外部には公開しない内部向けのPrismaサーバーをラップするように構築します。
NPMプロジェクト用ディレクトリ作成〜初期化
mkdir prisma-yoga-mysql-typescript-sample
cd prisma-yoga-mysql-typescript-sample
npm init -y
src/index.tsの作成
mkdir src
touch src/index.ts
graphql-yoga
のインストール
yarn add graphql-yoga
nodemon
、ts-node
およびTypeScriptのインストール
ここでチュートリアルではsrc/index.jsにコードを書き込んで実行しますが、TypeScriptで同じことを実行するために、コンパイル無しでNode.js上で直接TSファイルを実行できるライブラリts-nodeと、ファイルを監視し、変更があればNodeのプロセスを再起動してくれるnodemonをインストールします。
yarn add -D nodemon ts-node typescript
package.jsonのアップデート
yarn start
でスクリプトを実行できるように、package.jsonのscripts
の値を次のように書き換えます。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --ext ts,yaml,graphql --exec 'ts-node' src/index.ts"
},
(拡張子がts
、yaml
、graphql
のファイルを監視対象とする)
src/index.tsへの新規書込
import { GraphQLServer } from 'graphql-yoga';
// GraphQL SDLに沿ってschemaを定義
const typeDefs = `
type Query {
description: String
}
`;
// GraphQL APIのリクエストに応えるための実装
const resolvers = {
Query: {
description: () => `This is the API for a simple blogging application`,
},
};
const server = new GraphQLServer({
typeDefs,
resolvers,
});
server.start(() =>
console.log(`The server is running on http://localhost:4000`),
);
ここまで保存した時点で、yarn start
をコマンドラインで実行すると、http://localhost:4000
でサーバーが立ち上がるはずです。
http://localhost:4000 にアクセスして、次のクエリを実行して結果が返ってくれば成功です。
{
description
}
src/schema.graphqlの作成
続けて、src/index.tsのtypeDefs
に代入したものとは別に、GraphQLスキーマをあらためて定義するために、次のようにschema.graphqlを作成します。
touch src/schema.graphql
type Query {
posts: [Post!]!
post(id: ID!): Post
description: String!
}
type Mutation {
createDraft(title: String!, content: String): Post
deletePost(id: ID!): Post
publish(id: ID!): Post
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
}
src/index.tsのアップデート
上記で作成したスキーマを読み込むために、src/index.tsを次のように書き換えます。
import { GraphQLServer } from 'graphql-yoga';
// GraphQL SDLに沿ってschemaを定義
// const typeDefs = `
// type Query {
// description: String
// }
// `;
// GraphQL APIのリクエストに応えるための実装
const resolvers = {
Query: {
description: () => `This is the API for a simple blogging application`,
},
};
const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
});
server.start(() =>
console.log(`The server is running on http://localhost:4000`),
);
それぞれ保存した後、nodemonによってプロセスが再起動された場合はそのまま http://localhost:4000 にアクセスして、schemaが次のように反映されていればOKです。
Prismaサーバー(内部向け)構築
ここからPrismaを使って、内部向けにサーバーを構築していきます。
これから作る内部向けGraphQL APIサーバーは、DBに対して直接クエリを実行します。
Prisma CLIのグローバルインストール
prisma
コマンドが未だ使えない場合は、グローバルインストールしてください。
(追記: 本記事はPrismaのバージョンが1.23.4であることを前提にしてありますので、CLIも1.23.4をインストールしないとうまく動きません)
npm install -g prisma@1.23.4
databaseディレクトリの作成
次のprisma
コマンドをプロジェクトのルートで実行すると
prisma init database
REPLで実装を選ぶことになりますので、Create new database
、MySQL
、Prisma TypeScript Client
をそれぞれ選択します。
(本家のチュートリアル Step 13とは別の選択を取っていますので、ご注意ください)
% prisma init database
? Set up a new Prisma server or deploy to an existing server?
Set up a new Prisma server for local development (based on docker-compose):
Use existing database Connect to existing database
❯ Create new database Set up a local database using Docker
Or deploy to an existing Prisma server:
Demo server Hosted demo environment incl. database (requires login)
Use other server Manually provide endpoint of a running Prisma server
? What kind of database do you want to deploy to? (Use arrow keys)
❯ MySQL MySQL compliant databases like MySQL or MariaDB
PostgreSQL PostgreSQL database
MongoDB Mongo Database
? Select the programming language for the generated Prisma client
❯ Prisma TypeScript Client
Prisma Flow Client
Prisma JavaScript Client
Prisma Go Client
Don't generate
コマンド実行後は、database
ディレクトリが作成され、次のような構成になっているはずです。
(本家チュートリアルではdatabase/datamodel.graphql
が作成されていることになっていますが、prisimaのバージョン1.23.4で実行すると、代わりにdatabase/datamodel.prisma
が作成されます)
├── database
│ ├── datamodel.prisma // DBのテーブル構造元となる型定義
│ ├── docker-compose.yml
│ ├── generated
│ │ └── prisma-client
│ │ ├── index.ts
│ │ └── prisma-schema.ts
│ └── prisma.yml // Prismaサーバーの設定ファイル
├── package.json
├── src
├── index.ts
└── schema.graphql
datamodel.prismaのアップデート
本家チュートリアルに沿って、次の内容でdatamodel.prismaを書き換えてください。
type Post {
id: ID! @unique
title: String!
content: String!
published: Boolean! @default(value: "false")
}
上記ではどんな定義になっているか、大体想像がつくかと思いますが、デプロイされるとPost
テーブルにマッピングされ、id
〜published
までのフィールドと呼ばれる部分は各カラムにマッピングされます。
!
はNot nullを指し、@unique
は値がテーブルの中で一意なフィールドとなるよう指定しています。
@default
は、レコードをインサートする際、published
の値指定が無い場合はfalse
が自動的に割り当てられる指定となっています。
ここで登場する@unique
や@default
などのディレクティブ
と呼ばれるものについて詳しく知りたい方は、GraphQLの公式などをご参照ください。
DB接続用ポートの設定
こちらの設定は任意ですが、DBに直接、接続したい場合は database/docker-compose.ymlのservices.mysql.portsの値が次のようになるよう、追記してください。
mysql:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: prisma
volumes:
- mysql:/var/lib/mysql
ports:
- "3333:3306"
Dockerコンテナの起動
Prismaをデプロイさせるには、DBに接続できる状態にする必要があります。
コマンドラインで、docker-compose.ymlが存在するdatabaseディレクトリに移動し、docker-compose up
を実行し、Dockerコンテナを起動してください。
cd database
docker-compose up
Prismaのデプロイ
上記でDockerコンテナを起動した後、別のコマンドラインで再度databaseディレクトリに移動し、prisma deploy
コマンドを実行して、database/prisma.ymlに記載されているendpoint: http://localhost:4466 に対してデプロイさせます。
cd database
prisma deploy
デプロイと同時に、DBに対してmigrationが実行されます。
デプロイ完了後、http://localhost:4466 にアクセスし、DOCS
を開くと、Post
テーブルに対するCRUDが実装されているのが分かります。
また、上記でDB接続用ポートの設定を行なっていた場合は、Sequer ProなどのDBクライアントツールで次のような設定でDBにアクセスできます。
(ユーザ名やパスワード、ポートはdocker-compose.ymlで変更可能)
PrismaサーバーとBFFとの紐付け
最後に、DBを直接操作するPrismaサーバーと、BFF(外部向けに公開するGraphQL APIサーバー)との紐付けを行い、公開するGraphQL APIを通じてPrismaサーバーのGraphQL APIを実行し、DBに対して操作できるようにします。
Prismaサーバーとの紐付け用設定ファイルの作成
プロジェクトのルートに移動し、.graphqlconfig.yml
を次のように作成します。
touch .graphqlconfig.yml
projects:
database:
schemaPath: src/generated/prisma.graphql
extensions:
prisma: database/prisma.yml
app:
schemaPath: src/schema.graphql
extensions:
endpoints:
default: http://localhost:4000
Prismaのスキーマファイルの作成
次のコマンドを実行して、上記Yamlのprojects.database.schemaPathで指定されているパスに、スキーマファイルを生成します。
graphql get-schema
prisma-bindingのインストール
紐付けに必要なライブラリprisma-bindingをインストールします。
yarn add prisma-binding
src/index.tsのアップデート
インストールしたprisma-binding
のPrisma
クラスを使って、上記で作成したsrc/generated/prisma.graphql
を利用するように、src/index.tsを書き換えます。
import { GraphQLServer } from 'graphql-yoga';
import { Prisma } from 'prisma-binding';
import { IResolvers } from 'graphql-middleware/dist/types';
const resolvers: IResolvers = {
Query: {
posts(parent, args, ctx, info) {
return ctx.db.query.posts({}, info);
},
post(parent, args, ctx, info) {
return ctx.db.query.post({ where: { id: args.id } }, info);
},
},
Mutation: {
createDraft(parent, { title, content }, ctx, info) {
return ctx.db.mutation.createPost(
{
data: {
title,
content,
},
},
info,
);
},
deletePost(parent, { id }, ctx, info) {
return ctx.db.mutation.deletePost({ where: { id } }, info);
},
publish(parent, { id }, ctx, info) {
return ctx.db.mutation.updatePost(
{
where: { id },
data: { published: true },
},
info,
);
},
},
};
const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
context: req => ({
...req,
db: new Prisma({
typeDefs: 'src/generated/prisma.graphql', // the generated Prisma DB schema
endpoint: 'http://localhost:4466', // the endpoint of the Prisma DB service
// secret: "mysecret123", // specified in database/prisma.yml
debug: true, // log all GraphQL queries & mutations
}),
}),
});
server.start(() => console.log('Server is running on http://localhost:4000'));
src/schema.graphqlのアップデート
src/schema.graphqlについても、src/generated/prisma.graphql
で定義されているPost
の型を利用できるよう、次のように書き換えます。
# import Post from "./generated/prisma.graphql"
type Query {
posts: [Post!]!
post(id: ID!): Post
description: String!
}
type Mutation {
createDraft(title: String!, content: String): Post
deletePost(id: ID!): Post
publish(id: ID!): Post
}
さいごに
結構なボリュームのチュートリアルでしたが、手を動かして使ってみると慣れてきて、Prismaの有り難さが分かります。
下記の記事によると、Prismaを使えばN+1問題も解決してくれるそうなので、実戦でも使えるようにしていきたいです。
Do Prisma resolve N+1 problem?
ソースコード
今回作成したサンプルは下記のGitHubから手に入ります。
https://github.com/galoi/prisma-yoga-mysql-typescript-sample