RailsとNext.jsを使ったサービス環境構築編

概要

サーバサイドをRails、フロントエンドをNex.jsとしGraphQLを使ったシンプルサービスの構築手順を説明します。 この記事で構築したシステムを元に、次回のスキーマの変更に強いCIを構築する方法を説明します。

目次

  • 概要
  • 目次
  • 目的
  • 対象読者
  • PR
  • 本文
  • 環境構築
  • GraphQLの導入
  • RailsにGraphQLを導入する手順
  • Mutation
  • ネストしたクエリを作成する
  • 外部からのアクセスできるセキュリティ設定
  • CORS対応
  • GraphqlControllerコントローラの設定
  • Next.js側の設定
  • Next.jsでのコンポーネントテストとApollo Clientのテスト方法
  • まとめ
  • バックエンド Rails
  • ここから先は別記事
  • GraphQLの定義ファイルを出力する
  • Github ActionsでGraphQLの定義ファイルを自動生成してコミットする
  • フロントエンド Next.js
  • apollo clientの準備
  • テスティングフレームワーク jestの導入
  • 実験・調査・比較結果
  • 考察・提案
  • まとめ
  • 参考リンク
  • PR

目的

何のためにこれをするのか、xxxの開発効率を上げる。xxxのパフォーマンスが良くするなど。

対象読者

プロダクトオーナー向けなのか、アーキテクト・リードエンジニアなどアーキテクチャ設計を担当する向けかビジネスサイドの人向けなのかを明確にする。※読者を明確にすると文章の内容や構成をその人たち向けにかけるので独りよがりにならず良いです。

PR

UZUMAKIではアジャイル開発で新規事業の開発から、大規模Webアプリケーションのアーキテクチャ更新などの開発をしています。

お問い合わせはUZUMAKIのHPのお問合せフォームから

株式会社UZUMAKI

UZUMAKI(うずまき)はエンジニア・デザイナー・ディレクターで構成されるギルド型開発チーム。成長するWebサービスやアプリのリニューアル・リファクタリングに強みがあります。

株式会社UZUMAKI

本文

環境構築

必要な技術要素

本記事を読むにあたって必要な技術要素は以下の通りです。

  • Ruby 3.1
    • Rails7.0
  • Node.js 18
    • npm

RailsとNext.jsのインストールはそれぞれ省略します。

GraphQLの導入

RailsにGraphQLを導入する手順

まずは、GraphQLを導入するために必要なGemである graphql をインストールします。Gemfile に以下の行を追加し、bundle install コマンドでインストールしてください。

gem 'graphql'

次に、GraphQLのスキーマを定義します。スキーマは、GraphQLのリクエストに対して、どのようなレスポンスを返すかを定義するものです。例えば、以下のようなスキーマを定義することができます。

# app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :hello, String, null: false, description: 'A simple hello world'
    def hello
      'Hello World!'
    end
  end
end

これで、helloというクエリを定義し、その結果として Hello World! を返すことができるようになりました。また、リゾルバーと呼ばれるクラスを作成することで、GraphQLのクエリに対して具体的な動作を返すことができます。例えば、以下のようなリゾルバーを定義することができます。

# app/graphql/resolvers/hello_resolver.rb
class Resolvers::HelloResolver < GraphQL::Schema::Resolver
  def resolve
    'Hello World!'
  end
end

そして、スキーマにリゾルバーを関連付けることで、リゾルバーがクエリに対して実行されるようになります。例えば、以下のようなスキーマとリゾルバーを定義することができます。

# app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :hello, String, null: false, description: 'A simple hello world'
    def hello
      'Hello World!'
    end
    field :hello_resolver, resolver: Resolvers::HelloResolver
  end
end

このようにすることで、hello_resolverというクエリを定義し、その結果として Hello World! を返すことができるようになります。

Gemfile に GraphQLを利用するのに必要なgemを追加

# graphql機能
gem 'graphiql-rails'
gem 'graphql'

GraphQL gem 初期化コマンド 実行しベースの設定やファイルを生成する

rails generate graphql:install

routes.rbにGraphQL向けのルーティング設定が追加されていることを確認する(下記は自動生成されたものからリファクタいしている)

Rails.application.routes.draw do
  mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql' if Rails.env.development?
  post '/graphql', to: 'graphql#execute'

  #省略...
end

Typeクラスの生成

rails g graphql:object Post
rails g graphql:object Comment
module Types
  class PostType < Types::BaseObject
    field :id, ID
    field :name, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime
    field :updated_at, GraphQL::Types::ISO8601DateTime
    field :comments, [CommentType]

    def comments
      Loaders::AssociationLoader.for(Post, :comments).load(object)
    end
  end
