Hasura v2.1.0で導入されたActions transformerを使って、好きなREST APIをGraphQLに繋げてみよう

## はじめに
https://hasura.io/

Hasuraという、DBテーブルから自動的にGraphQL APIを作成してくれるGraphQLサーバーがあります。


「Hasuraって何？」という方は、[「多機能なGraphQLサーバー: Hasura GraphQL Engineを知っておきたい人が読む記事」](https://qiita.com/sho-hata/items/2dbd41be42662007071e)を読んでいただけると幸いです。


さて、先日このHasuraのコアとなる、`hasura/Graphql-engine`のマイナーアップデート版である**v2.1.0が公開されました。**

https://github.com/hasura/graphql-engine/releases/tag/v2.1.0

v2.1.0のアップデート内容には、`hasura metadata deploy`コマンドの登場や`Event Trigger transforms`機能など、かなり多くの機能追加・修正が加えられています。
便利な機能が目白押しですが、中でも目玉は**Actions transformer**機能でしょう。

https://hasura.io/docs/latest/graphql/core/actions/transforms.html#url

この機能の登場により、**HasuraからリクエストメソッドやURLを自由に組み立てて、様々なREST APIへ接続ができるようになりました。**

本記事では、この`Actions transformer`の概要紹介と、ちょっとしたサンプルを使って簡単な使い方の説明を行います。
公式ドキュメントにも使い方は書いてあるのですが、少し説明が足りない部分がある＆機能が複雑でわかりにくい部分があるため、日本語の情報源として役に立てればいいかなと思い、本記事を公開しました。

### 本記事の要約
- Hasura v2.1.0のアップデートで、**Actions transformer機能が追加された**

- 今までは`POST`でしかREST APIに接続できなかったが、この機能により`GET`や`PATCH`などのリクエストメソッドでHTTPリクエストができるようになった

### 環境
<img src="https://img.shields.io/badge/ Hasura GraphQL Engine-2.1.0-1EB4D4.svg?logo=hasura&style=plastic">

## おさらい: `Hasura Action`
Hasuraは、DBスキーマから基本的なCRUDと集計(aggregate)スキーマを自動生成してくれます。ではユーザー独自のロジックを組み込むにはどうするかというと、外部APIを使用して拡張するという方法をとります。

`Hasura Action`は、外部REST APIと接続し、そのレスポンス結果をGraphQLのスキーマに埋め込めるという機能です[^1]。

![Screenshot 2021-12-21 22.10.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/8b128a4e-1b91-7d75-5904-211baa4515e4.png)
> 図: https://hasura.io/docs/latest/graphql/core/actions/index.html

このHasura Actionはとても便利な機能なのですが、REST APIとの接続をwebhoookで行うため、以下のような制限がありました。

- リクエストメソッドは`POST`のみ
- エンドポイントを動的にできない
    - 例：`/api/v2/users/:user_id/followers`の`:user_id`の部分を動的に変えることができない

このため、**接続先APIをHasuraに合わせて改修する必要がある・外部サービスのAPIが使えない**などの制限があり、お世辞にも不自由なくREST APIと接続できるとは言いづらい状態でした。

ですが、この度のv2.1.0アップデートにより、接続先のREST APIにかかる制限がほとんどなくなりました。

## v2.1.0で導入された`Actions transformer`
`Actions transformer`は、Hasura Acitonで接続するREST APIへのリクエストをカスタマイズできる機能です。
リリースノートやドキュメントを読むと、「Actionによって生成されるHTTPリクエストを変換する」[^2]とあり、webhookリクエストをうまく変換しているようです。


カスタマイズできる接続先APIの設定は、下記に述べるようにかなり多岐にわたります。

 - エンドポイント
 - クエリパラメータ
 - リクエストメソッド
 - リクエストヘッダー
 - リクエストボディ
 - Content Type

設定が細かくできるようになったため、`Hasura Action`の設定画面も以前と見比べるとリッチになりました。

| v2.1.0以前 | v2.1.0以後 |
|:-----------:|:------------:|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/16e66ce1-9b36-11c0-1cfa-614bd0a7cb49.png" >|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/7d1322c9-df13-9b75-69d0-afee26de7791.png">|

次章で、`Actions transformer`機能でカスタマイズできる設定を１つづつ確認していきます。

:::note info
【コラム】
`Actions transformer`には関係しませんが、Action設定画面に新しく増えた「Declared Global Types」という部分で、
他のActionで作成したスキーマの型が表示されるようになりました。
今まで、それぞれのActionでどんな型が定義されているかはActionの設定画面に行かないとわからなかったため、一眼でどのような型があるかを確認できるようになり非常に便利になりました。
:::
![Screenshot 2021-12-22 0.04.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/f5191e71-4d15-00b4-f428-b494cbd70ce3.png)


##設定できること
### リクエストURL
#### Webhook(HTTP/S)Handler
リクエストURLは、「Webhook(HTTP/S)Handler」という設定項目で行います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/0094426b-dbb1-2db6-42d2-300fcb991dfe.png" width=50%>

動的に変化するURLではない限り、対象APIのエンドポイントに含まれるパスの情報はここで設定します。
従来通り、`ACTION_BASE_URL`という環境変数を設定し、`{{ACTION_BASE_URL}}`とすることで環境変数を埋め込むことができます。

動的に変化する場合やクエリパラメータを含めたい場合は、後述する「Request Options Transformation」で行います。
### リクエストメソッド・クエリパラメータ・（動的な）リクエストURL
「Request Options Transformation」という設定項目で、上記項目を設定できます。

![Screenshot 2021-12-22 0.15.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/35054e91-3111-867e-9b07-54db0edacb95.png)
#### Request Method
ここでは、リクエストメソッドを**GET / POST / PUT / PATCH / DELETE**から選択できます。

#### Request URL Template
ここでは、動的なURLを設定できます。
**`{{$base_url}}` + 「動的なパス**」というフォーマットになっており、`{{$base_url}}`には、先述した「Webhook(HTTP/S) Handler」で設定したURLが展開されます。

「動的なパス」部分には、Hasura Actionの変数を展開して設定ができます。ここで使用できるHasura Actionの変数は以下になります。

| 名前 | 説明 | 型 | 書き方例　|　補足|
|:-----------:|:-----------:|:------------:|:------------:|:------------:|
|$body | Hasura Actionで作成したInputスキーマ | Object | `{{$body.input.arg}}`|書き方例は、以下のような引数を持つActionスキーマから取得する例SampleAction(arg: String!):|
|$session_variables|クライアントから送られてくるHeaderに入っているsession_variables|Array|{{$session_variables['x-hasura-user-id']}}|書き方例は、session_variablesの中にある`x-hasura-user-id`から取得する例

変数展開ができることにより、「Action引数の値をパスの一部にする」というような、自由なパス設計が可能になりました。

#### Query Params
ここでは、クエリパラメータを設定できます。設定はkey_value形式で行います。
![Screenshot 2021-12-22 0.52.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/9bffd455-c4eb-2648-7f27-ee43724e7735.png)

下記の図のように、クエリパラメータのKeyとvalueにも、Actionの変数を使用できます。

![Screenshot 2021-12-22 0.54.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/96e8fa67-1cab-9cb0-fa09-385fcdd69092.png)

#### Payload Transformation
今までは、以下のようなリクエストボディの形式でしかAPIにリクエストができませんでした。`"input"`という階層が挟まっています。

```GraphQL
input {
  # Actionの引数が入る
  args: {
  ...
  }
}
```
v2.1.0からは、Hasuraから送るリクエストのボディを自由に設定ができます。
Actionの変数を利用でき、`Configure Request Body`エディタで記述します。どのように変換されるのかは、下の`Transformed Request Body`で確認ができます。
![スクリーンショット 2021-12-25 17.40.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/57a2d88f-126e-a264-5160-e4be1c5a868d.png)



## 実際に試してみる
ここからは、実際にHasuraから外部REST APIに接続してみます。

このチュートリアルでは、「**Hasuraから[Qiita V2 API](https://qiita.com/api/v2/docs#get-apiv2usersuser_id)に接続し、指定したユーザーの情報を取得する」**までを行います。

今回のチュートリアルで作るサンプルコードは以下のGitHubリポジトリにまとめています。READMEに沿っていただければ、Hasuraの動作環境が立ち上がります。


https://github.com/sho-hata/hasura-action-transfomer-sample

### スキーマ作成
Actionのスキーマを設定します。まずは、Hasuraコンソール画面で[Actions] -> [create]で新しくActionを作成します。
続いて、Actionのスキーマを定義します。今回は以下のように作成しました。

```GraphQL
type Query {
  find_qiita_user (user_id: String!): qiita_user
}
```
`find_qiita_user`というアクション(リゾルバ)名で、`String`型（必須）の`user_id`を引数をとり、`qiita_user`という型の値を返します。

今回作成する`qiita_user`型は、Qiita APIのレスポンスの形にします。今回は[GET /api/v2/users/:user_id](https://qiita.com/api
/v2/docs#get-apiv2usersuser_id)を叩くため、ドキュメントを参考にしてスキーマを作成します。

```GraphQL
type qiita_user {
  id: String!
  description: String!
  followers_count: Int!
  followees_count: Int!
  name: String!
}
```
このスキーマは、以下のフィールドを保持しています。

- ユーザーID
- 自己紹介コメント
- フォロワー数
- フォロー数

ここまでで、Actionは以下の図のようになります。
![スクリーンショット 2021-12-25 17.49.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/f76b78f8-64f3-038c-4770-0b56384a7905.png)


### 接続設定
接続先APIを設定します。
ベースとなるURLの設定項目**「Webhook (HTTP/S) Handler」**には、今回叩くQiita APIの動的パス以外の部分を設定します。

- `https://qiita.com/api/v2`

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/3717af9e-1fa1-4d72-9aef-ecfde4a8e716.png" width=50%>

> ヘッダーやタイムアウトの設定項目「**headers**」・「**Action Custom Timeout**」は今回設定しません（トークンが必要なAPIや、タイムアウトを制限したい場合には設定します）。また、「**Sample Context**」・「**Payload Transformation**」も今回は設定しません。

残るは、リクエストメソッドや動的パスの設定項目「**Request Options Transformation**」です。
Qiita APIのユーザー取得APIのエンドポイント`GET /api/v2/users/:user_id`に合わせ、以下のように設定します。

- `/users/{{$body.input.user_id}}`
![Screenshot 2021-12-24 0.48.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/5baedb37-570a-bc16-d295-9bccc0abe5c1.png)


APIに合わせて、リクエストメソッドは`GET`とします。
また、`{{$body.input.user_id}}`とすることで、アクションの引数`user_id`をurlへ埋め込むようにしています。

小ネタとして、[Payload Transformation]->[**Preview**]には、実際にアクセスするURLが表示されます。
「Sample Input」の`user_id`フィールドの値を変えてみて、発行されるURLをチェックしましょう。

### 動作確認
実際は`appllo`や`relay`などのクライアントライブラリを用いることになりますが、今回の動作確認はHasura Console上のGraphiQLエディタ上で行います。
![Screenshot 2021-12-24 0.52.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/38d73140-a277-edb5-5c2d-dd99a761617b.png)

Qiita APIを叩いて取得したデータがちゃんとレスポンスに入っていますね！🎉

## 注意点
~~現在(2021/12/20)、APIのレスポンスのスキーマはネストできないという問題があります。~~
2022/02追記
Hasura v2.2.0のリリースにより、入れ子構造になった返り値を返せるようになりました！

詳細は以下を参考にしてください。

https://hasura.io/blog/announcing-hasura-v2-2-0/

## おわりに
いかかでしたでしょうか。`Actions transformer`の登場により、Hasura Actionで接続するAPIの幅がかなり広がりました。
本記事ではQIita APIを取り上げましたが、Hasura公式にData Hubという公式サンプル集があります。FirebaseやElasticSearchなどのサンプルがありますので、軽く読んでみるのも良いでしょう。

https://hasura.io/data-hub/action-transforms/

[^1]: 本題と逸れるため説明を省きましたが、RESTではなく外部GraphQL APIと接続する[Hasura Remote Schemas](https://hasura.io/docs/latest/graphql/core/remote-schemas/index.html)という機能もあります。

[^2]: 参考：https://hasura.io/docs/latest/graphql/core/actions/transforms.html#action-transforms