PostGraphileで爆速GraphQL API開発: PostgreSQLからスキーマを自動生成

本記事はQmonus Value Streamの投稿キャンペーン記事です。

# はじめに

本記事では、PostGraphileを使用して、簡単にGraphQL APIを作成する方法をご紹介します。
素早いAPI試作が求められるPoCや先行開発フェーズにおいては、慣れないGraphQLのスキーマとリゾルバを定義してAPI開発している時間がもったいないですよね。
そんなときは、PostgreSQLテーブル定義して、PostGraphileでAPI変換した方が何倍も早くAPI開発ができます。

本記事は、「GraphQL APIって何？」という方も読んで頂けるようにGraphQLの説明から書いています。
そのため、本題のみご興味持っていただいた方は、[PostGraphileとは](https://qiita.com/junffy/items/08e362b1948cb5211d75#postgraphile%E3%81%A8%E3%81%AF)からお読み頂けますと幸いです。

# GraphQL

GraphQLの説明と特徴は下記になります。

- GraphQLはMeta (Facebook)によって2012年に開発され、2015年に公開されたAPI用のクエリ言語及びサーバーランタイム
- 基本単位はクエリ
- RESTと比較すると、GraphQLはクライアント主導のアプローチであり、必要なデータやデータの取得方法、及び形式をクライアントが決定可能
- クライアントが必要なデータを特定・指定できるため、RESTのデメリットであるオーバーフェッチとアンダーフェッチの問題も解決可能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136035/437e651a-629d-2200-652b-317ab800bc09.png)



## Pros & Cons

GraphQLのメリデメは下記になります。

### Pros
- 無駄なデータ取得が生じない
    - 必要なフィールドを正確に指定できるため、オーバーフェッチやアンダーフェッチが発生しない
- API呼び出し回数の抑制
- エンドポイント数が減るため、APIの構成が単純化される
### Cons
- 標準キャッシュのサポートがされていない
- RESTに比べて複雑化してしまうケースがある
    -　RESTやgRPCと比較して、学習コストが高いため慣れが必要
- バックエンド開発の負担増
    - クライアント側の柔軟なデータ取得を実現するためにサーバー側の処理が増え、パフォーマンスが下がってしまうケースがある

## 他のWeb APIとの比較

### REST

- 最もよく使われていて一般的なAPI設計スタイル
- 基本単位はリソース
- URLを使用してリソースを指定して、HTTPメソッドを使用して実行するアクションを決定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136035/a1b520d7-f7e0-06ef-0cec-260c3316083d.png)



HTTPメソッドには以下のような種類があります。

| メソッド | 説明 |
|:-:|:-:|
| GET | 既存リソースの取得  |
| POST  | 新規リソースの作成 |
| DELETE  | リソースの削除 |
| PUT  | リソース全体の更新及び作成 |

- シンプルに使えて、どのようなプロダクトにおいても大体使うことが可能
    - オーバーフェッチやアンダーフェッチなどで必要のないリソースを取得可能性有り
    - ODdataを使ってエンティティの一部取得をすることは可能(私自身、周りで使っているところをあまり見たことないですが…)
        - ODataに関しては下記記事にまとめてくださっています

https://qiita.com/Yanagawa_Yoshihisa/items/c0143ac499429ff75a05

https://qiita.com/Integrateio/items/c003985a3b215d0e44ba

### gRPC

- gRPCはGoogleが開発した高性能RPCフレームワーク
- Protocol Bufferを使用して、サービスインターフェースとペイロードメッセージの構造を定義します
- 軽量で高速なため高性能であるため、クライアントのリソース不足の場合に適しています
- 異なる言語を用いてサービス通信ができるため、マイクロサービス間通信でも使用されます

### 比較表

下記観点で、GraphQL、REST、gRPCを比較してみた結果を下表に示します。

1. 結合度
2. APIの呼び出し回数
3. パフォーマンス
4. 実装の複雑さ
5. キャッシング
6. コード生成
7. API探索性
8. バージョニングの容易さ

|  | GraphQL | REST | gRPC |
|:-:|:-:|:-:|:-:|
| 結合度  | 中 | 低  | 高 |
| APIの呼び出し回数  | 少ない | 多い  | 中程度 |
| パフォーマンス  | 良好 | 良好 | 非常に高い  |
| 実装の複雑さ  | 高い  | 中程度  | 低い |
| キャッシング  | 設計必要  | 良好  | 設計必要  |
| コード生成  | 良好  | 乏しい | 非常に良好  |
| API探索性  | 良好  | 良好  | 乏しい  |
| バージョニングの容易さ  | 設計必要  | 容易 | 困難 |

