GraphQLの開発環境作りをやってみよう!

記事の概要

こんにちは、ネットオン開発Sec.のサイトーさんです。

この記事では私がGraphQLの勉強のためにした、環境構築の手順についてお話ししたいと思います。

GraphQL とは

GraphQLのサイトでは以下のように説明しています。

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.

https://graphql.org/

要するに、GraphQL はAPI のための問い合わせ言語でありクライアントとサーバ間での言語仕様だそうです。そのため、通信のプロトコルや実装方法やデータの保存方法などは定義していないそうです。

ただ、GraphQL にはいくつかの設計原則があるそうです。

  • 階層(Hierarchical)
    • GraphQL のクエリは階層構造になっており、フィールドは他のフィールドの入れ子になることができ、レスポンスはクエリと同じ構造になること
  • プロダクト中心(Product-centric)
    • GraphQL はプロダクトのビューと作成を担当するフロントエンジニアに寄り添うために環境を構築すること
  • 強い型付け(Strong-typing)
    • GraphQL はクエリの実行前に構文が正しく、システム内で有効であることを確認し、レスポンスを返すこと
  • クライアント指定クエリ(Client-specified queries)
    • データの取得内容はフロントで決定し、GraphQL では求められた以上のものは返さない
  • イントロスペクティブ(Introspective)
    • GraphQL のシステムは自身で型システムを参照できるようにする

この原則を見ると、フロントエンジニアが開発しやすいように GraphQL が構築されるべき感じかなと思います。

REST API との違い

GraphQL と REST の違いを以下にまとめてみました。

REST APIGraphQL
データ操作方法GET / POST / PUT / DELETEQuery / Mutation / Subscribe
データの柔軟性低い高い
エンドポイントの数複数(APIごとに用意)1つ
リクエスト回数ビューの内容によっては複数回リクエストを発行1回

環境を作ってみる

使用したライブラリの中身はこのような感じです。

{
  "name": "Blog_Article_002",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "ts-node": ".\\node_modules\\.bin\\ts-node",
    "start": "nodemon --config ./nodemon.json"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@apollo/client": "^3.6.9",
    "@prisma/client": "^4.2.1",
    "apollo-server": "^3.10.1",
    "graphql": "^16.6.0",
    "nodemon": "^2.0.19",
    "prisma": "^4.2.1",
    "ts-loader": "^9.3.1",
    "ts-node": "^10.9.1",
    "typescript": "^4.7.4",
  }
}

今回は簡単なチャットサービスのためのAPIをいくつか作成する想定で実装していきます。ファイル監視やサーバ再起動をするために今回は nodemon を使用しており、DB には SQLite 、ORMはPrismaを使用しています。

nodemon の設定は以下のようになっています。

{
  "watch": ["src"],
  "ext": "*",
  "exec": "ts-node ./src/server.ts"
}

DBのテーブルなどはPrism のマイグレーションを利用して作成をしていくのでスキーマと接続情報を作成していきます。

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model User {
  id    Int     @id @default(autoincrement())
  name  String
}

model Room {
  id    Int     @id @default(autoincrement())
  name  String   @unique
}

model message {
  id    Int     @id @default(autoincrement())
  body  String
  userId  Int
  roomId  Int
}

schema.prisma のDB接続情報の url は環境変数の DATABASE_URL に設定しているので、環境変数の .env ファイルに DATABASE_URL の値を設定します。

DATABASE_URL="file:./dev.db"

設定が完了したので、 マイグレーションコマンドを実行しDBにテーブルを作成します。

npx prisma migrate dev

マイグレーション時にマイグレートファイルの名前を聞かれるので、わかりやすい名前を付けてください。DBのテーブルが作成されたか確認するために Prisma Studio を利用します。 これは Prisma の機能の一部で、テーブルの閲覧やレコードの編集などができます。以下のコマンドで Prisma Studio を起動します。

npx prisma studio

コマンドを実行すると Prisma Studio のURLが表示されるのでアクセスします。

ここから各テーブルのレコードなどを確認することができます。

Apollo-server の構築

GraphQL のサーバとして今回はApollo を使用しているので、サーバの起動用ファイルとGraphQLのスキーマ定義を作成します。

schema.tsの内容は以下のようにしました。作成するAPIとしては、Query と Mutation を作成します。

