express + nongoDBでGraphQLサーバを立ててクエリを実行する

プログラミング
スポンサーリンク




こんにちは、おみです。

最近GraphQLを学習する機会がありどのようなものかの概要は掴めたのですが、どのようにしてサーバを立てているのか気になりキャッチアップしましたので、備忘録として残しておきます。

ここ数日はNode.jsのキャッチアップを行なってきたので、今回はExpressとmongoDBを使って、GraphQLサーバを立ててCRUD処理を実行してみます。

 

スポンサーリンク

環境

MacOS 11.6
VSCode 1.61.1
Node.js 14.17.6

 

今回解説しないこと

  • Node.js(Express)自体の解説
  • mongoDBの利用方法

 

GraphQLとは

クエリ言語スキーマ言語で構成されたWebAPIの規格の一つです。

 

クエリ言語

クライアント側で、GraphQLサーバにリクエストを送る際に使用します。

クエリには下記の3種類があります。

名称 処理 イメージ
query データの取得 GET
mutation データの登録、更新、削除 POST, PUT, DELETE
subscription データの変化をクライアントに通知 WebSocket

 

スキーマ言語

サーバ側で、GraphQL APIの仕様を記述するために使用し増す。

スキーマ言語にはスキーマとリゾルバがあり、それぞれ

  • スキーマでクライアントが操作できるクエリや型を定義する
  • リゾルバで実際にデータに対して行う操作を定義する

ために使用します。

 

GraphQLのメリット / デメリット

メリット

エンドポイントがひとつになるため、コーディングやメンテナンスがしやすい

RESTではリソースごとに異なるエンドポイントを使用するため処理が煩雑になりがちですが、GraphQLではエンドポイントは”/graphql”のみであるため、処理を簡素化でき、コーディングやメンテナンスを容易に行うことができます。

 

リクエストに対して返却するデータを柔軟に設定できる

リクエストに対して返却するデータの形を柔軟に設定できるため、

    • 1回のリクエストで複数のリソースからデータを取得することができる。
    • レスポンスには特定のデータのみを含ませることで、通信速度の向上やデータ量の削減を行うことができる。

といった効果があります。

 

デメリット

パフォーマンスの分析が難しい

RESTでは処理ごとにエンドポイントが異なるためパフォーマンスの分析はエンドポイントごとに行えばすみますが、GraphQLでは全ての処理が1つのエンドポイントを使用するため、処理ごとのパフォーマンスの分析が難しくなります。

 

サーバエンジニアの作業負荷が増える

クライアントから発行されたクエリに対してDBからデータを取得する処理をサーバ側で記述する必要があるため、必然的にサーバエンジニアの作業負荷が高くなりがちです。

 

下準備

MongoDBに接続するためのURLを用意します。

↓参考サイト例

MongoDB Atlasで無料かつ手軽にDB環境を利用してみる - Qiita
調べ物をしてて発見したMongoDB Atlasを触ってみます。 自前でインストール不要、無料ですごく楽に始めることが出来るMongoDB公式のサービスといったところでしょうか。 少し触ってみましたが、プロトタイピングやデモ作成に向...

 

使用する端末からMongoDBへの接続許可を行います

IPアドレスで接続許可を行うため、[Apple メニュー] → [システム環境設定] → [ネットワーク]の順に開き、IPアドレスを確認します。

 

mongodbのサイトへログインし使用するクラスタのページを開き、

[SECURITY] → [Network Access] の順に選択してアクセス許可がされているか確認し、されていない場合は[ADD IP ADDRESS]を押下して追加します。

 

プログラムの概要

フォルダ構成

 

ライブラリ

名称 用途
express Node.jsでExpressを利用するために使用
express-graphql ExpressでGraphQLサーバを構築するために使用
graphql expressでGraphQLのクエリを実行するために利用
mongoose mongoDBに接続するために利用
nodemon ソースが変更された際にアプリを自動で再起動させるために利用

今回はグローバル指定でインストール

 

コマンド

npm install express express-graphql graphql mongoose
npm install -g --force nodemon

 

package.json

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "express-graphql": "^0.12.0",
    "graphql": "^15.6.1",
    "mongoose": "^6.0.11"
  }
}

 

DB構成

今回は、”members”と”places”の2つのテーブルを作成します。

members

名前 説明
name String 名前
age Number 年齢
placeId String 出身地のID

 

places

名前 説明
name String 名称

 

ソースコード + 解説

任意のディレクトリにプロジェクトを保存するディレクトリを作成し、VSCodeで作成したディレクトリを開いて初期化を行います。

npm init

 

今回使用するライブラリのインストールを行います。

npm install express express-graphql graphql mongoose
npm install -g --force nodemon

 

その他必要なディレクトリを作成します。

 

member.jsとplace.jsを開き、MongoDBで使用する各テーブルのモデルのソースコードを記述します。

member.js

// ライブラリの読み込み
const mongoose = require("mongoose");

// 定数を作成
const Schema = mongoose.Schema

