LoginSignup
5
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

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

Posted at

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

いままでGraphQL/REST APIのサーバ実装側をやってきましたが、GraphQLを呼び出すクライアント側も疎通しておこうと思い、やってみました。その際の備忘メモ。

つまるところ、

$ 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サーバを起動しておく

下記 のコマンドでサーバ用のソースコードを取得し、

$ git clone --branch practice_union https://github.com/masatomix/spring-data-rest-example

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

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

今回のソースを取得

$ 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

一個目のソースです。

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クラスなどは下記の通り定義しています。

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;

さて実行してみます。

$ 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です。

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);
}

実行してみます。

$ npx ts-node src/index2
User updated successfully: true
$

実行出来たようですね。

まとめ、、というか所感

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

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

関連リンク

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

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0