こんにちは。
最近、現場で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サーバとRESTサーバをさっと立ちあげて、実際に触ってみる
- GraphQLサーバとRESTサーバをさっと立ちあげて、実際に触ってみる。つづき。関連のあるデータの取得
- GraphQLサーバとRESTサーバをさっと立ちあげて、実際に触ってみる。つづき。戻り電文の型を変更する
あたりをやっておくとサーバ環境や、開発環境が整いますので、やってみてください。
まず、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くらいしか思いつかなくて、、たいして便利じゃないかもしれない。定石のやり方などもうすこし調べてみよ。。
以上、おつかれさまでした。
関連リンク
- https://www.apollographql.com/docs/react 公式(CLIから呼び出すサンプルはあまりなかったけど)
- https://github.com/masatomix/apollo-client-example/releases/tag/practice_union 今回のソース
-
もしくは
__typename
を取得して、(response as any).__typename
とかして型チェックするとか ↩