// スキーマの作成
const memberSchema = new Schema({
  name: String,
  age: Number,
  placeId: String
})

// モジュールのエクスポート
// 第一引数: mongoDBでのcollection名
//       勝手に小文字になり、最後にsが付く(Movies → Moviessにはならない)
// 第二引数: モデル
module.exports = mongoose.model("Member", memberSchema)

 

place.js


const mongoose = require("mongoose");

const Schema = mongoose.Schema

const placeSchema = new Schema({
  name: String,
})

module.exports = mongoose.model("Place", placeSchema)

 

schema.jsを開き、QueryとMutationのソースコードを記述します。

const graphql = require("graphql");
const Member = require("../models/member")
const Place = require("../models/place")

// 定数を生成
// Type ... データのタイプ
// ID, String等 ... 変数型
const { GraphQLObjectType,
  GraphQLID,
  GraphQLString,
  GraphQLSchema,
  GraphQLInt,
  GraphQLList,
  GraphQLNonNull
} = graphql

// タイプを生成
const MemberType = new GraphQLObjectType({
  // タイプの名前を入力
  name: "Member",
  // 取得したいデータ、型を入力
  // ポイント: カプセル化するためにクロージャで囲って関数化する
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    age: { type: GraphQLInt },
    place: {
      type: PlaceType,
      resolve(parent, args) {
        return Place.findById(parent.placeId)
      }
    }
  })
})

const PlaceType = new GraphQLObjectType({
  name: "Place",
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    members: {
      type: new GraphQLList(MemberType),
      resolve(parent, args) {
        return Member.find({ placeId: parent.id })
      }
    }
  })
})

// クエリを生成
const Query = new GraphQLObjectType({
  // タイプ名
  name: "Query",
  fields: {
    // クエリの名前を記述
    member: {
      // 作成したタイプ
      type: MemberType,
      // 検索時に使用するパラメータを取得
      args: { id: { type: GraphQLID } },
      // リゾルバを記述
      resolve(parents, args) {
        // 指定したIDに紐づくデータを返却
        return Member.findById(args.id)
      }
    },
    place: {
      type: PlaceType,
      args: { id: { type: GraphQLID } },
      resolve(parents, args) {
        // 指定したIDに紐づくデータを取得
        return Place.findById(args.id)
      }
    },
    members: {
      type: new GraphQLList(MemberType),
      resolve(parent, args) {
        // 全件取得
        return Member.find({})
      }
    },
    places: {
      type: new GraphQLList(PlaceType),
      resolve(parent, args) {
        // 全件取得
        return Place.find({})
      }
    },
  }
});

// ミューテーションを生成
const Mutation = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    // 追加処理
    addMember: {
      type: MemberType,
      args: {
        name: { type: GraphQLString },
        age: { type: GraphQLInt },
        placeId: { type: GraphQLID },
      },
      resolve(parent, args) {
        let member = new Member({
          name: args.name,
          age: args.age,
          placeId: args.placeId
        })

        return member.save()
      }
    },
    addPlace: {
      type: PlaceType,
      args: {
        name: { type: GraphQLString },
      },
      resolve(parent, args) {
        let place = new Place({
          name: args.name,
        })

        return place.save()
      }
    },
    // 更新処理
    updateMember: {
      type: MemberType,
      args: {
        id: { type: GraphQLNonNull(GraphQLID) },
        name: { type: GraphQLString },
        age: { type: GraphQLInt },
        placeId: { type: GraphQLID }
      },
      resolve(parent, args) {
        let updateMember = {}
        args.name && (updateMember.name = args.name)
        args.age && (updateMember.age = args.age)
        args.placeId && (updateMember.placeId = args.placeId)
        return Member.findByIdAndUpdate(args.id, updateMember, { new: true })
      }
    },
    updatePlace: {
      type: PlaceType,
      args: {
        id: { type: GraphQLNonNull(GraphQLID) },
        name: { type: GraphQLString },
      },
      resolve(parent, args) {
        let updatePlace = {}
        args.name && (updatePlace.name = args.name)
        return Place.findByIdAndUpdate(args.id, updatePlace, { new: true })
      }
    },
    // 削除処理
    deleteMember: {
      type: MemberType,
      args: {
        id: { type: GraphQLNonNull(GraphQLID) },
      },
      resolve(parent, args) {
        return Member.findByIdAndRemove(args.id)
      }
    },
    deletePlace: {
      type: PlaceType,
      args: {
        id: { type: GraphQLNonNull(GraphQLID) },
      },
      resolve(parent, args) {
        return Place.findByIdAndRemove(args.id)
      }
    },
  }
})

// モジュールをエクスポート
module.exports = new GraphQLSchema({
  query: Query,
  mutation: Mutation
})

 

app.jsを開き、GraphQLサーバを起動するためのソースコードを記述します。

app.js

