GraphQLサーバとRESTサーバをさっと立ちあげて、実際に触ってみる。つづき。クライアント側を実装してみる


こんにちは。
最近、現場でGraphQLとRESTサーバを構築する機会があったので、そのとき得られたナレッジを備忘としてまとめているのですが、その続きです。

いままでGraphQL/REST APIのサーバ実装側をやってきましたが、GraphQLを呼び出すクライアント側も疎通しておこうと思い、やってみました。その際の備忘メモ。
 
つまるところ、
```console
$ cat query.json 
 {
     "query":
  "query GetUsers {
    users {
      id
      email
    }
  }"
 }
 
$ curl --data  @query.json --request POST   --header 'content-type: application/json'   --url http://localhost:3000/graphql 

{"data":{"users":[{"id":"u001","email":"kino1@example.com"},{"id":"u002","email":"kino2@example.com"},{"id":"u003","email":null},{"id":"u004","email":null},{"id":"u005","email":null},{"id":"u006","email":null}]}}
$
```
この疎通を、コードを書いてやってみるということですね。


## やってみる

### 前提

- [GraphQLサーバとRESTサーバをさっと立ちあげて、実際に触ってみる](https://qiita.com/masatomix/items/53003a34d413206bb619)
- [GraphQLサーバとRESTサーバをさっと立ちあげて、実際に触ってみる。つづき。関連のあるデータの取得](https://qiita.com/masatomix/items/d52ef0bdeb131f9dd6f7)
-  [GraphQLサーバとRESTサーバをさっと立ちあげて、実際に触ってみる。つづき。戻り電文の型を変更する](https://qiita.com/masatomix/items/a0e3fe3152e3a9eb64d6)

あたりをやっておくとサーバ環境や、開発環境が整いますので、やってみてください。


### まず、GraphQLサーバを起動しておく

下記 のコマンドでサーバ用のソースコードを取得し、
```
$ git clone --branch practice_union https://github.com/masatomix/spring-data-rest-example
```

下記の記事に従って、BackendのRESTサーバとGraphQLサーバを起動しておいてください。

https://qiita.com/masatomix/items/53003a34d413206bb619

さきほどのcurlコマンドで値が返ってくれば、サーバ起動は成功しています。

### 今回のソースを取得

```console
$ git clone --branch practice_union https://github.com/masatomix/apollo-client-example
$ cd apollo-client-example
$ yarn
yarn install v1.22.22
...
Done in 0.11s.
$ 
```

### 実行する1

一個目のソースです。

```typescript:src/index.ts
import { ApolloClient, ApolloError, InMemoryCache, gql } from '@apollo/client';
import { User } from './interfaces';

interface ResultData {
    // data に入ってくるオブジェクト名と、その型
    users: User[];
}

const fetchUsers = async () => {

    // Apollo Clientのインスタンスを作成
    const client = new ApolloClient({
        uri: 'http://localhost:3000/graphql', // GraphQLエンドポイントのURL
        cache: new InMemoryCache()
    });

    // GraphQLクエリを定義
    const queryGetUsers = gql`
        query GetUsers {
          users {
            id
            email
          }
        }
        `;

    // クエリを実行してデータを取得する関数
    try {
        console.log('Loading...')
        const result = await client.query<ResultData>({ query: queryGetUsers }); // 戻り値の型を ResultData と指定
        if (result.error) {  //実際はここはなさそう？ 
            console.error('Error fetching data:', result.error)
        } else {
            console.table(result.data.users)
        }
    } catch (error) {
        console.error('error:', error);
        if (error instanceof ApolloError) {
            console.error('GraphQL errors:');
            console.table(error.graphQLErrors);
            console.error(error.networkError);
        };
        console.log('例外発生')
    } finally {
        console.log('Loading complete.')
    }
}


const main = async (args: string[]) => {
    // データを取得する関数を呼び出し
    fetchUsers();
}

// コマンドライン引数を取得
const args = process.argv.slice(2);

// モジュールが直接実行された場合のみ処理を実行
if (require.main === module) {
    main(args);
}
```

中で呼ばれる``User``クラスなどは下記の通り定義しています。
```typescript:src/interfaces.ts
export type User = {
    id: string
    firstName?: String
    lastName?: String
    email?: String
    age: number
    companyCode: String
    company: Company
}

export type Company = {
    code: string
    name?: String
}


export type UserInput = {
    userId: string
    firstName?: string
    lastName?: string
    email?: string
    age?: number
    companyCode?: string
}

export interface UpdateUserVariables {
    user: UserInput
}

export interface SuccessResponse {
    success: boolean;
}

export interface FailResponse {
    success: boolean;
    message: string;
    error: string;
}

export type Response = SuccessResponse | FailResponse;
```

さて実行してみます。

```console
$ npx ts-node src/index
Loading...
┌─────────┬────────────┬────────┬─────────────────────┐
│ (index) │ __typename │   id   │        email        │
├─────────┼────────────┼────────┼─────────────────────┤
│    0    │   'User'   │ 'u001' │ 'kino1@example.com' │
│    1    │   'User'   │ 'u002' │ 'kino2@example.com' │
│    2    │   'User'   │ 'u003' │        null         │
│    3    │   'User'   │ 'u004' │        null         │
│    4    │   'User'   │ 'u005' │        null         │
│    5    │   'User'   │ 'u006' │        null         │
└─────────┴────────────┴────────┴─────────────────────┘
Loading complete.
$ 
```

実行出来ましたね！先のcurlの結果と見くらべてみてください。

### 実行する2

つづいてMutationです。

```typescript:src/index2.ts
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
import { FailResponse, Response, UpdateUserVariables } from './interfaces';

interface ResultData {
    // data に入ってくるオブジェクト名と、その型
    updateUser: Response
}

const isFailResponse = (arg: unknown): arg is FailResponse => {
    const r = arg as Response
    return !r.success
}

// クエリを実行してデータを取得する関数
const updateUser = async (variables: UpdateUserVariables) => {

    // Apollo Clientのインスタンスを作成
    const client = new ApolloClient({
        uri: 'http://localhost:3000/graphql', // GraphQLエンドポイントのURL
        cache: new InMemoryCache()
    });

    // GraphQLクエリを定義
    const mutationUpdateUser = gql`
        mutation UpdateUser($user: UserInput) {
          updateUser(user: $user) {
            ... on SuccessResponse {
              success
            }
            ... on FailResponse {
              error
              message
              success
            }
          }
        }
        `;

    try {
        const result = await client.mutate<ResultData, UpdateUserVariables>({
            mutation: mutationUpdateUser,
            variables: variables
        }); // 戻り値はResultData、引数は、UpdateUserVariablesの型であると指定

        if (result.errors) {
            console.error('GraphQL errors:', result.errors);
        } else {
            const response = result.data?.updateUser
            if (isFailResponse(response)) {
                console.log('Update user result:', response?.success);
                console.log('message:', response?.message);
                // console.log('error:', response!.error);
            } else {
                // 成功レスポンスの処理
                console.log('User updated successfully:', response?.success);
            }
        }
    } catch (error) {
        console.error('Error update data:', error);
    }
}

const main = async (args: string[]) => {
    // データを取得する関数を呼び出し
    updateUser({
        user: { userId: 'u001', companyCode: 'com003' }
    });
}

// コマンドライン引数を取得
const args = process.argv.slice(2);

// モジュールが直接実行された場合のみ処理を実行
if (require.main === module) {
    main(args);
}
```

実行してみます。

```console
$ npx ts-node src/index2
User updated successfully: true
$
```
実行出来たようですね。

## まとめ、、というか所感

- ライブラリ(Apollo Client)をつかえばGraphQLの電文を発行するのはわりと簡単でした。
- とくにMutationのほうにあるエラー処理について。unionで成功/失敗を型を変えて返してあげたら便利かな？(エラー判定しやすいかな？)って思ってたけど、普通に``isFailResponse``と自前で定義してチェックする[^1]くらいしか思いつかなくて、、たいして便利じゃないかもしれない。定石のやり方などもうすこし調べてみよ。。

以上、おつかれさまでした。

[^1]:  もしくは``__typename``を取得して、``(response as any).__typename``とかして型チェックするとか

## 関連リンク

- https://www.apollographql.com/docs/react 公式(CLIから呼び出すサンプルはあまりなかったけど)
- https://github.com/masatomix/apollo-client-example/releases/tag/practice_union 今回のソース