はじめに
株式会社レコチョクでエンジニアをしている森川です。
GraphQLを使うという話があるのですが、GraphQLってなんぞ?という無知蒙昧人間なので、とりあえず使うことになるNext.jsと合わせてお勉強をすることにしました。
基本的にお勉強が苦手なので、毎度何か始める時はとりあえず動かしてよくわからないところを後から調べて誤魔化しています。
今回もその雰囲気を出すためにとりあえず両方構築からしてみました。
せっかくなので、その過程を残しておきます。
目次
Next.jsとは
Reactをベースにしたフロントエンドフレームワークです。
バージョン13.4以降からAppRouterを導入できるようになりました。今後はAppRouterが推奨されるみたいです。
SSR, SSGを選んでレンダリングできるので表示が早いです。
詳しくは公式を読むのが一番いいです。
Apollo Clientとは
GraphQL APIをシンプルにクライアント側で操作するためのライブラリです。
Subscription機能により、リアルタイムデータの取得が可能です。
バージョン情報
パッケージ名 | バージョン |
---|---|
@apollo/client | ^3.10.8 |
@as-integrations/next | ^3.0.0 |
@graphql-codegen/cli | 5.0.2 |
@graphql-codegen/client-preset | 4.3.2 |
@graphql-codegen/typed-document-node | ^5.0.9 |
@graphql-codegen/typescript-operations | ^4.2.3 |
@types/node | ^20 |
@types/react | ^18 |
@types/react-dom | ^18 |
graphql | ^16.9.0 |
typescript | ^5 |
セットアップ
nodeとかyarnはインストール済みの前提で進めてしまいます…。
Nextjsでプロジェクト作成
npx create-next-app@latest |
ごちゃごちゃと聞かれるので、適時自分がビルドしたい内容に合わせて選択します。参考
✔ What is your project named? … sample-app ✔ Would you like to use TypeScript? … No / Yes ✔ Would you like to use ESLint? … No / Yes ✔ Would you like to use Tailwind CSS? … No / Yes ✔ Would you like to use `src/` directory? … No / Yes ✔ Would you like to use App Router? (recommended) … No / Yes ✔ Would you like to customize the default import alias (@/*)? … No / Yes ✔ What import alias would you like configured? … @/* |
Apollo Clientの導入
yarn add -D @apollo/client @as-integrations/next graphql @graphql-codegen/cli @graphql-codegen/client-preset @graphql-codegen/typed-document-node @graphql-codegen/typescript-operations |
- @apollo/client
- Apollo Clientのコアライブラリ
- @as-integrations/next
- Next.jsとApollo Clientを統合するためのライブラリ
- graphql
- GraphQLのコアライブラリ、スキーマの定義、クエリの解析、実行、結果の検証を行うための機能を提供してくれます。
- graphql-codegen
- GraphQLスキーマからコードを生成するライブラリ
codegen.tsの生成
codegen.tsを生成します。
yarn graphql-codegen init |
Backend - API or server Application built with Angular ❯ Application built with React Application built with Stencil Application built with Vue Application using graphql-request Application built with other framework or vanilla JS |
どれで作るか聞かれるのでReactを選択します。
? What type of application are you building? Application built with React ? Where is your schema?: (path or url) http://localhost:4000 ? Where are your operations and fragments?: src/**/*.tsx ? Where to write the output: src/gql/ ? Do you want to generate an introspection file? No ? How to name the config file? codegen.ts ? What script in package.json should run the codegen? codegen |
いろいろ聞かれるので自分のプロジェクトに合わせて選択します。
パス等を変更する場合は以下の内容もそれに置き換えて考えてください。
実行するとcodegen.tsが生成されます。
生成されたファイルを開き
ignoreNoDocuments: trueを追加します。
→これを追加するとdocmentsのパスにファイルが存在しない場合にエラーを出さないようになります。
<br />import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { overwrite: true, schema: "http://localhost:4000", documents: "src/**/*.tsx", generates: { "src/gql/": { preset: "client", plugins: [] } }, ignoreNoDocuments: true // これを追加 }; export default config; |
このままにするとschemaが存在しないというエラーが出るので今回はフリーのAPIを使ってそれに接続するようにします。
SpaceX API
https://spacex-production.up.railway.app/
<br />import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { overwrite: true, schema: "https://spacex-production.up.railway.app/", // これを変える documents: "src/**/*.tsx", generates: { "src/gql/": { preset: "client", plugins: [] } }, ignoreNoDocuments: true }; export default config; |
- 設定参考
スキーマを自分で定義したり、スキーマごとに別のディレクトリに書き出したりとかもできます。これに関しては後述します。
codegenの実行
package.jsonに codegenが追加されているのでこのコマンドを実行します。
yarn codegen |
実装
Apollo Clientの実装
ApolloClientを利用するためにインスタンスを初期化します。
srcの配下に apollo/ディレクトリを作成し、その中に client.tsを作成します。
import { ApolloClient, InMemoryCache } from "@apollo/client"; export const client = new ApolloClient({ uri: "https://spacex-production.up.railway.app/", cache: new InMemoryCache(), }); |
uri: GraphQLサーバーのエンドポイントを指定します。
cache: クエリの結果をキャッシュするための設定です。
ApolloProviderの実装
コンポーネントでApolloClientを利用するためにApolloProviderを作成します。
これで、コンポーネント内でクエリを実行することができます。
appの配下に providers/ディレクトリを作成し、その中に WithApolloProvider.tsxを作成します。
"use client"; import { ApolloProvider } from "@apollo/client"; import React from "react"; import { client } from "../../apollo/client"; export const WithApolloProvider = ({ children }: React.PropsWithChildren) => { return <ApolloProvider client={client}>{children}</ApolloProvider>; }; |
データを取得してみる
appの配下に
components/ディレクトリを作成し、その中に
api/ディレクトリを作成します。
その中に
GetLaunches.tsxを作成します。
"use client" import { gql, useQuery } from "@apollo/client"; import { Launch } from "../../../gql/graphql"; const ALL_LAUNCHES = gql` query GetAllLaunches { launches { mission_name rocket { rocket_name } } } `; export const GetLaunches = () => { const { data, loading, error } = useQuery(ALL_LAUNCHES); if (loading) return <div>LOADING...</div>; if (error) return <div>{error.message}</div>; return ( <div> <ul> {data.launches.map((launch: Launch, idx: number) => ( <li key={String(idx)}> mission_name: {launch.mission_name}(rocket_name: {launch.rocket?.rocket_name}) </li> ))} </ul> </div> ); }; |
データを表示する
import { GetLaunches } from "./components/api/GetLaunches"; import { WithApolloProvider } from "./provider/WithApolloProvider"; export default function Home() { return ( <div> <h1>SpaceX Launches</h1> <p>---------------------</p> <WithApolloProvider> <GetLaunches /> </WithApolloProvider> </div> ); } |
起動
yarn dev |
確認
http://localhost:3000/にアクセスしてデータが表示されていれば成功です!
おまけ:GraphQLのクエリを別ファイルで定義する場合
.graphqlファイルを作成し、そこにクエリを記述することで、クエリを別ファイルで管理することができます。
src/gql/queryディレクトリを作成し、その中に GetAllLaunches.graphqlを作成します。
query GetAllLaunches { launches { mission_name rocket { rocket_name } } } |
codegen.tsを以下のように変更します。
<br />import type { CodegenConfig } from '@graphql-codegen/cli'; import path from 'path'; const config: CodegenConfig = { overwrite: true, schema: "https://spacex-production.up.railway.app/", documents: path.resolve(__dirname, "src/gql/query/*.graphql"), // 絶対パスにしないとエラーが出る generates: { "src/gql/": { preset: "client", plugins: [] } }, // ignoreNoDocuments: true 有効だとDocmentsが存在しない場合にエラーを出さないのでコメントアウト }; export default config; |
codegenを実行します。
yarn codegen |
GetLaunches.tsxを以下のように変更します。
"use client" import { useQuery } from "@apollo/client"; import { AllLaunchesQueryDocument, Launch } from "../../../gql/graphql"; export const GetLaunches = () => { const { data, loading, error } = useQuery(AllLaunchesQueryDocument); if (loading) return <div>LOADING...</div>; if (error) return <div>{error.message}</div>; return ( <div> <ul> {data?.launches?.map((launch: Launch | null, idx: number) => ( <li key={String(idx)}> mission_name: {launch?.mission_name}(rocket_name: {launch?.rocket?.rocket_name}) </li> ))} </ul> </div> ); }; |
確認する。
yarn dev |
まとめ
スキーマはバックエンド側で定義されているものを使うことになると思うので、そのスキーマを元にクエリを定義していくことになることが多いかなと思います。
なので、バックと共通のファイルを利用することでフロントエンドは型や返却値を意識せず、都度codegenで定義ファイルを作成し、それを利用することで呼び出しなどに無駄なリソースを割かずに開発を進めていくことができそうです。
RESTAPIを使っていると、型定義を書くのが面倒だったり、型が合わないとかでエラーが出ることが多いですが、GraphQLを使うことでこれらの問題を解決することができるので、GraphQLを使っていくことで開発効率が上がることが期待できるかなと思います。
Subscriptionなども実際の開発では使っていくことにはなるのかなと思いますが、Docsも豊富だったので、今回のようにお手軽に実装していけるのではないかなーと思っています。
最後まで閲覧いただきありがとうございました。