end

graphql/type/query_type.rb を以下のように追記します。

class Types::QueryType < Types::BaseObject
  # Add `node(id: ID!) and `nodes(ids: [ID!]!)`
  include GraphQL::Types::Relay::HasNodeField
  include GraphQL::Types::Relay::HasNodesField

  # 追記部分
  field :posts, [Types::PostType], null: false
  def posts
    Post.all
  end
end
image

Mutation

データの取得だけでなく、データを追加・変更・削除をするMutationの簡単なサンプルも追加します。

Postを作成するMutationを自動生成します

rails g graphql:mutation CreatePost

生成されたファイルの中身を下記のように修正します。

app/graphql/mutations/create_post.rb

module Mutations
  class CreatePost < BaseMutation
    field :post, Types::PostType, null: true
    argument :name, String, required: true

    def resolve(**args)
      post = Post.create!(args)

      {
        post:
      }
    end
  end
end

app/graphql/types/mutation_type.rb にcreate_postのfieldが追加されていることを確認してください。

module Types
  class MutationType < Types::BaseObject
    field :create_post, mutation: Mutations::CreatePost
  end
end
image

同様にコメント追加用のMutationを作成します。

Mutationの自動生成コマンドを実行し

rails g graphql:mutation CreateComment

app/graphql/mutations/create_comment.rb ファイルを修正

module Mutations
  class CreateComment < BaseMutation
    field :comment, Types::CommentType, null: false
    argument :name, String, required: true
    argument :post_id, Integer, required: true

    def resolve(**args)
      comment = Comment.create!(args)

      {
        comment:
      }
    end
  end
end

動作確認

image

ここまでできたらGraphQLでPostとCommentを追加できます。

ネストしたクエリを作成する

module Types
  class PostType < Types::BaseObject
    # 中略、下記の一行を追加
    field :comments, [CommentType], null: true
  end
end

N+1対策に、graphql-batchを追加する

graphql-rubyにはN+1対策にGraphQL::Dataloaderが組み込まれていますが、よりシンプルにかけるgraphql-batchを採用します。

Gemfileに書きを追記

gem 'graphql-batch’

graphql-batchのexampleに従い1:1の場合のカスタムローダーを作成

app/graphql/loaders/record_loader.rb

module Loaders
  class RecordLoader < GraphQL::Batch::Loader
    def initialize(model)
      @model = model
    end

    def perform(ids)
      @model.where(id: ids).each { |record| fulfill(record.id, record) }
      ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
    end
  end
end

1:Nの場合のカスタムローダはこちらのexampleを作成する

app/graphql/loaders/association_loader.rb

app/graphql/base_rails_schema.rb

GraphQL::dataloaderをGraphQL::Batchに置き換える


# 置き換え
# use GraphQL::dataloader
  use GraphQL::Batch

app/graphql/types/post_type.rb にcommentsフィールドにN+1対応するコードを追加し、null不許可の設定を調整

module Types
  class PostType < Types::BaseObject
    field :id, ID
    field :name, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime
    field :updated_at, GraphQL::Types::ISO8601DateTime
    field :comments, [CommentType]

    def comments
      Loaders::AssociationLoader.for(Post, :comments).load(object)
    end
  end
end

合わせて app/graphql/types/comment_type.rb のnull不許可の設定を調整

module Types
  class CommentType < Types::BaseObject
    field :id, ID
    field :name, String, null: false
    field :post_id, Integer, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime
    field :updated_at, GraphQL::Types::ISO8601DateTime
  end
end

動作確認

{
  posts {
    id
    name
    comments {
      id
      name
    }
  }
}

N+1問題が発生していないことを確認

外部からのアクセスできるセキュリティ設定

CORS対応

Gemfileにrack-cors

gem 'rack-cors'

config/initializers/cors.rb に設定を追加

環境変数 FRONTEND_URL からはアクセス可能のするよう設定。developmentやtestでは任意でアクセス可能

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    if Rails.env.production?
      origins ENV.fetch('FRONTEND_URL', '')
    else
      origins '*'
    end
    resource '*', headers: :any, methods: %i[get post patch put delete]
  end
end

GraphqlControllerコントローラの設定

自動生成されてコメントに従い、外からのAPIアクセスできるようにコメントを外す

class GraphqlController < ApplicationController
  # If accessing from outside this domain, nullify the session
  # This allows for outside API access while preventing CSRF attacks,
  # but you'll have to authenticate your user separately
  protect_from_forgery with: :null_session # ここのコメントを外す

Next.js側の設定

Next.jsにApollo Clientを導入し、GraphQLを利用する手順

