36
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

HameeAdvent Calendar 2019

Day 21

Apollo+PrismaでGraphQLに入門する

Last updated at Posted at 2019-12-20

この記事は Hamee Advent Calendar 2019 21日目の記事です。

先日、とある管理画面を実装後に GraphQL で実装すれば良かったなーと思う事があり、改めて GraphQL に興味が沸き前々から触ってみようと思っていたので、実際に触ってみました。

本当は簡単なTodoアプリを作る予定だったのですが、GraphQLサーバーの触りだけをまとめる感じになってしまいました ( ˆ꒳​ˆ; )

t-yng/graphql-todo-app

ライブラリの紹介

  • 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
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 に実行スクリプトを追加して実行します。

package.json
  "scripts": {
    "dev": "ts-node src/index.ts"
  }
$ yarn dev

http://localhost:4000/graphql にアクセスして、次のクエリを実行すれば結果が返ってきます。

query {
  hello
}

GraphQLのインターフェースを実装してみる

次に簡単なTodoを取得するGraphQLのQueryを自分で実装してみます。

スキーマを定義

Todoオブジェクトの型定義とそれを取得する Query の型定義を書いていきます。
※ 型定義という言い方が正しいのか不明

src/index.ts
const typeDefs = gql`
    type Todo {
        id: Int!
        title: String
    }

    type Query {
        todo(id: Int): Todo
        todoes(title: String): [Todo]
    }
`;

リゾルバを実装

リゾルバとは実際にリクエストを受け取りスキーマ定義に合わせてデータを返す部分です。
仮で変数を定義して値を返してみます。

src/index.ts
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

おー!無事に結果が取得できています。(*゚▽゚ノノ゙☆パチパチ
何か新鮮な体験で凄い楽しいですね!

スクリーンショット 2019-12-21 1.56.07.png

DBと連携する

変数のデータを取得するだけだと味気ないので、折角なら実践的にDBにあるデータを取得するようにしましょう。

prismaを導入

prisma/prisma とは GraphQLサーバー と データーベース を繋ぐ ORM です。
モデルの定義を書けば、テーブルのマイグレーションやデータベースへアクセスするためのコードを自動生成をしてくれます。

※ この記事だとTypeORM とかを利用する場合と大差がなく、メリットを全く享受出来ていない気がしてます...

prisma自体はライブラリではなく、CLIツールなので開発用パッケージとしてインストールします。

$ yarn add -D prisma

データベースとprismaのセットアップ

ローカルで開発するだけであれば、initコマンドを実行するだけでデータベースのDocker環境をスキャフォールドしてくれます。

今回の設定は Create new databaseMySQLPrisma 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にアクセスするために次の箇所をコメントアウトしておきます。

docker-compose.yml
# 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にテーブルとしてマイグレーションしてくれます。

datamodel.prisma
type Todo {
  id: Int! @id(strategy: AUTO)
  title: String!
}

マイグレーションの実行

マイグレーションを実行する前に prisma.yml を変更して、クライアントコードの生成場所とhooksの設定をしておきます。

prisma.yml
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アクセスはそれを利用します。

src/index.ts
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 }});
        },
    },
};

無事に結果が返ってきました!
スクリーンショット 2019-12-21 1.56.07.png

スキーマ定義を別ファイルに切り出す

テンプレートリテラルでスキーマ定義を書いていくのは見づらくメンテナンス性も悪そうなので、別ファイルに分割します。

graphql-import のインストール

Urigo/graphql-import はGraphQLのスキーマ定義ファイルの import を可能にしてくれるライブラリです。

$ yarn add graphql-import

スキーマ定義を分割

スキーマ定義を配置するディレクトリを新しく作ります。

$ mkdir src/schema
src/schema/todoes.graphql
type Todo {
    id: Int!
    title: String
}
src/schema/schema.graphql
# import Todo from "todoes.graphql"

type Query {
    todo(id: Int): Todo
    todoes(title: String): [Todo]
}

スキーマ定義をimportする

スッキリしました。

src/index.ts
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 をちゃんと使えばスキーマ定義を起点にした新しい開発体験が出来ると期待しているので、もっと色々と試してみたいです。

36
27
3

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
36
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?