## GraphQLの仕様

実際にGraphQLの仕様について確認します。
基本的に[公式ページ](https://graphql.org/learn/)に記載されていますので、併せてご確認ください。

### スキーマ言語
GraphQLには型があります。この型定義ができるのが、スキーマ言語です。
GraphQLを介した通信は、このスキーマ言語による型定義をベースとしています。

#### スカラー型

GraphQLにおいてデフォルトで定義されている型は下記。

`Int`: 符号付きの32bit整数
`Float`: 符号付きの倍精度浮動小数点数
`String`: UTF-8の文字列
`Boolean`: `true`もしくは`false`
`ID`: 一意な識別子 (String型)
また、GraphQL ユーザー独自に型を定義することができます。

```graphql
scalar Date
```


#### オブジェクトの型とフィールド

GraphQLの型定義は、型名とフィールドから成ります。

```graphql
type Character {
  name: String!
  appearsIn: [Episode!]!
}
```

- `Character`というオブジェクトの型を定義している
- `name`と`appearsIn`の 2 つのフィールドを持つ
- `String`は文字列のスカラー型
- `String!`と最後に!をつけることで、そのフィールドがnullにならないことを示す
- `Episode`はユーザー定義の型
- `[Episode]`と`[]`で囲むことにより、配列であることを示す
- !がついているので、このフィールドはnullにならないことを示している

#### 列挙型

`Episode`型として指定されたフィールドには、`NEWHOPE`、`EMPIRE`、`JEDI`のいずれかしか入らないことを示しています。

```graphql
enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}
```

#### 引数を持つフィールド

フィールドには引数を定義することができます。

```
type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}
```
引数は必須および任意で指定することが可能です。
必須としたい場合はnull不可を示す!を引数の型に付けます。任意とした場合、引数にデフォルト値を指定することも可能です。上記の例では、`unit`の引数が提供されない場合には`METER`が使用されます。

#### Query型と Mutation型

スキーマ定義において、Query型と Mutation型は特殊な意味を持ち、APIにおけるエンドポイントを示すことができます。

例えば、以下のようなクエリ文を GraphQL サーバーに送り、データが取得できたとします。

```graphql:クエリ文
query {
  hero {
    name
  }
  droid(id: "2000") {
    name
  }
}
```

```json:レスポンス
{
  "data": {
    "hero": {
      "name": "R2-D2"
    },
    "droid": {
      "name": "C-3PO"
    }
  }
}
```

リクエストしたクエリ文には、`hero`および`droid`の2文が含まれており、結果も取得できています。
GraphQLのエンドポイントに`hero`および`droid`が存在することを示しています。

GraphQLにおけるエンドポイントの定義は、冒頭でも触れたようにQuery型又は、Mutation型を利用します。例えば以下のような型定義が必要です。

```graphql
type Query {
  hero(episode: Episode): Character
  droid(id: ID!): Droid
}
```

上記は、Query型にheroフィールドおよびdroidフィールドが定義されています。
Query型に登録されたフィールドがエンドポイントとして登録されますが、型定義そのものは通常のオブジェクトと変わりません。

Query型とMutation型の2つは実行できる処理が異なります。
GraphQLにおいて、Queryはデータの取得(GET)、Mutationはデータの変更(POST、PUT)を意味します。

#### インターフェース

```graphql
interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}
```

上記`Character`インターフェースを実装する型では、`id`、`name`、`friends`、`appearsIn`の4フィールドを実装する必要があります。

例えば、下記は`Character`インターフェースを実装した`Human`型と`Droid`型です。

```graphql
type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}
```

```graphql
type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}
```

`Character`インターフェースで定義されていたフィールド以外については自由に実装することができます。`Human`型では`starships`と`totalCredits`、`Droid`型では`primaryFunction`が実装されています。
同じインターフェースを実装している場合においても、型名が異なれば別の型となります。

```graphql:クエリ文
query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    primaryFunction
  }
}
```

```json:変数
{
  "ep": "JEDI"
}
```
```json:レスポンス
{
  "errors": [
    {
      "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ]
    }
  ]
}
```

`hero`は`Character`型の値を取得するクエリです。
このクエリ文では`"ep": "JEDI"`に該当する値をもつ`Character`を取得しています。
他方で、`primaryFunction`は`Droid`型にのみ実装されたフィールドであり、`Character`型のすべてに含まれているとは限りません。
そのため、エラーが発生しています。

取得したデータが`Droid`型の場合だった場合、同時にprimaryFunctionの値を取得したい場合は、インラインフラグメントを使えば上手く取得することができます。

```graphql:クエリ文
query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
```

```json:変数
{
  "ep": "JEDI"
}
```

```json:レスポンス
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}
```

#### ユニオン型

```graphql
union SearchResult = Human | Droid | Starship
```

上記の場合、`SearchResult`として`Human`、`Droid`、`Starship`の3つを指定しています。
これは、`SearchResult`型を指定したフィールド等では、その実装として`Human`、`Droid`、`Starship`のどれかを入れることが可能であることを示します。
例えば、SearchResult 型の結果を返すsearchクエリが存在する場合に、以下のような処理が行えます。

```graphql:クエリ文
{
  search(text: "an") {
    __typename
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}
```
```json:レスポンス
{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo",
        "height": 1.8
      },
      {
        "__typename": "Human",
        "name": "Leia Organa",
        "height": 1.5
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1",
        "length": 9.2
      }
    ]
  }
}
```

クライアントは型名を表す文字列`__typename`を元に、取得できたデータの型を判断できます。
`...on XXX`は、インラインフラグメントです。型に応じて取得するフィールドを選択しています。
`Human`型と`Droid`型はどちらも`Character`型を実装しているため、下記のように書くことも可能です。

```graphql
{
  search(text: "an") {
    __typename
    ... on Character {
      name
    }
    ... on Human {
      height
    }
    ... on Droid {
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}
```

#### 入力型

入力型は、クエリの引数として用いられるフィールドの組み合わせを定義できます。
例えば、`ReviewInput`型を書きのように定義します。

```graphql
input ReviewInput {
  stars: Int!
  commentary: String
}
```

`ReviewInput`型を用いる`CreateReviewForEpisode`クエリが存在する場合、以下のような処理が行えます。

```graphql:クエリ文
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
```

```json:変数
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
```

```graphql:レスポンス
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}
```

`ReviewInput`型は感想を登録するために必要な情報を示していると考えられます。
このように、「ある操作を行うために必要な情報」について、入力型という形式で表現することができます。
ただし、入力型は通常の型と違い、フィールドに引数を設定することはできないので注意してください。

### クエリ言語

ここからはクエリ言語についてです。
クエリ言語を用いることにより、データの問い合わせやデータ変更依頼を行うことができます。

#### フィールド

```graphql:クエリ文
{
  hero {
    name
  }
}
```

```json:レスポンス
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}
```

`hero`はスキーマ言語の項目で見たように、`Query`型に登録されたフィールドを呼び出しています。
(`name`は上記`hero`の持つフィールドのうち、取得したいフィールドを指定してます)。結果として、`"name": "R2-D2"`が取得できています。


例えば `Character`型のフィールドとして`friends`を持ち、かつ`friends`はオブジェクトの配列だったとします。
その場合、以下のような処理を行うこともできます。

```graphql:クエリ文
{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}
```
```json:レスポンス
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}
```

フィールドがオブジェクトであれば、上記のように取得するフィールドを指定することができます。
`Query`型もオブジェクトであると考えれば、どんなクエリ文も取得するフィールドを指定しているだけと考えられます。

#### 引数

クエリ文には引数を渡すことができます。例えば、ID が 1000 である人間を取得するクエリは以下の通りです。

```graphql:クエリ文
{
  human(id: "1000") {
    name
    height
  }
}
```
```json:レスポンス
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}
```

RESTでデータを取得する場合、値をリクエストに含めたいときは URLまたは、クエリパラメータに入れます。
一方で GraphQL の場合、ネストしたオブジェクトのフィールドそれぞれに引数を指定することができます。
上記の例を参考に、身長をメートル表記からフィート表記に変えたい場合は下記になります。

```graphql:クエリ文
{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
```
```json:レスポンス
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}
```

#### エイリアス
フィールドにはエイリアスをつけることができます。
例えば、`hero`クエリを2つまとめて発行したい場合、そのままでは同じ`hero`なので両者の区別がつきません。
そこで、エイリアスをつけることで区別できるようになります。

```graphql:クエリ文
{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
```
```json:レスポンス
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}
```

#### フラグメント

フラグメントは、フィールド取得構成を使いまわすことのできる機能です。

```graphql:クエリ文
{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}
​
fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
```

```json:レスポンス
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
      "friends": [
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}
```
それぞれのフィールドとして`..comparisonFields`を指定しています。
これは、取得するフィールドの指定を`comparisonFields`フラグメントに委譲しています。
また、`fragment comparisonFields on Character`は、取得できたデータ型が`Character`型だった場合に取得するフィールドの指定を行っています。
`hero`では`Character`型が取得できるので、どちらもデータを取得できてます。

なお、フラグメントには引数が利用できます。

```graphql:クエリ文
query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}
```
```json:レスポンス
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "friendsConnection": {
        "totalCount": 4,
        "edges": [
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          },
          {
            "node": {
              "name": "C-3PO"
            }
          }
        ]
      }
    },
    "rightComparison": {
      "name": "R2-D2",
      "friendsConnection": {
        "totalCount": 3,
        "edges": [
          {
            "node": {
              "name": "Luke Skywalker"
            }
          },
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          }
        ]
      }
    }
  }
}
```

#### 操作名
操作名は、発行するクエリに任意指定することができる名称です。クエリ発行ごとに毎回指定可能です。
これまでに例として記述されたクエリ文は、クエリ操作(query、mutationなど)と操作名の指定を省略した記法になります。操作名を指定する場合、クエリ操作についても明記が必要です。
以下に例を示します。

```graphql:クエリ文
query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}
```
```json:レスポンス
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}
```
省略記法を用いる場合、操作名は不要です。不要な操作名をあえて明記する理由は、サーバーのログ記録です。
クエリを発行する際に操作名を記述し、GraphQLサーバーで記録することにより、不具合や問い合わせなどの際にログを追いかけることを容易にします。

#### 変数
GraphQL では、クエリ文と同時に変数の指定も送信することができます。

```graphql:クエリ文
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
```
```json:変数
{
  "episode": "JEDI"
}
```
```json:レスポンス
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}
```

クエリ文には、`Episode`型を指定できる`$episode`変数が引数として存在します。変数定義では、この `episode`に`JEDI`を代入しています。


#### ディレクティブ
ディレクティブは、変数を与えることで取得するデータの構造を動的に変化させることができます。

```graphql:クエリ文
query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}
```
```json:変数
{
  "episode": "JEDI",
  "withFriends": false
}
```
```json:レスポンス
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}
```
`@include`ディレクティブにより、`friendsフィールドを含むかどうか選択しています。今回は false を引数に与えたため、`friends`フィールドを除く結果となりました。

GraphQL には、上記の`@include`、および逆の操作を行える`@skip`の2種類が定義されています。
ライブラリやユーザーで独自に追加定義が可能で、Apollo Serverでは`@key`や`@provides`等のディレクティブが定義されています。

#### Mutations
RESTでは、GET リクエストではデータの取得のみを行います。データの変更を行うにはPOSTやPUTなどのリクエストメソッド使います。
QraphQL でもデータの変更を伴わない操作にはQueryを、データの変更を伴う操作にはMutationを利用すると使い分けされています。

```graphql:クエリ文
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
```
```json:変数
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
```
```json:レスポンス
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}
```
レビューを新規作成するクエリ文です。`mutation CreateReviewForEpisode`と示されています。


# PostGraphileとは

[PostGraphile](https://www.graphile.org/postgraphile/introduction/)は、PostgreSQLのスキーマからGraphQL APIを構築するミドルウェアです。PostGraphileはサーバーのセットアップやGraphQLスキーマの手動生成を省略して、GraphQL APIを生成してAPIの開発スピードをアップさせることができます(すぐに使って試したいPoCに最適)。

下記、PostGraphileの特徴です。

- データベースのスキーマを基にして、自動的にGraphQLスキーマとリゾルバーを生成します
    - データベースに対してGraphQLクエリを直接発行することができます
- PostGraphileはセキュリティに重点を置いており、権限設定をサポートしています
    - データベースのテーブルとカラムごとにアクセス権限を制御することができます
- GraphQLのクエリを簡単にテストするためのグラフィQLインターフェースを提供してます
- データベースのスキーマが変更された場合、自動的にGraphQLスキーマを更新し、変更内容を反映します
- 必要に応じてPostGraphileの動作をカスタマイズできます
    - プラグインや設定オプションを活用して、カスタムリゾルバーやGraphQLの拡張機能を追加するなど、APIの振る舞いを調整することができます

## Hasuraとの違い

- PostGraphileには、Hasuraよりもカスタマイズを行うための設定オプションが多くあります
    - また、上記の通りプラグインや設定オプションなどでAPIの振る舞いを調整できます
- Hasuraの方が認証やアクセス制御を簡単に設定できます
    - PostGraphileでは時間はかかりますが、Hasuraよりも自由度の高い認証やアクセス制御が可能です

→ 簡単に早く作る必要があるのであれば、Hasura
→ 高度なカスタマイズが必要なのであれば、PostGraphile

## 実際に使ってみる

### PostGraphileを使用する場合

```bash:データベースへの接続と作成
# データベースへの接続
$ psql -U username -d database_name