次に、Next.jsでGraphQLを利用するためにApollo Clientを導入します。Apollo ClientはGraphQLのクエリを実行するためのクライアントライブラリであり、Next.jsでも利用することができます。

まずは、必要なパッケージをインストールします。以下のコマンドを実行して、必要なパッケージをインストールしてください。

$ npm install --save @apollo/client graphql

次に、Apollo Clientを初期化します。_app.js ファイルに以下のようなコードを追加してください。

import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: '/graphql',
  cache: new InMemoryCache()
});

export default function App({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
}

このようにすることで、ApolloProvider コンポーネントの下に定義したコンポーネントで、GraphQLのクエリを実行することができるようになります。例えば、以下のようなコードを書くことで、クエリを実行することができます。

import { useQuery, gql } from '@apollo/client';

const GET_HELLO = gql`
  query {
    hello
  }
`;

function HelloWorld() {
  const { loading, error, data } = useQuery(GET_HELLO);

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <p>{data.hello}</p>
  );
}

このようにすることで、GET_HELLO というクエリを実行し、その結果として Hello World! を表示することができます。

クエリとミューテーションの作成方法

GraphQLでは、クエリとミューテーションを使用してデータの取得や更新を行います。クエリはデータを取得するために、ミューテーションはデータを更新するために使用します。

クエリを作成する場合は、以下のような形式でクエリを定義します。

const GET_DATA = gql`
  query {
    data {
      id
      name
    }
  }
`;

このように定義することで、data という名前のクエリを定義し、その結果として idname のフィールドを返すようになります。

ミューテーションを作成する場合は、以下のような形式でミューテーションを定義します。

const ADD_DATA = gql`
  mutation AddData($name: String!) {
    addData(name: $name) {
      id
      name
    }
  }
`;

このように定義することで、addData という名前のミューテーションを定義し、その結果として idname のフィールドを返すようになります。また、$name という変数を定義して、name の値を受け取ることができるようになります。

Next.jsでのコンポーネントテストとApollo Clientのテスト方法

Next.jsでは、コンポーネントのテストに Jest を使用することができます。また、Apollo Clientのテストには、 @apollo/client/testing を使用することができます。

まずは、必要なパッケージをインストールします。以下のコマンドを実行して、必要なパッケージをインストールしてください。

$ npm install --save-dev jest @testing-library/react @apollo/client/testing

次に、以下のようなコードを書くことで、コンポーネントのテストを行うことができます。

このようにすることで、HelloWorld コンポーネントをテストし、クエリに対して Hello World! を返すように設定した結果を表示することができます。

また、以下のようなコードを書くことで、Apollo Clientのテストを行うことができます。

このようにすることで、Apollo Clientを使用してクエリを実行し、その結果として Hello World! を表示することができます。

まとめ

本記事では、RailsとNext.jsを使用してGraphQL通信の環境構築を行う方法を解説しました。必要な技術要素や環境構築方法、GraphQLの導入方法、クエリやミューテーションの作成方法、テスト方法について説明しました。

これから学習を進める場合には、公式ドキュメントやオンラインのチュートリアルを参考にすることをおすすめします。また、以下のようなリソースも参考にすることができます。