export const typeDefs = gql`
  type Query {
    getRoom(roomId: ID): [Room]
    getUser(userId: ID): [User]
    getMessages(roomId: ID): [Message]
  }
  type Mutation {
    createRooms(name: String): Room
    createUsers(name: String): User
    createMessage(userId: ID, roomId: ID, message: String): Message
  }

  type Room {
    id: ID!
    name: String
    messages: [Message]
  }

  type Message {
    id: ID!
    body: String
    userId: ID
    roomId: ID
  }

  type User {
    id: ID!
    name: String
    messages: [Message]
  }
`;

サーバの起動は最低限の設定のみにするため、server.ts は以下のようにしています。

import { ApolloServer } from "apollo-server";
import { typeDefs } from "./schema";
import { resolvers } from "./resolvers";

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

query を実装してみる

resolver の作成に当たって、全ての resolver をまとめるファイル resolver.ts を作成します。ここではこの先作成する mutation や 各ノードもまとめいます。

export const resolvers = {
  Query: query,
  Mutation: mutation,
  User: user,
  Room: room,
};

query の各 resolver の実装内容は以下の通りです。

export const query = {
  getUser: (parent: any, args: any, context: any) => {
    const result = prisma.user.findMany({where:{id: Number(args.userId)}});
    return result;
  },
  getRoom: async (parent: any, args: any, context: any) => {
    const result = await prisma.room.findMany({where:{id: Number(args.roomId)}})
    return result;
  },
  getMessages: (parent: any, args: any, context: any) => {
    const result = prisma.message.findMany({where:{roomId: Number(args.roomId)}})
    return result;
  },
};

引数のparent には、親ノードのデータ取得結果が保存されており、 args にはリクエストから渡された内容が保存されています。prismaの findfirst や findmany では引数で 出力したいカラムや 条件などをSQLで取得するように細かく設定できます。

今回定義したRoom や User には部屋での会話の一覧や、ユーザの発言の一覧などが関連付けて取得できるように設定しているので、子ノードの実装も行います。

<room.ts>

export const room = {
  messages: (parent: any, args: any, context: any) => {
    const result = prisma.message.findMany({where: {roomId: parent.id}});
    return result;
  },
};

<user.ts>

export const user = {
  messages: (parent: any, args: any, context: any) => {
    const result = prisma.message.findMany({where: {id: parent.id}});
    return result;
  },
};

mutation を実装してみる

mutation の実装については query の実装と大きく変わることはありません。

export const mutation = {
    createRooms: async (parent: any, args: any, context: any) => {
        const room = await prisma.room.create({data: {name: args.name}})
        return room
    },
    createMessage: async (parent: any, args: any, context: any) => {
        const message = await prisma.message.create({data:{body: args.message, roomId: Number(args.roomId), userId: Number(args.userId)}});
        return message;
    },
    createUsers: async(parent: any, args: any, context: any) => {
        const user = await prisma.user.create({data:{name: args.name}});
        return user;
    }
}

prisma の create メソッドでテーブルにデータを追加することができるので、引数の data にカラムに対応するデータを設定します。

実行結果

Apollo サーバの起動は以下のコマンドから実行できます。

npm run start

実行に成功するとシェル内に アクセスURLが表示されるのでアクセスします。

ここでは今回作成したquery や mutation が表示されます。

試しにquery の getRoom でデータを取得してみます。引数の設定などは画面下部の Variables から設定することが可能です。実行結果は以下の通りです。

部屋の情報と部屋内での会話の一覧が取得することができました!

まとめ

今回はGraphQLの概要と環境の構築についてお話しさせて頂きました。普段はフロントエンジニアとして業務に携わっていたのですが、GraphQL を理解するにつれてフロントエンドに寄り添った設計原則を感じることができました。作成したものは簡単な query と mutation ですが、他には subscribe というデータの変化をリアルタイムで返す機能などもあるのですが、時間の都合上今回は記事にはしていません。subscribe はまだ実装経験が無いので次回の記事で挑戦するかもしれません。

参考URL & 書籍

GraphQL公式サイト:https://graphql.org/

GraphQL定義書:https://spec.graphql.org/October2021

これを読めばGraphQL全体がわかる。GraphQLサーバからDB、フロントエンド構築:https://reffect.co.jp/html/graphql

TypeScriptでApollo-Serverを構築する:https://qiita.com/omukaik/items/4b31b771c674fcea9118

初めてのGraphQL:https://www.oreilly.co.jp/books/9784873118932