# データベースの作成
$ createdb my_database
```
- 下記でテーブルを作成します
```sql:テーブルの作成
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE
);

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title TEXT NOT NULL,
  body TEXT,
  author_id INTEGER REFERENCES users(id)
);
```
- 次にテーブルにデータを登録します
```sql:テーブルへのユーザーと投稿の挿入
-- ユーザーの挿入
INSERT INTO users (name, email) VALUES 
('John Doe', 'john@example.com'),
('Jane Smith', 'jane@example.com');

-- 投稿の挿入
INSERT INTO posts (title, body, author_id) VALUES 
('First Post', 'This is John''s first post', 1),
('Hello World', 'Jane''s greeting to the world', 2),
('Another Post', 'John''s second post', 1);
```
- 下記でテーブルの確認できます
```sql:テーブルの登録確認

my_database=# \dt
        List of relations
 Schema | Name  | Type  | Owner
--------+-------+-------+--------
 public | posts | table | junffy
 public | users | table | junffy
(2 rows)
```
- 下記で`server.js`を作成して、nodeサーバー立ち上げ
    - PostGraphileが起動して、GraphiQLがlocalhostで閲覧できます。

```javascript:server.js
const express = require("express");
const { postgraphile } = require("postgraphile");

const app = express();

app.use(
  postgraphile(
    process.env.DATABASE_URL || "postgres:///my_database",
    "public",
    {
      watchPg: true,
      graphiql: true,
      enhanceGraphiql: true,
    }
  )
);




