🐘

PostgreSQLからGraphQLサーバーを生成するPostgraphileを手元で動かしてみる

2021/08/18に公開

https://www.graphile.org/

Postgraphile とは

PostgreSQL サーバーのスキーマ情報を読み取って自動的に GraphQL サーバーを立ち上げてくれる CLI/Node.js ライブラリです。
同じようなことをしてくれるものに Hasura があり、Hasura が Docker Image で配布されていて独立したサーバーとして立ち上がるのに対し Postgraphile は Node.js の HTTP サーバーに組み込まれるところが大きな違いです。

なぜ Postgraphile について調べたか

Netflix が社内サービスを立ち上げるときに Postgraphile を使って開発効率を高めたという記事を書いています。

https://netflixtechblog.com/beyond-rest-1b76f7c20ef6

Postgraphile で TODO アプリが作れるとかではなく実サービスの開発に適用できないかなと考えていて、クライアントサイドは GraphQL で高い DX を得られサーバーサイドはサーバー実装にかかかるコストを減らせるのではないかと思っています。ほとんどの会社は Netflix ほど巨大な開発組織ではないですが参考になる点も多いのではないかと思っています。

Postgraphile を立ち上げてみる

以下のサイトで DVD レンタルサービスを題材にしたサンプルの PostgreSQL データが配布されていて、それを Docker Image にしたものを使います。
https://www.postgresqltutorial.com/postgresql-sample-database/
https://hub.docker.com/r/dexels/dvdrental

docker-compose.yml
version: "2"
services:
  postgres:
    image: dexels/dvdrental:1
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: mysecretpassword
docker-compose up -d

CLI から立ち上げる

Postgraphile は CLI から起動できます。

npx postgraphile -c \
  'postgres://postgres:mysecretpassword@localhost:5432/dvdrental' \
  --watch --enhance-graphiql --dynamic-json

これで http://localhost:5000/graphiql にアクセスすると GraphiQL が立ち上がっています。

Relay のCursor Connections Specificationが実装されていて便利ですね。

Node.js の HTTP サーバーから立ち上げる

Node.js の http モジュールや express のミドルウェアとして使うことができます。他にも Fastify や Koa などにも対応しています。doc

const express = require("express");
const { postgraphile } = require("postgraphile");

const app = express();

app.use(
  postgraphile(
    process.env.DATABASE_URL ||
      "postgres://postgres:mysecretpassword@localhost:5432/dvdrental",
    "public",
    {
      watchPg: true,
      graphiql: true,
      enhanceGraphiql: true,
    }
  )
);

app.listen(process.env.PORT || 5000);

Postgraphile が提供する Query, Mutation

Query

DB からレコードを引いてくる感覚で使える Query が提供されます。
公式やサードパーティの Plugin を使うと Aggregate やより高度なフィルターなどが使えます。

query QueryExample($after: Cursor!, $first: Int!, $actorId: Int!) {
  # PKで1件引いてくる
  actor(actorId: $actorId) {
    firstName
    lastName
  }

  # Relay Cursor Specを実装したリスト (offsetベースのページングもできる)
  actors(after: $after, first: $first) {
    edges {
      cursor
      node {
        firstName
        lastName
      }
    }
  }

  # FKでJOINして引いてくる
  actorAndFilm: actor(actorId: $actorId) {
    firstName
    lastName
    filmActors(first: $first) {
      edges {
        node {
          film {
            title
          }
        }
      }
    }
  }

  # 簡単なフィルター
  searchActor: actors(condition: { firstName: "Cuba" }) {
    edges {
      node {
        firstName
        lastName
      }
    }
  }
}

Mutation

INSERT/UPDATE/DELETE ができます。

mutation MutationExample {
  createActor(input: { actor: { firstName: "lol", lastName: "yay" } }) {
    actor {
      id
      firstName
      lastName
    }
  }

  updateActor(input: { actorId: 10, patch: { firstName: "no" } }) {
    actor {
      id
      firstName
      lastName
    }
  }

  deleteActor(input: { actorId: 10 }) {
    actor {
      id
      firstName
      lastName
    }
  }
}

Discussion