Prismaも悪くないが、Hasura GraphQL Engineでも超高速でGraphQL Serverを実装

# 言いたいこと
- GraphQLのAPIを自前で実装するのは面倒なんでこういうのに頼って楽したい
- [Prisma](https://github.com/prisma/prisma)もいいと思うが、PostgreSQLなら[Hasura GraphQL Engine](https://github.com/hasura/graphql-engine)も結構いい
- Prismaと違ってHasura GraphQL Engineはクライアントから直接呼ばれることも可能だが、凝った実装しようとするとやはりClient専用のGraphQL Serverは必要

# Hasura GraphQL Engineって何？
PrismaはPostgreSQLだけでなく、MySQL等のその他のRDBMSに対応したGraphQL Serverを構築しますが、Hasura GraphQL EngineはPostgreSQL専用のGraphQL Serverを構築します。

# 構築方法
結構ドキュメントがしっかり作られていて、正直この記事書く必要ないと思うくらいです。英語アレルギーの人は私の拙い日本語記事を参考にやってみてくれると幸いです。

## Hasura GraphQL Engineのセットアップ
公式ドキュメントではHerokuを作った方法とDocker(-compose)を使った方法の2種類が記載ありますが、明らかにDockerを使った方が楽なので、その方法で本記事は記載します。

```sh
$ mkdir hasura_sample
$ cd hasura_sample
$ wget https://raw.githubusercontent.com/hasura/graphql-engine-install-manifests/master/docker-compose/docker-compose.yaml
```

まずは`wget`で更新サンプルの`docker-compose.yml`を取得します。中身は特に凝った内容ではないです。

```yml
version: '3.6'
services:
  postgres:
    image: postgres
    restart: always
    volumes:
    - db_data:/var/lib/postgresql/data
  graphql-engine:
    image: hasura/graphql-engine:v1.0.0-alpha13
    ports:
    - "8080:8080"
    depends_on:
    - "postgres"
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:@postgres:5432/postgres
    command:
      - graphql-engine
      - serve
      - --enable-console
volumes:
  db_data:
```

ただ、私の環境では`version 3.6`に対応していないので、`version 3.3`に落としたりしております。

そのまま`docker-compose up`叩けば終わりです。（公式では`-d`でデーモン起動にしてますが、ログを見たいのでデーモン起動なしで起動しています。）

## PostgreSQLにテーブル作成
GraphQLで取得するデータの格納先となるPostgreSQLのテーブルを作成する必要があります。
もちろん既存のDBに接続する場合はこの処理は不要です。
先程のdocker-composeを立ち上げた状態ならば`http://localhost:8080/console`でHasura GraphQL Engineのコンソールにアクセスできるはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/5d9dc9db-57d0-a084-0059-b2520cf4fd72.png)

まずはGraphQLで取得するデータを格納するテーブルを作成します。
画面上部にある`DATA`タブをクリックし、schemaを作成する画面を表示します。
（余談ですが、画面はReactでできてますね）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/dbd02ebf-501b-9a35-20d7-0880f76add27.png)