・GraphQL公式ドキュメント(https://graphql.org/) ・Rails on GraphQL(https://graphql-ruby.org/) ・Next.js with Apollo Client(https://www.apollographql.com/docs/react/get-started/)

GraphQLを利用することで、フロントエンドとバックエンドの間の通信を効率的に行うことができます。ぜひ、今回の記事を参考にして、GraphQLを導入してみてください。

バックエンド Rails

ここから先は別記事

GraphQLの定義ファイルを出力する

Rakeファイルを作成する

lib/tasks/graphql.rake

require 'graphql/rake_task'
GraphQL::RakeTask.new(schema_name: 'BaseRailsSchema', directory: 'docs')

動作確認

rails graphql:schema:dump

docs以下に`schema.graphql` とschema.json が生成されることを確認する

Github ActionsでGraphQLの定義ファイルを自動生成してコミットする

ここで、自動生成することでコミットミスを防ぐ

image
image

.github/workflows/gerenate_grphql_schema.yml

image

いくつか細かい問題が発生した修正も入っているので詳しくはこちらのPRの差分を確認してください

Add: graphql schemaの自動生成 by konyu · Pull Request #1 · konyu/graphql_rails

Contribute to konyu/graphql_rails development by creating an account on GitHub.

Add: graphql schemaの自動生成 by konyu · Pull Request #1 · konyu/graphql_rails

フロントエンド Next.js

github cilのダウンロードしてghコマンドを使えるようにする

GitHub CLI

Take GitHub to the command line

GitHub CLI
gh auth login

必要なライブラリを追加する

npm i -S graphql                                                                              ~/d/u/graphql_next graphql_base
npm i -D typescript ts-node @graphql-codegen/cli @graphql-codegen/client-preset


apollo clientも追加する
npm i -D @apollo/client

package.json

"scripts": {
		# 追記
    "codegen": "scripts/codegen.sh",
    "codegen-watch": "npx graphql-codegen --watch"
  },

scripts/codegen.sh にschema.graphqlをgithubにあるところから取得して、コードを自動生成する

SCHEMA_FILE=/repos/konyu/graphql_rails/contents/docs/schema.graphql

if [ -e schema.graphql ]; then
  rm schema.graphql
fi

gh api $SCHEMA_FILE -H "Accept: application/vnd.github.raw" > schema.graphql
npx graphql-codegen

設定ファイルの作成

import { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  schema: "schema.graphql",
  documents: ["src/**/*.tsx"],
  ignoreNoDocuments: true, // for better experience with the watcher
  generates: {
    "./src/gql/": {
      preset: "client",
    },
  },
};

export default config;

自動生成をしてみる

npx graphql-codegen

src/ggl 下に定義ファイルが自動生成されることを確認する

apollo clientの準備

src/pages/_app.tsx

src/pages/index.tsx

codegen時に、名前が変わったクエリのメソッド名やパラメタ名があるときには生成時に失敗する

型が変わるとエラーになる build時にエラーになる

image

テスティングフレームワーク jestの導入

npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom

RailsからNext.jsのリポジトリのテストを実行してその結果を取得する

Tokenを取得する

https://rcmdnk.com/blog/2023/01/24/computer-github/

というものから選べて、Tokenを適用するレポジトリの範囲を決める事ができます。 Only Selectでは1つのTokenで最大50個のレポジトリを選ぶ事が出来ます。

単に公開されている情報をAPI経由で取得したくてrate limitを上げたいだけの場合などは Public Repositories (read-only)にしてpermissionを何も付けずに Tokenを取得すればOKです。

ここでOnly select repositoriesを選んで今回workflowを他からAPIで呼び出したいレポジトリを選んでおきます。

その下でPermissionsの項目で必要なものをNo accessRead-onlyRead and write の中から選べるようになっているので 一番上のActionsRead and writeにします。

image

他のトリガーを読んで、結果を待ち、成功失敗の結果を取得するもの

Trigger Workflow and Wait - GitHub Marketplace

This action triggers a workflow in another repository and waits for the result

Trigger Workflow and Wait - GitHub Marketplace

Railsの方の設定に追加する

image

Next.jsで強制的にテストを失敗させた場合

image

実験・調査・比較結果

内容によって、実験や、調査、比較するものがあれば観点をまとめて比較を書く

箇条書きでダラダラ書くのではなく、表やグラフを用いるべし

考察・提案

実験した結果や調査した内容から得られた知見をまとめる

例: こういうパターンにはAが適しており、次のパターンではBが適している

Githubに検証した際に作ったURLがあればここに貼る

まとめ

行なった内容のまとめ目的から本文の内容を要約する。概要とほぼ同じで良いが、もう少しフランクに感想なども付け加えて良い

参考リンク

  • https://zenn.dev/slowhand/articles/4fe99377185100
  • https://qiita.com/takano-h/items/cf17ec515b9d850b4923
  • https://qiita.com/kyntk/items/7ff8d312480ec913f049
  • https://zenn.dev/mybest_dev/articles/a8f3096821851c#rails側のスキーマ定義からフロントエンド用定義の自動生成
  • ドキュメントのURL

PR

XではUZUMAKIの新しい働き方や日常の様子を紹介!ぜひフォローをお願いします!

noteではUZUMAKIのメンバー・クライアントインタビュー、福利厚生を紹介!

UZUMAKI|note

株式会社UZUMAKIです。主に代表の工藤が投稿してますが、ほかのメンバーもたまに投稿します。https://uzumaki-inc.jp/ 受託開発を生業としてますが、リモートワークを通じて、今までにない組織形態での働き方を模索、実践しています。

UZUMAKI|note

UZUMAKIではRailsエンジニアを絶賛募集中です。

↓の記事を読んでご興味を持っていただいた方は、ぜひ応募よろしくお願いします!

UZUMAKI 採用情報

これを実践するには、通常の社会で求められる上司による部下の管理が必要という前提が不要になってきます。例えば、時間管理をしない、圧倒的な性善説で動く、メンバーは自発的に動くということをUZUMAKIでは当たり前のように行っております。 現在は、 コーポレートサイト記載の企業 ...

UZUMAKI 採用情報

是非応募宜しくおねがいします!