GraphQL/StepZen 入門

本記事では、ハンズオンでの実施を想定して、StepZen を使用して、REST API とリレーショナルデータベースから GraphQL API を作成します。また、複数のデータソースを使用した GraphQL API の作成も体験します。
詳細については、公式ドキュメント（[StepZen Product Documentation](https://stepzen.com/docs)）を確認ください。

# 0. StepZen のセッティング

## 0.1 アカウント作成

まず始めに、StepZen のウェブサイトにアクセスして、アカウントを作成しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/2bd01f06-88d3-2811-d4b4-47e8415dbe13.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/7a70c9f9-a96e-7f1d-5b8f-a3f50c73c818.png)

## 0.2 StepZen CLI インストール

StepZen CLI を使用すると、ローカルマシンから StepZen サービスに直接アクセスできます。CLI をインストールするには、次の npm コマンドを実行します。（Node.jsがインストールされていることを前提としています。）

```bash
npm install -g stepzen
```

## 0.2 StepZen CLI でログイン

インストール後、以下のコマンドを使って StepZen にログインします。

```bash
stepzen login
```

下記プロンプトが出てくるので、それぞれ[StepZen Dashboard](https://dashboard.stepzen.com/account)の Account ページから取得したアカウント名と Admin Key を入力ください。

```bash
What is your account name?: ACCOUNT_NAME
What is your admin key?: ADMIN_KEY
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/643e271e-f1c5-cf35-6a4d-3565be2dbb95.png)

これで StepZen のセッティングが完了しました。

## 0.3 GraphQL Playground のインストール

必要に応じて、デバッグ用にクライアントアプリをインストールします。
ブラウザからもクエリできるので、必須ではありません。
また、他のクライアントアプリでも構いません。

下記サイトを参考に以下のコマンドを実行して、GraphQL のクライアントアプリである GraphQL Playground をインストールします。（HomebrewをすでにインストールしているMac ユーザーを想定しています）
[graphql/graphql-playground: 🎮 GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration)](https://github.com/graphql/graphql-playground)

```bash
brew install --cask graphql-playground
```

# 1. GraphQL でクエリ

## 1.1 Snowtooth GraphQL API での操作

まず、架空のゲレンデのリフト情報とトレイル情報を管理している Snowtooth GraphQL API（https://snowtooth.moonhighway.com/ ）にアクセスします。ここで、以下を参考にGraphQL の基本的な操作、Query、Mutation、Subscription を試してみましょう。

```graphql
query {
  Lift(id: "astra-express") {
    name
    status
  }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/cc6e887a-a11d-c1ef-332a-f9fb0da8c236.png)

```graphql
mutation {
  setLiftStatus(id: "astra-express" status: CLOSED) {
    name
    status
  }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/7d441224-532b-fff5-e1eb-d5aca5b1a85d.png)

```graphql
subscription {
  liftStatusChange {
    name
    capacity
    status
  }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/272c9cba-1224-9f49-8842-48ee38f9b675.png)

## 1.2 curl での操作
ここで、GraphQLがREST APIと同様にHTTPで通信していることを理解するために、curlでデータを取得してみます。（Subscriptionはwebsocketを使用していますが。）
Snowtooth GraphQL API の Playground (https://snowtooth.moonhighway.com/ )の右上に`COPY CURL`ボタンがあり、それを押すことで下記のような curl コマンドをコピーできます。ターミナルに貼り付けて実行することで、Playground と同じようにデータを取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/889fb5ac-2b60-5ee1-0f95-1512b2873289.png)

```bash
curl 'https://snowtooth.moonhighway.com/' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: https://snowtooth.moonhighway.com' --data-binary '{"query":"query openLift {\n  Lift(id: \"astra-express\") {\n    name\n    status\n  }\n}\n"}' --compressed
```

# 2. REST API の GraphQL 化

## 2.1 tutorial にある REST API を使って動作確認

StepZen のチュートリアルに従い、REST API の操作を行いましょう。これにより、REST API がどのように機能するかを理解することができます。
GraphQL 化する REST API として下記エンドポイントを使用します。

https://introspection.apis.stepzen.com/customers

ブラウザや curl コマンドでアクセスすると下記のような json ファイルを取得することができます。

```json
[
  {
    "address": {
      "city": "Boston",
      "countryRegion": "US",
      "id": 1,
      "postalCode": "02122",
      "stateProvince": "Massachusetts",
      "street": "999 Croissant Bl"
    },
    "email": "lucas.bill@example.com",
    "id": 1,
    "name": "Lucas Bill",
    "orders": [
      {
        "carrier": "ups",
        "createdAt": "2020-08-04",
        "customerId": 1,
        "id": 19,
        "shippingCost": 2,
        "trackingId": "1Z881F5V0397269080"
      },
      {
        "carrier": "ups",
        "createdAt": "2020-08-01",
        "customerId": 1,
        "id": 20,
        "shippingCost": 1,
        "trackingId": "1Z4V180W0324616505"
      }
    ]
  }
]
```

以下のコマンドを実行することで、GraphQL API の構成ファイルが生成されます。

```bash
stepzen import curl https://introspection.apis.stepzen.com/customers --query-name "customers" --query-type "Customer" --name "customers"
```

:::note warn
追記（2023/07/20）：上記コマンドでエラーが出るようになっていました。もしエラーが出るようであれば、ポケモンAPI等の認証の不要な別のAPIで試してみてください。

```bash: pokeapi呼び出し
stepzen import curl https://pokeapi.co/api/v2/pokemon/ditto --query-name "pokemons" --query-type "Pokemon" --name "pokemons"
```
:::

生成された構成ファイルは以下のとおりです。
`index.graphql`および`curl/index.graphql`に記載の`@sdl`と`@rest`が GraphQL Directives を用いた StepZen 独自の設定となります。今回は、認証のない REST API を用いているため、非常にシンプルな構成ファイルとなっています。

```json:stepzen.config.json
{
  "endpoint": "api/getting-started"
}
```

```index.graphql
schema @sdl(files: ["curl/index.graphql"]) {
  query: Query
}
````

```curl/index.graphql
type Address {
  city: String
  countryRegion: String
  id: Int
  postalCode: String
  stateProvince: String
  street: String
}
type OrdersEntry {
  carrier: String
  createdAt: Date
  customerId: Int
  id: Int
  shippingCost: Int
  trackingId: String
}
type RootEntry {
  address: Address
  email: String
  id: Int
  name: String
  orders: [OrdersEntry]
}

type Query {
  myQuery: [RootEntry]
    @rest(endpoint: "https://introspection.apis.stepzen.com/customers")
}
```

構成ファイル完成後、以下のコマンドでデプロイします。

```
stepzen start
```

デプロイが完了したことを、[StepZen Dashboard](https://dashboard.stepzen.com/explorer)から確認してみます。
Explorer から、GraphQL API の仕様の確認やクライアント側のクエリの確認を実施できます。
StepZen Dashboard から API にアクセスする場合は、自動でヘッダーに apikey が入力されているため、クエリのみの編集で簡単に動作確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/a657a36d-1736-0ba7-31da-58ea3ebc1a8c.png)


## 2.2 Dashboard でアクセス数・レイテンシー等を確認

[StepZen Dashboard](https://dashboard.stepzen.com/)を使って、API のアクセス数やレイテンシー等のパフォーマンス指標を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/2c4f8537-ee13-73a8-5604-7970f8c9e3d9.png)


## 2.3 GraphQL Playground でクエリ

REST API を GraphQL 化した後は、再度 GraphQL Playground でクエリを実行してみましょう。
ブラウザからアクセスする場合と異なり、ヘッダーに apikey を記載する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/a2e42139-3896-400a-5fe7-65e766796576.png)

## 2.4 stepzen request コマンドでクエリ

最後に、StepZen CLI を使用して、GraphQL エンドポイントにクエリを送信します。以下のようなコマンドを使用します。

```bash
stepzen request 'query {myQuery {email}}'
```

以下のような json ファイルを取得することができます。

```json
{
  "data": {
    "myQuery": [
      {
        "email": "lucas.bill@example.com"
      },
      {
        "email": "mandy.jones@example.com"
      }
    ]
  }
}
```


# 3. RDB の GraphQL 化

## 3.1 tutorial にある postgresql で動作確認

StepZen チュートリアルに従い、PostgreSQL データベースの操作を行います。
PostgreSQL データベースはすでに準備されているものを使用します。接続情報は以下のとおりです。

```
host: postgresql.introspection.stepzen.net
database name: introspection
username: testUserIntrospection
password: HurricaneStartingSample1934
```

作業用フォルダにて、下記コマンドを実行します。

```bash
stepzen import postgresql
```

以下のような質問を対話的に聞かれるので、回答していきます。

```
? What would you like your endpoint to be called? api/opining-condor
? What is your host? postgresql.introspection.stepzen.net
? What is the username? testUserIntrospection
? What is the password? [hidden]
? What is your database name? introspection
? What is your database schema (leave blank to use defaults)?
? Automatically link types based on foreign key relationships using @materializer
 (https://stepzen.com/docs/features/linking-types) Yes
Starting... done
```

ここで、最後の質問である`? Automatically link types based on foreign key relationships using @materializer (https://stepzen.com/docs/features/linking-types)`に Yes で答えることにより、外部キー制約によるリレーションを階層化された GraphQL 定義に反映させることができます。
下記のようなフォルダが生成されます。中身についての説明は省略します。

```bash
hello-stepzen
├── config.yaml
├── index.graphql
├── postgresql
│   └── index.graphql
└── stepzen.config.json
```

以下のコマンドでデプロイします。

```bash
stepzen start
```

生成された API は以下のようなフィールドを含みます。

![Screenshot 2023-07-09 at 0.05.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/8d35e5e3-0460-f3c6-e273-85351e77f8b5.png)


また、以下のようにそれぞれ外部キーによるリレーションを用いて階層化されていることがわかります。

![Screenshot 2023-07-09 at 0.06.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184221/6448599f-10ff-7944-45f9-3370753c1811.png)

# 4. エンドポイントのカスタマイズ

## 4.1 複数データソースの統合

複数のデータソース（REST API や RDB）を一つのエンドポイントにデプロイするには、index.graphql ファイル内の `@sdl`に複数の`graphql`ファイルを指定します。

```index.graphql
schema @sdl(files: ["curl/index.graphql", "postgresql/index.graphql"]) {
  query: Query
}
```

## 4.2 エンドポイントをカスタマイズ

`@materializer` や`@dbquery` を使用すると、クエリの実行方法をカスタマイズできます。これにより、自動生成した構成ファイルでは取得できないようなクエリをデプロイできます。

例えば、以下のように`@dbquery`と SQL 文を組み合わせることで複雑なフィルタリングが可能です。

```graphql
type Query {
  getCustomerByEmailAndName(email: String!, name: String!): [Customer]
    @dbquery(
      type: "postgresql"
      query: """
      select * from "customer" where "email" = $1 and "name" = $2
      """
      configuration: "postgresql_config"
    )
}
```

また、以下のように`@materializer`を使用することで各フィールドのデータ取得方法を指定できます。

```graphql
type Customer {
  email: String!
  id: Int!
  name: String!
  orders: [Order]
    @materializer(
      query: "getOrderListByCustomerId"
      arguments: [{ name: "customerId", field: "id" }]
    )
}
```

```graphql
type Query {
  getOrderListByCustomerId(customerId: Int!): [Order]
    @dbquery(
      type: "postgresql"
      query: """
      select * from "order" where "customerid" = $1
      """
      configuration: "postgresql_config"
    )
}
```

# おわりに

本ハンズオンでは、StepZenのセッティングから、GraphQLでのクエリ方法、REST APIやRDBのGraphQL API化、カスタマイズ方法について説明しました。これで、StepZenを用いて簡単なGraphQL APIを構築できるようになったかと思います。認証認可やモック生成等、応用についても別途まとめたいと思います。

わかりにくいことがあれば、コメントいただければと思います。