Apollo Client とは Apollo パッケージは、GraphQL を使ったクライアントアプリやサーバーを作成するためのライブラリ群です。
クライアントアプリを作るためのライブラリは、Apollo Client として @apollo/client
という NPM パッケージにまとめられています。
Web アプリのコンポーネントを作成するときは React がよく使われますが、Apollo は GraphQL を扱いやすくする React コンポーネント(ApolloProvider
、Query
、Mutation
、Subscription
)や React Hook 拡張(useQuery
) などを提供しています。
ここでは、Apollo Client パッケージを使用して、
Node.js アプリ(コマンドラインアプリの JS)から GraphQL API の実行 React アプリ(Web サイトの JS)から GraphQL API の実行 を行ってみます。
呼び出す GraphQL API は何でもよいのですが、今回は GitHub GraphQL API を利用することにします。
Node パッケージのインストール Apollo Client Apollo Client 関連のパッケージとしては、@apollo/client
と、それが使用する graphql
をインストールします。
Apollo Client のインストール ### yarn の場合
$ yarn add @apollo/client graphql
### npm の場合
$ npm install @apollo/client graphql
fetch ポリフィル Apollo クライアント内部の実装では、Web ブラウザの fetch API を利用しています。
React アプリから Apollo クライアントを利用する場合は問題ないのですが、コンソールで動作する Node.js アプリから利用する場合は、fetch API の埋め合わせ (polyfill) をするモジュールが必要になります。
ここでは、cross-fetch
パッケージをインストールしておきます。
fetch ライブラリのインストール ### yarn の場合
$ yarn add cross-fetch
### npm の場合
$ npm install cross-fetch
(コラム)古いパッケージ Apollo のクライアント系パッケージはいろいろな名前で提供されていましたが、すべて @apollo/client
以下にまとめられました。
次のようなパッケージはもう使用しません。
apollo-client:
すべて @apollo/client
に統合されたので使いませんapollo-boost:
次のように ApolloClient
コンストラクタを使えばOKです (import {ApolloClient} from '@apollo/client/core'
)graphql-tag:
GraphQL をパースするための gql
も @apollo/client
に統合されています (import {gql} from '@apollo/client'
)GitHub アクセストークンの発行 GitHub の GraphQL API を使用するには、下記のように POST リクエストのヘッダで GitHub アクセストークンを付加する必要があります。
authorization: Bearer a4304a13bc6cdd52509c90a38a676fce962ce518
アクセストークンを付加しないと、API 実行時に次のような HTTP 401 エラーが返されます。
ApolloError: Response not successful: Received status code 401
GitHub のアクセストークンは、下記の設定画面から Generate new token
ボタンを押すことで作成できます。
このとき、公開するデータのスコープ設定を行うのですが、何もチェックしなくても Public なリポジトリ情報やユーザー情報は取得可能です。
repo
にチェックを入れると、Private なリポジトリの読み書きが可能になり、user
にチェックを入れると、ユーザー情報の読み書きが可能になります。
read:user
にだけチェックを入れると、ユーザー情報の読み込みだけが可能になります。
残念ながら、Private リポジトリをリードオンリーで扱うスコープは存在しないようです(設計ミス?)。
スコープの詳細については、下記の GitHub ドキュメントを参考にしてください。
Node.js アプリから Apollo Client を使用する まずは、Node.js を使ったコマンドラインアプリから、ApolloClient
を使って GitHub GraphQL API を呼び出してみます。
下記のサンプルコードでは、指定した GitHub リポジトリ (apollographql/apollo
) の最新 Issue 5件分を取得しています。
このコードを実行できるようになれば、あとはクエリ部分を変更することでいろいろな情報を取得できます。
ポイントとしては、下記のあたりでしょうか。
cross-fetch/polyfill
モジュールをインポートして、Web ブラウザの fetch
関数をエミュレートするauthorization
ヘッダで GitHub のアクセストークンを指定するGraphQL クエリを gql`クエリ文字列`
という形で定義する サンプルコード main.ts import { ApolloClient , ApolloError , InMemoryCache , gql } from '@apollo/client/core' ;
import 'cross-fetch/polyfill' ; // グローバルな fetch 関数を定義する
// トークンは環境変数などから取得するのが常套手段
// const token = 'a4304a13bc6cdd52509c90a38a676fce962ce518';
const token = process . env . MYAPP_GITHUB_TOKEN ;
if ( typeof token === 'undefined' ) {
throw new Error ( 'MYAPP_GITHUB_TOKEN cannot be found' );
}
// GraphQL クライアントを生成
const apolloClient = new ApolloClient ({
uri : 'https://api.github.com/graphql' ,
headers : { authorization : `Bearer ${ token } ` },
cache : new InMemoryCache (),
});
// 発行する GraphQL クエリ
const searchQuery = gql `
query {
search(query: "repo:apollographql/apollo is:issue", type: ISSUE, first: 5) {
issueCount
nodes {
... on Issue { number title }
}
}
}
` ;
// クエリを発行
apolloClient . query ({ query : searchQuery })
. then ( result => handleApolloResult ( result . data ))
. catch ( handleApolloError );
// GraphQL レスポンスをハンドル
function handleApolloResult ( data : any ) {
const { issueCount , nodes } = data . search ;
console . log ( `Num of issues: ${ issueCount } ` );
for ( const issue of nodes ) {
console . log ( `* ${ issue . number } : ${ issue . title } ` );
}
}
// GraphQL のエラーをハンドル
function handleApolloError ( err : ApolloError ) {
console . error ( err . message );
}
実行結果 Num of issues: 176
* 937: Broken code display in blog for scaling GraphQL
* 934: Unknown directives
* 929: start apollo gateway
* 926: using REST services in apolo federation
* 909: Get a Graph Manager API key instructions need updating
React アプリから Apollo Client を使用する 次に、React アプリから Apollo Client を使って GraphQL API を実行してみます。
Apollo Client はもともと React アプリから使用することを想定して作られているため、とてもきれいに実装できます。
GitHub のアクセストークンは相変わらず必要なので、上記の説明 を参考にして取得しておいてください。
下記の例では、GitHub のアクセストークンを TypeScript コードに埋め込んでいますが、本番用の Web アプリでは、OAuth などの仕組みを使って動的にユーザーのアクセストークンを取得する必要があります。
OAuth 関連の実装を説明すると長くなってしまうので、そのあたりは下記の記事を参考にしてください。
サンプルコード 次の App
コンポーネントでは、前述のコマンドラインアプリと同様に ApolloClient
インスタンスを生成しています。
このインスタンスを、ApolloProvider
コンポーネントの client
プロパティにセットすると、下位のコンポーネントから簡単に GraphQL API を呼び出せるようになります(後述)。
App.tsx import * as React from 'react' ;
import { ApolloClient , ApolloProvider , InMemoryCache } from '@apollo/client' ;
import { Issues } from './Issues' ;
// 注意: 本番環境ではアクセストークンは OAuth などで動的に取得すること
const GITHUB_TOKEN = 'a4304a13bc6cdd52509c90a38a676fce962ce518' ;
// GraphQL クライアントを生成
const apolloClient = new ApolloClient ({
uri : 'https://api.github.com/graphql' ,
headers : { authorization : `Bearer ${ GITHUB_TOKEN } ` },
cache : new InMemoryCache (),
});
export const App : React . FC = () => {
return (
< ApolloProvider client = { apolloClient }>
< Issues />
</ ApolloProvider >
);
};
次の Issues
コンポーネントは、Apollo Client を使って GitHub GraphQL API を呼び出し、指定したリポジトリの最新 Issue 5件分を取得して表示します(前述のコマンドラインアプリと同様です)。
クエリの実行方法はとても簡単で、Apollo Client が提供する useQuery
フックを呼び出すだけです。
クエリの実行結果により、ロード中 (loading
)、エラー発生 (error
)、取得完了 (data
) の戻り値を得られるので、現在の状況に応じて描画内容を切り替えることができます。
Issues.tsx import * as React from 'react' ;
import { gql , useQuery } from '@apollo/client' ;
// 発行する GraphQL クエリ
const GET_ISSUES = gql `
query {
search(query: "repo:apollographql/apollo is:issue", type: ISSUE, first: 5) {
issueCount
nodes {
... on Issue { number title }
}
}
}
` ;
export const Issues : React . FC = () => {
// GraphQL のクエリを実行
const { loading , error , data } = useQuery ( GET_ISSUES );
// クエリ実行中の表示
if ( loading ) return < p > Loading ...</ p >;
// エラー発生時(レスポンスがないとき)の表示
if ( error ) return < p style = {{ color : 'red' }}>{ error . message }</ p >;
// クエリの結果が返ってきたときの表示
const { issueCount , nodes : issues } = data . search ;
return <>
< h2 > Num of issues : { issueCount }</ h2 >
< ul >
{ issues . map ( i => < li key = { i . number }>{ i . number } - { i . title }</ li >) }
</ ul >
</>;
};
実行結果 図: クエリ実行中 図: 結果の取得後 ここでは、クエリ実行中に「Loading …」と表示するだけにしていますが、ロード中のくるくるアイコンなどを表示すると、かなりそれっぽくなります。
ローカルストレージに保存したアクセストークンを使用する 上記の例では、GitHub のアクセストークンをハードコードしていますが、実運用ではこのようなコードはデプロイできません。
ここでは、ローカルストーレージ (GITHUB_TOKEN
) に保存されたアクセストークンを使って、ApolloClient
および ApolloProvider
を生成する方法を紹介します。
OAuth によってアクセストークンを取得し、ローカルストレージに保存するまでの流れは次の記事を参考にしてください。
次のサンプルコンポーネント GitHubApolloProvider
は、GitHub のアクセストークンを HTTP ヘッダに付けて GraphQL クエリを発行するための ApolloProvider
を提供します。
このような ApolloClient
のカスタマイズ方法は、Apollo 本家サイトの Authentication のページ で説明されています。
GitHubApolloProvider.tsx import * as React from 'react' ;
import {
ApolloClient ,
ApolloProvider ,
createHttpLink ,
InMemoryCache ,
} from '@apollo/client' ;
import { setContext } from '@apollo/client/link/context' ;
const httpLink = createHttpLink ({
uri : 'https://api.github.com/graphql'
});
const authLink = setContext (( _ , { headers }) => {
// Get the authentication token from local storage if it exists
const token = localStorage . getItem ( 'GITHUB_TOKEN' );
// Return the headers to the context so httpLink can read them
return {
headers : {
... headers ,
authorization : token ? `Bearer ${ token } ` : '' ,
}
}
});
// Create a GraphQL client
const apolloClient = new ApolloClient ({
link : authLink . concat ( httpLink ),
cache : new InMemoryCache ()
});
export const GitHubApolloProvider : React . FC = ( prop ) => {
return < ApolloProvider client = { apolloClient }>
{ prop . children }
</ ApolloProvider >;
};
あとは、ApolloProvider
を使っていたところを次のように GitHubApolloProvider
に置き換えれば、それ以下のコンポーネントでアクセストークン付きの GraphQL クエリを発行できるようなります。
App.tsx import * as React from 'react' ;
import { GitHubApolloProvider } from './GitHubApolloProvider' ;
import { Issues } from './Issues' ;
export const App : React . FC = () => {
return (
< GitHubApolloProvider >
< Issues />
</ GitHubApolloProvider >
);
};
ローカルストレージに保存されたアクセストークンは、GraphQL のリクエストを実行するたびに参照してくれるので、ApolloClient
インスタンスを生成した後でアクセストークンが設定された場合にもうまく動作します。
環境変数に設定したアクセストークンを使用する ローカル環境での開発時に、何らかの理由で、環境変数に保存した GitHub アクセストークン (Personal access token ) を使用したい場合は、index.tsx
の最初の方で、次のような感じで環境変数の値をローカルストレージにコピーしてしまえば OK です。
index.tsx if ( process . env . NODE_ENV === 'development' && process . env . MYAPP_GITHUB_TOKEN ) {
console . warn ( 'In development mode, use MYAPP_GITHUB_TOKEN env variable.' );
localStorage . setItem ( 'GITHUB_TOKEN' , process . env . MYAPP_GITHUB_TOKEN );
}
おまけ: GraphQL クエリにパラメータを設定する パラメータ付きの GraphQL クエリを実行するには、useQuery フック の第2パラメータで、次のような形で変数オブジェクト(変数名と値の連想配列)を渡します。
const { loading , error , data } = useQuery ( QUERY , { variables : 変数オブジェクト });
下記のサンプルコードでは、変数で指定した GitHub リポジトリのイシューを取得しています。
Issues.tsx import * as React from 'react' ;
import { gql , useQuery } from '@apollo/client' ;
const QUERY = gql `
query($searchQuery: String!) {
search(query: $searchQuery, type: ISSUE, first: 5) {
nodes {
... on Issue { number title }
}
}
}
` ;
export const Issues : React . FC = () => {
// 変数を渡して GraphQL クエリを実行
const queryVars = {
searchQuery : 'repo:apollographql/apollo is:issue'
};
const { loading , error , data } = useQuery ( QUERY , { variables : queryVars });
if ( loading ) return < p > Loading ...</ p >;
if ( error ) return < p style = {{ color : 'red' }}>{ error . message }</ p >;
const { nodes } = data . search ;
return <>
< ul >
{ nodes . map ( x =>
< li key = { x . number }>{ x . number } - { x . title }</ li >
)}
</ ul >
</>;
};
関連記事