app.listen(5000, () => {
  console.log("PostGraphile server is running on http://localhost:5000/graphql");
});
```

- GraphiQL画面

![Screenshot 2024-07-02 at 9.18.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136035/dffa6c29-eac8-dfab-ae22-9375788b8131.png)


```sql:クエリリクエスト送信
query {
  allPosts {
    nodes {
      id
      title
      body
      userByAuthorId {
        id
        name
        email
      }
    }
  }
}
```
![Screenshot 2024-07-02 at 11.06.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136035/6967344e-01ec-ea9d-ebdf-c5bd0a5a6124.png)

PostgreSQLテーブルに追加したデータをクエリで呼び出すことができました。
テーブルにデータを追加して、PostGraphileサーバーを立ち上げれば、すぐにGraphQL API化できて呼び出せるので便利ですね。爆速でAPI開発できそうです。

## PostGrahile有り無し比較

## 使う場合

```sql:データベーススキーマ定義
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE
);

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title TEXT NOT NULL,
  body TEXT,
  author_id INTEGER REFERENCES users(id)
);
```
```javascript:server.js (サーバーのセットアップ)
const express = require("express");
const { postgraphile } = require("postgraphile");

const app = express();

app.use(
  postgraphile(
    process.env.DATABASE_URL || "postgres:///my_database",
    "public",
    {
      watchPg: true,
      graphiql: true,
      enhanceGraphiql: true,
    }
  )
);

