Next.js, Rails環境でRelayにサクッと入門してみた
はじめに
この記事はCODEBASE OKINAWA Advent Calendar 20221日目の記事になっております。今回は普段触っていなかったGraphQLクライアントの一つであるRelayにサクッと入門したので、やっていく中で詰まった内容や環境構築の手法について書いていこうと思います。
また今回実際に試した内容はGitHubに上げております
Relayとは
RelayはMeta社が開発しているGraphQLクライアントで、同列にApollo Clientやurqlなどがあります。
Apollo Clientなどと同様にnormalized cacheの機構があり、通信効率の良い状態管理ができます。
また、Relayは開発元がMeta社だけあり、通信状態がSuspenseやErrorBoundaryを使う様になっており、他のGraphQLクライアントに比べてReactファーストな思想が強いと感じました。
筆者個人としては、元々Apollo Clientを使って開発することが多かったのですが、RelayはFragment Collocationを強制することにより、堅牢性の高いコンポーネント設計がしやすいことから、より大規模なアプリケーション向きの印象のあったRelayを今回手っ取り早く試したいと思ったので記事にすることにしました。
主な使用技術
- React 18.2.0
- Next.js 13.0.5
- TypeScript 4.9.3
- Ruby 3.1.2
- Rails 7.0.4
- Relay
- graphql-ruby
Railsでgraphql-ruby環境を構築する
まずはRailsでGraphQL環境を作っていきます。RailsでのGraphQL環境構築は多くの方が投稿しているのでここではある程度省略したいと思います。
GraphQL環境が構築できて以下のようにPlaygroundでtestField
のqueryを実行できる様になっているか確認します。
Next.jsから実行するためのQueryを実装する
今回は手っ取り早く試すので、超簡単に以下のカラムを持ったpostsテーブルを用意します。
id: ID
content: String
like_count: Integer
created_at: datetime
updated_at: datetime
% rails g model Post content:string like_count:integer
invoke active_record
create db/migrate/20221129160525_create_posts.rb
create app/models/post.rb
invoke test_unit
create test/models/post_test.rb
create test/fixtures/posts.yml
seeds.rbも適当に作る
Post.create([{ content: 'test', like_count: 20 }, { content: '2022年もいい年でしたね👶', like_count: 2022 }])
生成したPostモデルをもとにGraphQLのTypes::PostType
を生成します。
% rails g graphql:object post
create app/graphql/types/post_type.rb
次に実際にpostsを全件取得するQueryを追加する
module Types
class QueryType < Types::BaseObject
# Add `node(id: ID!) and `nodes(ids: [ID!]!)`
include GraphQL::Types::Relay::HasNodeField
include GraphQL::Types::Relay::HasNodesField
# Add root-level fields here.
# They will be entry points for queries on your schema.
# TODO: remove me
field :test_field, String, null: false,
description: 'An example field added by the generator'
def test_field
'Hello World!'
end
field :posts, [Types::PostType], null: false
def posts
Post.all
end
end
end
次にNext.jsからGraphQLのAPIをリクエストできるようにCORSの設定をするために以下のgemを追加する
gem 'rack-cors'
installしたらconfig/initializers/cors.rbに以下の内容を加える
(今回はNext.jsが3000, Railsが4000ポートを使うことにしています。)
# Be sure to restart your server when you modify this file.
# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
# Read more: https://github.com/cyu/rack-cors
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
end
これで一旦Rails側の実装は終わりなのでNext.js, Relayの方に進んでいく。
Next.jsでRelayの環境を構築する
通常通り、以下のコマンドでNext.jsのプロジェクトを作成する
% yarn create next-app --typescript
続いてRelayを動かすために必要なライブラリのインストールを行う
% yarn add relay-runtime react-relay
% yarn add --dev relay-compiler graphql babel-plugin-relay @types/react-relay relay-config
次にいくつかRelayを動かすための設定ファイルの変更、追加をしていきます
まずはrelay.config.jsを作成。今回はTypeScriptを使った環境なのでtypescriptを指定しています。
module.exports = {
src: "./src",
language: "typescript", // "javascript" | "typescript" | "flow"
schema: "./schema.graphql",
exclude: ["**/node_modules/**", "**/__mocks__/**", "**/__generated__/**"],
}
babelの設定にもRelayを使うようにpluginに追加をします
{
"plugins": ["relay"],
"presets": ["next/babel"]
}
次に実際にRailsで立ち上げているGraphQLサーバーへリクエストするための環境の設定ファイルを追加します。
import { useMemo } from 'react'
import { Environment, Network, RecordSource, Store } from 'relay-runtime'
let relayEnvironment
function fetchQuery(operation, variables) {
return fetch(process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then((response) => response.json())
}
export function createEnvironment() {
return new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource()),
})
}
export function initEnvironment(initialRecords) {
const environment = relayEnvironment ?? createEnvironment(initialRecords)
if (initialRecords) {
environment.getStore().publish(new RecordSource(initialRecords))
}
if (typeof window === 'undefined') return environment
if (!relayEnvironment) relayEnvironment = environment
return relayEnvironment
}
export function useEnvironment(initialRecords) {
const store = useMemo(() => initEnvironment(initialRecords), [initialRecords])
return store
}
NEXT_PUBLIC_GRAPHQL_ENDPOINT=http://localhost:4000/graphql
relay compierを実行するためにpackage.jsonのscriptsに以下を追加する
"relay": "relay-compiler"
次にRelay CompilerでQueryの型生成をするためにGraphQLサーバーのschemaをNext.js側で保持する必要があります。
そのために再度Rails側に戻ります。
こちらのコメントを参考にGraphQLのschemaファイルを生成するRakeタスクを追加する。
require "graphql/rake_task"
GraphQL::RakeTask.new(schema_name: "RelaySampleBackendSchema") # ここのschema名は自分の環境のschema名を確認する
そして以下のコマンドを実行する
% rails graphql:schema:dump
実行するとschema.graphql
というファイルが生成されるのでこのファイルの内容をNext.jsの /data
に追加する
(この手順、もっと簡単な方法がありそう)
次に今回Next.js側で実行するQueryを定義する
import { graphql } from "react-relay";
const PostsQuery = graphql`
query postsQuery {
posts {
id
content
likeCount
}
}
`;
export default PostsQuery;
ここまでの設定をした後で以下のコマンドでrelay compilerを実行する
% yarn relay (git)-[main]
yarn run v1.22.19
warning ../../../../package.json: No license field
$ relay-compiler
[INFO] [default] compiling...
[INFO] [default] compiled documents: 1 reader, 1 normalization, 1 operation text
[INFO] Done.
✨ Done in 0.38s.
これで定義したQueryの型が生成されたので取得したpostsを表示するpageを作っていく
_app.tsx
にはRelayのProviderを読み込んでおく
import "../styles/globals.css";
import { AppProps } from "next/app";
import { RelayEnvironmentProvider } from "react-relay";
import { useEnvironment } from "../lib/client_environment";
export default function App({ Component, pageProps }: AppProps) {
const environment = useEnvironment(pageProps.initialRecords);
return (
<RelayEnvironmentProvider environment={environment}>
<Component {...pageProps} />
</RelayEnvironmentProvider>
);
}
import React, { Suspense } from "react";
import { useLazyLoadQuery } from "react-relay/hooks";
import PostsQuery from "../../src/queries/posts.query";
import { postsQuery } from "../../src/queries/__generated__/postsQuery.graphql";
const PostsIndexPage = () => {
const { posts } = useLazyLoadQuery<postsQuery>(PostsQuery, {});
return (
<>
<Suspense fallback="Loading...">
{posts.map((post) => {
return (
<div key={post.id}>
<p>id: {post.id}</p>
<p>content: {post.content}</p>
<p>likes: {post.likeCount}</p>
</div>
);
})}
</Suspense>
</>
);
};
export default PostsIndexPage;
実際にページにアクセスして以下の内容が表示されていれば無事Relayを使ってGraphQLサーバーからデータ取得ができている。
まとめ
relayはgraphql-codegenなどApolloを使う場合に併用するようなライブラリの機能も全て含んでいるライブラリなので設定でやることが多い様に思います。何か参考になれば幸いです。
今回手っ取り早くRelayに入門するために簡単なQueryを実行するまでを試してみましたが、mutationや認証ありのリクエスト実行などがまだ試せていないので随時記事として更新できたらと思います。
Discussion