画面中央にある``Create Table`ボタンをクリックするか、左メニューにある、`Add Table`をクリックすることでschemaの定義画面が開けます。そこで必要な情報を入力することでテーブルが作成されます。今回は例として、コミュニティというテーブルを作成し、カラムとしては`id`と`name`と`description`の3カラムを用意するものとします。（Railsを意識してテーブルを作ってます）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/882b5d52-a33e-b594-e9bc-7859471f2091.png)

次に作成したコミュニティに子テーブルとなるイベントテーブルを以下のように作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/519efbca-f537-4fa2-28b2-a170ea7aae67.png)

大事なのはコミュニティテーブルとリレーションを貼る`community_id`です。

## テーブルのリレーションを作成
作成したコミュニティテーブルとイベントテーブルのリレーションを定義し、GraphQLで解釈できるようにします。まずはイベントテーブルに`Foreign Key`を作成します。作成したイベントテーブルを開き、`Modify`タブで以下のように設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/389ee20d-327e-0982-a997-302968bd2568.png)

これで`Foreign Key`の設定は完了したので、次にコミュニティテーブルからイベントテーブルへのリレーションを作成します。コミュニティテーブルを開き、`Relationships`タブを開くと`Add`ボタンがあると思うので、クリックすると名称の登録が開きます。ここの名称は1コミュニティから見たイベントデータなので、`events`としておきます。要は後に使用するGraphQL queryで1communityに対するイベントデータを取得する名前になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/9aae2f00-4e1b-0181-fd94-411f3a36f739.png)

これであらかたのテーブル定義が完了です。もちろんイベントテーブルからコミュニティテーブルを参照するようなGraphQL queryが必要な場合は逆方向のリレーション登録も必要です。

## GraphQLの実行
画面上部にある`GRAPHQL`タブを開くことによってGraphQLを実行することが可能です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/e6d2c5d6-41c8-2df8-6495-ffd23943cb27.png)

という感じに既にGraphQL Serverが完成しています。データの絞り込み(`where`)やソート(`order_by`)のSQLを書く間隔で使用できて、私的にはとてもわかりやすい。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/77b1736b-e205-179b-a3e2-6b6195af8e41.png)

もちろん`like`検索も可能です。

通常のCRUDならとても簡単に実装できてしまいました。

## 認証・認可
### 認証
認証が必要ならばwebhookで実装することも可能。
https://docs.hasura.io/1.0/graphql/manual/auth/index.html

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/1425b2c1-a3b0-6f28-322c-ef2c1430a233.png)

もちろん、webhookで受けた先の認証は好きに行なえますが、AWS cognitoを使ってもよし、firebaseも使ってもよしで実装できるならなんでも可能です。

### 認可
認可もある程度は定義できそうです。
https://docs.hasura.io/1.0/graphql/manual/auth/common-roles-auth-examples.html

例を交えて設定例を下記に書きます。
例えばwebhookで認可するユーザIDが判明した場合に、そのユーザのみが参照できるデータを表示するということを想定したいと思います。

まずは作成したコミュニティテーブルに作成者となる`user_id`を追加します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/219555b7-c846-909f-275a-47424fa3bace.png)

次に`Permissions`タブを開き`user`というロールを追加した後に以下のように`X-HASURA-USER-ID`というパラメータで先程作成した`user_id`が一致するもののみ表示する設定にします。この`X-HASURA-USER-ID`はGraphQLのAPI呼び出しの際にhttp headerとして付与する必要があります。もし認可にwebhookを使っているならwebhookのレスポンスに設定するといいです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/e3733ed6-5293-eba3-66c4-36e5c61616ea.png)

現状の設定ではhttp headerに何もつけなければすべて取得する認可設定となっているので、以下のようにすべてのデータが取得できてしまいます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/e48cbdaf-3821-dc15-1d25-c7fe44da0019.png)

ところが、先程の`user`ロールとデータにあるユーザIDをhttp headerに設定すると一部のデータしか参照することができなくなります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/4a15f110-2c2c-3d1f-6254-ffec67eed558.png)

ただし、このままで子テーブルのデータを同時に取得しようとするとエラーとなってしまいます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/6cddbb1b-6cc9-61e6-b9f4-0145b6ff19bc.png)

これはイベントデータに認可の権限が設定されていないためにデータ取得が行えないためのエラーが発生しているためです。なのでイベントテーブルに認可の設定を追加します。
今回はコミュニティテーブルの`user_id`でデータの参照をコントロールしているためにイベントテーブルからも同様にします。そこでまずはイベントテーブルからコミュニティテーブルのリレーションを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/6085d02b-16c3-a9a5-b446-1497ca7cb716.png)

`Relationships`タブを開けば`Add`ボタンをクリックしてから名前を入れれば完了です。
最後のイベントテーブルの`Permissioins`タブから**コミュニティテーブルの`user_id`にてデータの絞り込みを行う設定**にすれば完成です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/9ad006ee-833b-da87-895d-3346e02b3514.png)

そうすることによってイベントテーブルのデータも正常に取得できるようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/70363/53da730a-03f7-6d58-ea6a-297e2cbe0367.png)


## その他実際に使用する場合に出てくるであろう要件

集計関数が必要な場合はviewを定義すれば可能です。
https://docs.hasura.io/1.0/graphql/manual/queries/aggregations.html

※その他実務上で必要になってきそうなものがあればココに追記します。

# 最後に
これだけでGraphQLのAPIが作成できてしまうので、バックエンドの構築はとても楽です。フロント専門のエンジニアでもある程度は構築できることでしょう。AWS AppSyncも悪くないですが、既存のデータをGraphQL対応したり、検索も強固なものにするにはRDBMSを用いたGraphQLを構築したいはずです。
ただ、書いた通り通常のCRUDは簡単に構築できるのですが、集計データを保持する場合にクライアントが集計処理をしないといけないのはアプリケーションの実装上避けたい場合もありますので、そういう場合は考えないとダメですね。
そのような場合や、複数のマイクロサービスからデータを取得し合算してクライアントに返す場合は、Prismaみたいに途中に外部公開用のGraphQL Serverを立てて、そこで処理するのがベターでしょうか。