// ライブラリを読み込む
const express = require("express");
const graphqlHTTP = require("express-graphql").graphqlHTTP;
const mongoose = require("mongoose");
const schema = require("./schema/schema")
const app = express();

// mongoDBに接続する
mongoose.connect('ここにmongodbに接続するためのURLを記述する');
mongoose.connection.once('open', () => {
  console.log('db connected!!');
});

// ポート番号を指定
var port = 3000;

// エンドポイントを生成
app.use('/graphql', graphqlHTTP({
  schema,
  graphiql: true
}))

// サーバを起動
app.listen(port, () => {
  console.log('listening port ' + port);
});

ここまででソースコードの記述は終了です。

サーバを起動し、クエリを実行してみましょう。

 

クエリの実行

app.jsでエンドポイントを生成する箇所でgrapiqlをtrueとしているので、”http://localhost:4000/graphql”にアクセスするとクエリを実行するためのエディタが開かれます。

ここからクエリを実行してみましょう。

エディター左部の白いテキストエリアにクエリを記述し、丈夫の実行ボタンを押下するとクエリを実行できます。

まずはplaceにデータをaddしていきます。

query1

mutation {
  addPlace(name: "大阪府"){
    id,
    name
  },
}

result1

{
  "data": {
    "addPlace": {
      "id": "ここに登録されたIDが出てくる",
      "name": "大阪府"
    }
  }
}

 

query2

mutation {
  addPlace(name: "兵庫県"){
    id,
    name
  },
}

 

result2

{
  "data": {
    "addPlace": {
      "id": "ここに登録されたIDが出てくる",
      "name": "兵庫県"
    }
  }
}

 

データが登録されているか確認してみましょう。

query3

query {
  places{
    id,
    name
  }
}

 

result3

{
  "data": {
    "places": [
      {
        "id": "ID",
        "name": "大阪府"
      },
      {
        "id": "ID",
        "name": "兵庫県"
      }
    ]
  }
}

 

続いてmemberを追加していきましょう。

かりんちゃ♡とほのぢゃんときらぢゃんを追加します。

query4

mutation {
  addMember(name: "藤吉夏鈴", age: 20, placeId: "大阪府のplaceID"){
    name,
    age
  }
}

result4

{
  "data": {
    "addMember": {
      "name": "藤吉夏鈴",
      "age": 20
    }
  }
}

 

query5

mutation {
  addMember(name: "田村保乃", age: 22, placeId: "大阪府のplaceID"){
    name,
    age
  }
}

result5

{
  "data": {
    "addMember": {
      "name": "田村保乃",
      "age": 22
    }
  }
}

 

query6

mutation {
  addMember(name: "増本綺良", age: 19, placeId: "兵庫県のplaceID"){
    name,
    age
  }
}

result6

{
  "data": {
    "addMember": {
      "name": "増本綺良",
      "age": 19
    }
  }
}

 

ゔゔっ…

狭いところにとじこめてごめんねかりんちゃ…

 

データが登録されているか確認してみましょう。

query7

query {
  members {
    id,
    name,
    age,
    place {
      name
    }
  }
}

 

result7

{
  "data": {
    "members": [
      {
        "id": "ID",
        "name": "藤吉夏鈴",
        "age": 20,
        "place": {
          "name": "大阪府"
        }
      },
      {
        "id": "ID",
        "name": "田村保乃",
        "age": 22,
        "place": {
          "name": "大阪府"
        }
      },
      {
        "id": "ID",
        "name": "増本綺良",
        "age": 19,
        "place": {
          "name": "兵庫県"
        }
      }
    ]
  }
}

 

ほのぢゃんは先日誕生日を迎えたので、年齢を更新しましょう。

query8

mutation{
  updateMember(id: "田村保乃のID", age: 23) {
    name,
    age
  }
}

result8

{
  "data": {
    "updateMember": {
      "name": "田村保乃",
      "age": 23
    }
  }
}

 

かりんちゃ♡がDBは狭くて嫌といっているので、出してあげましょう。

ごめんねぇ…

query9

mutation {
  deleteMember(id: "藤吉夏鈴のID") {
    name,
    age
  }
}

result9

{
  "data": {
    "deleteMember": {
      "name": "藤吉夏鈴",
      "age": 20
    }
  }
}

 

確認

query10

query {
  members {
    name,
    age,
    place {
      name
    }
  }
}

result10

{
  "data": {
    "members": [
      {
        "name": "田村保乃",
        "age": 23,
        "place": {
          "name": "大阪府"
        }
      },
      {
        "name": "増本綺良",
        "age": 19,
        "place": {
          "name": "兵庫県"
        }
      }
    ]
  }
}

 

ここまででMemberに対するクエリのほとんどを実行できました。

Placeの実行していないクエリに関しても、同じ要領で実行できます。

 

今回はExpressとmongodbを使用して、GraphQLサーバを立ててクエリを実行してみました。

次回はFlutterアプリから今回実装したGraphQLサーバに接続して、データのやりとりができるか試してみます。

 

 

タイトルとURLをコピーしました