【サーバーレスでGraphQL】AWS AppSyncのSchemaからcodegenする

# AWS AppSyncに生成したSchemaからcodegenする
## はじめに
GraphQLやTypeScriptなど最近のフロントエンド開発は堅牢性を日々増していっているのを感じます。とくにRESTが抱えていた問題を一挙に解決する目的でつくられた**GraphQL**のエコシステムはこれまでにない開発体験を与えてくれます。

そんな便利なGraphQLですがAWSの[AppSync](https://aws.amazon.com/jp/appsync/)では、それをサーバーレスで提供してくれます。サーバーレスでGraphQLを提供できるようなフレームワークはまだ多くないのでとてもいいですね。

ということでこの記事ではAppSyncで作ったAPIから出力されたスキーマを用いてフロントエンドで利用するコードを生成しようと思います。

## 環境

| 項目 | バージョン |
| --| -- |
| node | 16.13.2 |
| aws-cli | 2.4.15 |
| cdk | 2.10.0 |

## 実装概要
### AppSync
今回は純粋なAPIとしてのみ利用したいのでAmplifyを利用せず、AppSyncのみを作成します。
またIaCなどは利用せず、コンソール画面上からポチポチやっていきます。

### Apollo Client React Hooks
スキーマ情報からGraphQL管理ライブラリであるApollo ClientのReact Hooksを生成します。
型補完などが動作するかも確認します。

## 実装
### AppSync
画面でポチポチつくっていきます。
中身はなんでもいいのでイベントアプリというサンプルプロジェクトで開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/97f8e4a4-24f1-58c0-9754-9e1424fc9218.png)

続けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/b60f6872-8b82-01bf-f35d-d645ba781d32.png)

いい感じに複雑そうなスキーマができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/8eefe964-c0b1-5366-f909-178714f2fb4b.png)

フロントのコード生成で利用するので`設定 > API Details`でAPI URL、API ID、API KEYをコピーしておきます。
![Screen Shot 2022-02-03 at 21.29.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/27101f24-2c34-9730-befa-1fd64e561a89.png)


### Apollo Client React Hooks

[cra](https://create-react-app.dev)を利用してReactのテンプレートを作成します。

```sh
npx create-react-app apollo-codegen --template typescript
cd apollo-codegen
npm install graphql @apollo/client @graphql-codegen/cli @graphql-codegen/introspection @graphql-codegen/typescript @graphql-codegen/typescript-react-apollo @graphql-codegen/typescript-operations dotenv
```

コード生成用のプログラムを記述します。
下記のファイルでは、`src/graphql`フォルダ下に記述した`xxxx.gql`を読み込み、その型情報やhooksを`src/generated/graphql.tsx`に出力するようになっています。

```.graphql.codegen.js
module.exports = {
  schema: [
    {
      [process.env.REACT_APP_GRAPHQL_API_URL]: {
        headers: { "X-API-KEY": process.env.REACT_APP_GRAPHQL_API_KEY },
      },
    },
  ],
  documents: ["./src/graphql/**.gql"],
  overwrite: true,
  generates: {
    "./src/generated/graphql.tsx": {
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-react-apollo",
      ],
      config: {
        skipTypename: false,
        withHooks: true,
        withHOC: false,
        withComponent: false,
      },
    },
    "./graphql.schema.json": {
      plugins: ["introspection"],
    },
  },
};

```

`REACT_APP_GRAPHQL_API_URL`と`REACT_APP_GRAPHQL_API_KEY`は`.env`に記述します。
AppSyncの設定画面からコピーします。(`REACT_APP_`とついているのは後のReactのプログラムでも流用するためです。)

```plaintext
REACT_APP_GRAPHQL_API_URL=https://xxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql
REACT_APP_GRAPHQL_API_KEY=xxxxxxxxxx
```

package.jsonのscriptsに`qqlcodegen`コマンドを追加します。

```json
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "gqlcodegen": "graphql-codegen --require dotenv/config --config .graphql.codegen.js"
  },
```

最後にサンプルとして以下のGraphQLファイルを追加します。

```src/graphql/query.graphql
query ListEvents {
  listEvents {
    items {
      id
      name
      description
      when
      where
    }
  }
}

```

## コード生成
以下のコマンドでコードを生成してみます。

```sh
npm run gqlcodegen

> apollo-codegen@0.1.0 gqlcodegen
> graphql-codegen --require dotenv/config --config .graphql.codegen.js
  ✔ Parse configuration
  ✔ Parse configuration
  ✔ Generate outputs
```

`src/generated/graphql.tsx`を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/ef70af94-b284-06be-c926-e16e9696f526.png)

hooksやその型情報が生成されていることがわかります。

## 動作確認
ApolloProviderを記述して

```ApolloProvider.tsx
import React from "react";
import {
  ApolloClient,
  ApolloProvider as BaseApolloProvider,
  InMemoryCache,
} from "@apollo/client";

const client = new ApolloClient({
  uri: process.env.REACT_APP_GRAPHQL_API_URL!,
  // API_KEYをフロントで使うのは基本的にNGだがテスト的に使う
  headers: { "X-API-KEY": process.env.REACT_APP_GRAPHQL_API_KEY! },
  cache: new InMemoryCache(),
});

const ApolloProvider: React.FC = ({ children }) => {
  return <BaseApolloProvider client={client}>{children}</BaseApolloProvider>;
};

export default ApolloProvider;
```

index.tsxで呼び出して

```index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import ApolloProvider from "./ApolloProvider";

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById("root")
);
```

App.tsxでlistEventを呼び出してみます。

```App.tsx
import React from "react";
import "./App.css";
import { useListEventsQuery } from "./generated/graphql";

function App() {
  const { data } = useListEventsQuery();

  return (
    <div className="App">
      <ol>
        {data?.listEvents?.items?.map((item) => {
          return (
            <li key={item?.id}>
              {item?.name} 場所:{item?.where} 日時:{item?.when}
            </li>
          );
        })}
      </ol>
    </div>
  );
}

export default App;
```

型補完もいい感じに効いてそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/6ec74af5-4f59-281e-6dd8-502b004346d3.png)


DynamoDBに適当なデータをいれて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/3f8090a5-1fd1-cf09-6c80-243b96daaba9.png)


`npm start`すると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235240/476c4d83-46df-adde-939c-f228fea72a47.png)


いい感じに動作してそうです。


## 最後に
AppSyncでもGraphQLの強力なエコシステムの恩恵を受けることができそうです。GraphQLは今後も積極的に採用していきたいですが、まだまだサーバーレスでの選択肢は多くないので有効な選択肢になると思います。
またAppSync自体の管理ですが[Serverless Framework](https://www.serverless.com/plugins/serverless-appsync-plugin)や[Amplify](https://aws.amazon.com/jp/amplify)、[CDK](https://aws.amazon.com/jp/cdk)などさまざまあります。個人的にはAmplifyはGUIなどもあり、かなり強力ですが、猛烈にロックインする必要があるので少し躊躇するところでもあります。このあたりも知見をためていきたいです。