app.listen(5000, () => {
  console.log("PostGraphile server is running on http://localhost:5000/graphql");
});
```

### 使わない場合

```javascript:スキーマ定義
const { gql } = require('apollo-server-express');

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]
  }

  type Post {
    id: ID!
    title: String!
    body: String
    author: User!
  }

  type Query {
    user(id: ID!): User
    users: [User!]!
    post(id: ID!): Post
    posts: [Post!]!
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
    updateUser(id: ID!, name: String, email: String): User!
    deleteUser(id: ID!): Boolean!
    createPost(title: String!, body: String, authorId: ID!): Post!
    updatePost(id: ID!, title: String, body: String): Post!
    deletePost(id: ID!): Boolean!
  }
`;
```
```javascript:リゾルバ定義
const resolvers = {
  Query: {
    user: async (_, { id }, { pgClient }) => {
      const result = await pgClient.query('SELECT * FROM users WHERE id = $1', [id]);
      return result.rows[0];
    },
    users: async (_, __, { pgClient }) => {
      const result = await pgClient.query('SELECT * FROM users');
      return result.rows;
    },
    post: async (_, { id }, { pgClient }) => {
      const result = await pgClient.query('SELECT * FROM posts WHERE id = $1', [id]);
      return result.rows[0];
    },
    posts: async (_, __, { pgClient }) => {
      const result = await pgClient.query('SELECT * FROM posts');
      return result.rows;
    },
  },
  Mutation: {
    createUser: async (_, { name, email }, { pgClient }) => {
      const result = await pgClient.query(
        'INSERT INTO users(name, email) VALUES($1, $2) RETURNING *',
        [name, email]
      );
      return result.rows[0];
    },
    updateUser: async (_, { id, name, email }, { pgClient }) => {
      const result = await pgClient.query(
        'UPDATE users SET name = COALESCE($2, name), email = COALESCE($3, email) WHERE id = $1 RETURNING *',
        [id, name, email]
      );
      return result.rows[0];
    },
    deleteUser: async (_, { id }, { pgClient }) => {
      const result = await pgClient.query('DELETE FROM users WHERE id = $1', [id]);
      return result.rowCount > 0;
    },
    createPost: async (_, { title, body, authorId }, { pgClient }) => {
      const result = await pgClient.query(
        'INSERT INTO posts(title, body, author_id) VALUES($1, $2, $3) RETURNING *',
        [title, body, authorId]
      );
      return result.rows[0];
    },
    updatePost: async (_, { id, title, body }, { pgClient }) => {
      const result = await pgClient.query(
        'UPDATE posts SET title = COALESCE($2, title), body = COALESCE($3, body) WHERE id = $1 RETURNING *',
        [id, title, body]
      );
      return result.rows[0];
    },
    deletePost: async (_, { id }, { pgClient }) => {
      const result = await pgClient.query('DELETE FROM posts WHERE id = $1', [id]);
      return result.rowCount > 0;
    },
  },
  User: {
    posts: async (user, _, { pgClient }) => {
      const result = await pgClient.query('SELECT * FROM posts WHERE author_id = $1', [user.id]);
      return result.rows;
    },
  },
  Post: {
    author: async (post, _, { pgClient }) => {
      const result = await pgClient.query('SELECT * FROM users WHERE id = $1', [post.author_id]);
      return result.rows[0];
    },
  },
};
```
```javascript:server.js(サーバーのセットアップ)
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const { Pool } = require('pg');

const pgClient = new Pool({
  connectionString: process.env.DATABASE_URL || "postgres:///my_database",
});

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

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 5000 }, () =>
  console.log(`Server ready at http://localhost:5000${server.graphqlPath}`)
);
```

# 感想

いかがでしたでしょうか？
すぐにAPIを試作しないといけないPoCや先行開発フェーズにおいては、慣れないGraphQLのスキーマとリゾルバを定義している時間がもったいないですよね。
そんなときは、PostGraphileでPostgreSQLテーブル定義して、作った方が何倍も効率的にAPI試作ができそうです。

みなさんもぜひ使ってみてください！読んでいただきありがとうございました。

# 参考文献

- [GraphQL公式ドキュメント](https://graphql.org/learn/)
- [Hasura公式ドキュメント](https://hasura.io/learn/ja/graphql/hasura/introduction/)
- [PostGraphile公式ドキュメント](https://www.graphile.org/postgraphile/introduction/)
- [GraphQLサーバのHasuraを使ってみた](https://zenn.dev/rescuenow/articles/30135e098d8d87)
- [Hasuraを使ってみた](https://qiita.com/kyamamoto9120/items/e0f3f15dac9ff532e202)
- [REST vs. GraphQL vs. gRPC – Which API to Choose?](https://www.baeldung.com/rest-vs-graphql-vs-grpc)
- [When to use gRPC vs GraphQL](https://stackoverflow.blog/2022/11/28/when-to-use-grpc-vs-graphql/)
- [API architecture showdown - Rest vs graphQL vs gRPC](https://pradeepl.com/blog/api/rest-vs-graphql-vs-grpc/)