今参加しているプロジェクトでは React (TypeScript) をフロントエンドに Hasura を GraphQL サーバーにした構成になっています。
Hasura を GraphQL サーバーにして code generator で TypeScript の型生成を行ったメモをまとめたエントリーです
経緯のメモ
構成
- GraphQL サーバー
- Frontend
- React
18.2.0
- typescript
5.0.4
- Vite
4.3.9
- @tanstack/react-query
4.29.3
- graphql-request
6.0.0
- graphql
16.6.0
- React
Frontend の vite で構築した React から react-query + graphql-request で Hasura にアクセスする構成にしました
1. GraphQL codegen の下準備
GraphQL 関連のパッケージをインストール
$ npm i graphql
$ npm i -D @graphql-codegen/cli
codegen config の作成
npx graphql-codegen init
で設定ファイルが作成できる
$ npx graphql-codegen init ? What type of application are you building?: Application built with React ? Where is your schema?: http://localhost:4000/graphql ? Where are your operations and fragments?: src/**/*.graphql ? Where to write the output: src/gql/ ? Do you want to generate an introspection file?: Yes ? How to name the config file?: codegen.ts ? What script in package.json should run the codegen?: codegen etching latest versions of selected plugins... Config file generated at codegen.ts $ npm install To install the plugins. $ npm run codegen To run GraphQL Code Generator. # 必要なパッケージをインストール $ npm install
設定ファイル codegen.ts
が作成され codegen を実行する npm script が追加される
📝 Where to write the output?
は最後が /
で終わるパスを指定しておかないと codegen 時にエラーになる
=> ✖ [client-preset] target output should be a directory, ex: "src/gql/"
codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { overwrite: true, schema: "http://localhost:4000/graphql", documents: "src/**/*.graphql", generates: { "src/gql/": { preset: "client", plugins: [] }, "./graphql.schema.json": { plugins: ["introspection"] } } }; export default config;
2. Hasura にアクセスして codegen できるようにする
codegen.ts
の schema に Hasura の GraphQL のエンドポイントの URL を指定し、全てにアクセスできるよう headers に { "x-hasura-admin-secret": ADMIN_SECRET }
を渡せば良い
cf. Admin Access | Hasura GraphQL Docs
codegen.ts
const config: CodegenConfig = { overwrite: true, - schema: "http://localhost:4000/graphql", + schema: [ + { + 'https://hasura.XXXXX.dev/v1/graphql': { + headers: { + 'x-hasura-admin-secret': 'YOUR_ADMIN_SECRET' + } + } + } + ], documents: "src/**/*.graphql", // … }
Hasura のエンドポイント と secret key を環境変数にする
secret key が直接設定ファイルに書かれているのは良くないので .env
経由で読み込ませるように変更する
.env
HASURA_GRAPHQL_URL=https://hasura.XXXXX.dev/v1/graphql HASURA_GRAPHQL_ADMIN_SECRET=your_secret
📝 VITE_
接頭語を使うと React から環境変数にアクセス可能になるが、build されたファイルに直接値が書き込まれフロントエンドから丸見えの状態になってしまうので注意
codegen.ts
const config: CodegenConfig = { overwrite: true, schema: [ { - 'https://hasura.XXXXX.dev/v1/graphql': { + [process.env.HASURA_GRAPHQL_URL ?? "http://localhost:4000/v1/graphql"]: { headers: { - 'x-hasura-admin-secret': 'admin_secret' + 'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET ?? '', }, }, }, ], documents: 'src/**/*.graphql', // … }
環境変数 .env
を使って codegen できるように npm script を修正する
codegen コマンドに --require dotenv/config
を付けることで .env
を読み込めるようになる
📝 vite + react 環境なら dotenv
が含まれているので別途 npm install dotenv
をする必要はない
package.json
"scripts": { - "codegen": "graphql-codegen --config codegen.ts" + "codegen": "graphql-codegen --require dotenv/config --config codegen.ts" },
cf. require field – GraphQL Code Generator
3. client-preset を使って React-Query + Graphql-Request で使える型を生成する
以前は Apollo や React-Query といったクライアントごとにプラグインを使ってクライアントに合わせた型を生成していたが、graphql-codegen v3 以降では client-preset (preset: "client"
) を使うことでクライアントを問わず利用できる型を出力できるようになった
@graphql-codegen/cli
v3 以降では npx graphql-codegen init
で出力される設定がデフォルトで client-preset
を使うものになっているので特に変更すべき点はない
codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { overwrite: true, schema: [ { [process.env.HASURA_GRAPHQL_URL ?? "http://localhost:4000/v1/graphql"]: { headers: { 'X-Hasura-Admin-Secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET ?? '', }, }, }, ], documents: "src/**/*.graphql", generates: { // client-preset を使った型を `src/gql/` に出力する "src/gql/": { preset: "client", plugins: [] }, "./graphql.schema.json": { plugins: ["introspection"] } } }; export default config;
適当な .graphql
ファイルを作成して codegen で型生成をする
何かしら src/**/*.graphql
が存在しなければ graphql.schema.json
も生成できないので適当な query ファイルを作成する
今回は Hasura を使っているのでコンソールから適当なクエリを作ってファイルにコピペすればOK
./src/gql/queries/getUser.graphql
query GetUser($email: String!) { users(email: $mail) { id, email, displayName } }
GraphQL codgen
$ npm run codegen > graphql-codegen@0.0.0 codegen > graphql-codegen --require dotenv/config --config codegen.ts ✔ Parse Configuration ✔ Generate outputs
graphql.schema.json
と src/gql/
ディレクトリ内に型ファイルが生成される
|- graphql.schema.json |- /src/gql/ |- fragment-masking.ts |- graphql.ts |- gql.ts |- index.ts
graphql.ts
にフロントで使う TypeScript の型が書かれている
4. React-Query + GraphQL-Request を使って Hasura に GraphQL リクエストを送信する
$ npm i @tanstack/react-query graphql-request
4-1. react-query の プロバイダーを設定
<QueryClientProvider>
で全体を囲う
/src/App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const queryClient = new QueryClient(); function App() { return ( <QueryClientProvider client={queryClient}> <h1>My App</h1> <MyComponent /> </QueryClientProvider> ); } export { App };
4-2. GraphQLClient を作成
Hasura にフロントエンドからアクセスするには JWT token を header に含める必要があるので、トークンをセットしたクライアントを返す関数を作成する
cf. Authentication Using JWTs | Hasura GraphQL Docs
/src/graphqlClient.ts
import { GraphQLClient } from 'graphql-request'; const endpoint = import.meta.env.VITE_HASURA_URL ?? ''; export const buildGraphQLClient = (token?: string) => { const headers = token ? { authorization: `Bearer ${token} } : undefined; const client = new GraphQLClient(endpoint, { headers, }); return client; };
4-3. React-Query + GraphQL Client (graphql-request) を使って Hasura にリクエストする Hooks を作成する
'/src/hooks/useGetUser.ts'
import { useQuery } from '@tanstack/react-query'; // codegen で生成された {QueryName}Document を使用する import { GetUserDocument, GetUserQueryVariables } from '../gql/graphql'; import { getGraphQLClient } from '../graphqlClient'; // token を取得する hook があるものとする import { useAuth } from './useAuth'; export const useGetUser = ({ email }: GetUserQueryVariables ) => { const { token } = useAuth(); return useQuery( ['graphl', 'get', 'user', email], ({ queryKey }) => { const client = getGraphQLClient(token); return client.request(GetUserDocument, { email: queryKey[3] ?? '' }); } ); };
4-4. アプリから User 情報の取得を行う
先ほど作成した useGetUser
を使って Hasura に GraphQL リクエストを送ってユーザー情報を取得する
/src/MyComponent.ts
import { useGetUser } from './hooks/useGetUser'; function MyComponent() { const const { data, isLoading, error } = useGetUser(email); if ( isLoading ) { return <div>Loading…</div> } if { error } { throw error; return null; } return ( <div>User: { data ? <UserInfo {…data} /> : <span>No User</span> }</div> ); }
これで TypeScript の型補完の恩恵を受けながら Hasura に GraphQL リクエストを送れるようになりました!
mutation だったり React-Query のオプションなどは長くなるのでまた別のエントリーにしようと思います。
おわり ₍ ᐢ. ̫ .ᐢ ₎
[参考]
- GraphQL codegen: react-query + graphql-request + hasura
- Setup Hasura with GraphQL Code Generator
- Admin Access | Hasura GraphQL Docs
- Cloud Standard & Enterprise Edition: Multiple Admin Secrets | Hasura GraphQL Docs
- require field – GraphQL Code Generator
- @graphql-codegenの環境変数の設定方法 - Qiita
- Getting started with React Query and GraphQL
- GraphQL Code Generator + React Queryの紹介 | HiCustomer Lab - HiCustomer Developer's Blog
- GraphQL Code Generator v5 Roadmap · Issue #8296 · dotansimha/graphql-code-generator · GitHub
- Authentication Using JWTs | Hasura GraphQL Docs
- Overview | TanStack Query Docs
- graphql-request - npm
- ぼくのかんがえたさいきょうのデータフェッチ 2021Summer🏄♂️【Next.js / Hasura】