Next.js(React)×Ruby on Rails チュートリアル(TypeScript対応)

当サイトでは一部リンクに広告が含まれています
アイキャッチ

近年では、フロントエンドとバックエンドでそれぞれ別なフレームワークを使用して開発することが増えてきました。

しかし、おそらく多くの学習者の方はフロントエンドバックエンド(Ruby on Rails / Laravel / Django など)のどちらかしか学習していないかもしれません。

そこで、今回のチュートリアルでは初めての方でもモダンな開発をできるよう、以下のような内容を学びます!

  • Next.js (React) を使用したフロントエンドの開発
  • TypeScriptを使用した高品質なコードの記述
  • Ruby on Railsを使用したバックエンド (API) の開発
  • Next.js (React) とRailsを連携させたフルスタックアプリケーションの構築

このチュートリアルでは、Next.js (TypeScript) 、そしてRailsを使用して Todo アプリを作成します。

これらの技術に完全に初めて触れる方であっても、アプリの開発が可能となることを目指します!

使用する技術の紹介

Next.js (React)

Next.jsは、モダンなフロントエンドフレームワークの一つです。
Next.js は React をベースにしており、React で作成されたコンポーネントを使用することができます。

実際の現場でもNext.jsを採用している企業は多く、Next.jsの人気は年々高まっています。

Ruby on Rails

Ruby on Railsは、Rubyで書かれたWebアプリケーションフレームワークです。

Railsは、データベースアクセス、ルーティング、セキュリティなどのバックエンドの機能を提供します。

このチュートリアルでは、RailsをAPIサーバーとして使用し、Next.js をフロントエンドとして使用します。
Railsはデータの永続化とAPIエンドポイントの提供に焦点を当て、Next.js はユーザーインターフェースの作成に焦点を当てます。

補足

本教材では macOS での開発を想定しています。
そのため「ターミナル」や「コマンドライン」といった用語を使用していますが、Windows での開発を行う場合は「コマンドプロンプト」や「PowerShell」を使用してください。

作業フォルダについて

今回のアプリ構成は、フロントエンド(Next.js)とバックエンド(Ruby on Rails)に分かれています。
そして、ローカル環境では ~/Desktop/workspace/todo-frontend~/Desktop/workspace/todo-backend にそれぞれのプロジェクト用のフォルダを配置していきます。

そのため、例えば Next.js のコマンドを実行する際は ~/Desktop/workspace/todo-frontend に移動してから実行する必要がありますのでご注意ください。

# Next.js のコマンドを実行する場合
cd ~/Desktop/workspace/todo-frontend
npm run dev # Next.js の開発サーバーを起動するコマンド

一方、Rails のコマンドを実行する際は ~/Desktop/workspace/todo-backend に移動してから実行します。

# Rails のコマンドを実行する場合
cd ~/Desktop/workspace/todo-backend
bundle exec rails s # Rails の開発サーバーを起動するコマンド
目次

Node.jsのインストール

まず、Node.js というJavaScriptの実行環境をインストールします。

Node.jsは、JavaScriptをブラウザ以外の環境で実行するためのプラットフォームです。
Node.jsをインストールすることで、ターミナルかJavaScriptを実行できるようになります。

今回は Node.js を学習するために、Node.jsをインストールします。

Macの場合:

まずHomebrewをインストールしていない場合はHomebrewをします。
Homebrewのインストール方法は、Homebrew公式ウェブサイトを参照してください。

次に、Node.js のバージョンを管理するために、Nodebrewをインストールします。

$ brew install nodebrew

インストール完了後、以下のコマンドで nodebrew のバージョンが表示されればOKです。

$ nodebrew -v
#=> nodebrew 1.2.0

Nodebrewをインストールしたら、Node.jsをインストールします。

Node.js には最新バージョンと安定バージョンがあります。
最新バージョンは最新の機能が使えますが、作られたばかりのためバグがあったり、すぐに機能が変更される可能性があります。
一方、安定バージョンは最新バージョンよりも古いですが、バグが少なく、機能も安定しています。

慣れないうちは安定バージョンを使うことをおすすめしますので、今回は安定バージョンをインストールします。

$ nodebrew install-binary stable

なおこのとき、環境によっては以下のようなエラーが出ることがあります。

Fetching: https://nodejs.org/dist/v20.3.0/node-v20.3.0-darwin-arm64.tar.gz
Warning: Failed to create the file
Warning: /Users/username/.nodebrew/src/v20.3.0/node-v20.3.0-darwin-arm64.tar.gz
Warning: : No such file or directory
...

この場合は、以下のコマンドを実行してから、再度インストールを試してください。

$ mkdir -p ~/.nodebrew/src

インストール完了後、以下のコマンドで nodebrew でインストールした Node.js のバージョンが表示されればOKです。

$ nodebrew ls
v20.3.0

current: none

ただし、現在のバージョンが none と表示されている場合は、以下のコマンドでインストールしたバージョンを指定してください。

$ nodebrew use v20.3.0

最後に、以下のコマンドで Node.js のバージョンが表示されればOKです。

$ node -v
#=> v20.3.0

Windowsの場合:

  1. Node.js公式ウェブサイトを開きます。
  2. ホームページで推奨されているLTS(Long Term Support)バージョンのインストーラーをダウンロードします。
  3. ダウンロードしたインストーラーを実行します。
  4. インストールウィザードの指示に従って、Node.jsをインストールします。

パッケージマネージャの設定

Node.jsのインストールが完了したら、パッケージマネージャを設定します。

パッケージマネージャは、依存関係の解決やライブラリの管理に使用されます。
ここでは、npm(Node Package Manager)を使用します。

ターミナル(Terminal)を開き、以下のコマンドを実行します。

npm install npm@latest -g

これにより、npmが最新バージョンに更新されます。

Node.jsのインストールとパッケージマネージャの設定が完了しました。
次のセクションでは、TypeScriptのセットアップに進みます。

Next.jsのインストール

では、このセクションでは、Next.jsのインストール方法を説明します。

Next.jsはモダンなフロントエンド開発に欠かせないツールです。
ここではNext.jsを用いたプロジェクトのセットアップ方法を説明します。

プロジェクト作成

ターミナルを開き、デスクトップにプロジェクト用のディレクトリを作成しましょう。

以下のコマンドを実行してデスクトップに移動し、workspace という名前のディレクトリを作成します。

$ cd ~/Desktop
$ mkdir workspace
$ cd workspace

では、Next.jsを用いた新しいプロジェクトを作成します。

まず、以下のコマンドを実行します。

$ npx create-next-app@latest --typescript todo-frontend

なお、この時に create-next-app というコマンドのインストール、またはアップデートが求められる場合がありますので y と入力してください。

Need to install the following packages:
  create-next-app@13.4.9
Ok to proceed? (y)

コマンドを実行してプロジェクト作成を開始すると、設定の選択を求められますので、以下の通り選択してください。

✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … No
✔ Would you like to use App Router? (recommended) … No
✔ Would you like to customize the default import alias? … No

※Next.js 側のアップデートにより、多少選択肢が変わる場合がありますが、基本的にはデフォルトの選択肢で問題ありません。

上記のうち App Router という項目は Next.js 13 から追加された機能ですが、今回は使用しませんので No を選択してください。

各種設定が終わると以下のようにターミナルに表示されます。

127 packages are looking for funding
  run `npm fund` for details
Success! Created todo-frontend at /Users/username/Desktop/workspace/todo-frontend

ポート番号の設定

npm run dev というコマンドで Next.jsのサーバを起動するのですが、その前にポート番号3001 に変更するために package.json を編集します。

"scripts": {
  "dev": "next dev -p 3001",
  "build": "next build",
  "start": "next start -p 3001",
  // ...

上記の設定により、Next.jsのサーバは 3001 番ポートで起動するようになります。


ポート番号とは、コンピューターに接続されたネットワーク上で、通信を行うために割り当てられた番号のことです。
例えば、ブラウザで http://localhost:3001 というURLを開くと、 3001 番ポートで通信を行うことになります。

動作確認

では、 npm run dev を実行してNext.jsのサーバを起動しましょう。

$ cd ~/Desktop/workspace/todo-frontend
$ npm run dev

今後、Next.jsのサーバを起動する際は、このコマンドを実行するようにしましょう。
上記のコマンドを実行すると、以下のようにターミナルに表示されます。

$ npm run dev

> todo-frontend@0.1.0 dev
> next dev -p 3001

- ready started server on 0.0.0.0:3001, url: http://localhost:3001
(node:65209) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
- event compiled client and server successfully in 114 ms (18 modules)
- wait compiling...
- event compiled client and server successfully in 86 ms (18 modules)

ターミナルがこのような表示である間はずっとNext.jsのサーバが起動していることに注意しましょう.

それでは表示されたURLである http://localhost:3001 をブラウザで開いてみます。

以下のような画面が表示されれば、正しくNext.jsのインストールが完了しています。

next_初回起動

ブラウザで確認できたら、先ほど npm run dev を実行したターミナルで Ctrl + C を押して、Next.js のサーバを終了させましょう。

以上で Next.js のインストールは完了です。

次の章では、バックエンドのセットアップに進みます。

不要な CSS の削除

Next.js ではデフォルトで styles/globals.css というファイルが作成されますが、今回には不要な記述があるので削除しておきましょう。
以下の記述を削除してください。

styles/globals.cssの以下をすべて削除

:root {
  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 214, 219, 220;
  --background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

body {
  color: rgb(var(--foreground-rgb));
  background: linear-gradient(
      to bottom,
      transparent,
      rgb(var(--background-end-rgb))
    )
    rgb(var(--background-start-rgb));
}

全体のスタイルを調整

pages/_document.tsx というファイルを編集し、以下のように変更してください。

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html lang="en">
      <Head />
      <body className="bg-blue-100">
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

ここでは body 全体に bg-blue-100 というクラスを指定することで、基本の背景色を設定しています。

(補足) サーバの起動について

今後、Next.js に関するコマンドを実行する際は今回と同じように、ターミナルで todo-frontend プロジェクトに移動してから実行してください。

$ cd ~/Desktop/workspace/todo-frontend

例えば、今後何度も「サーバを起動して動作確認」という手順が出てきます。
その際は以下のコマンドを実行してあげればOKです。

$ cd ~/Desktop/workspace/todo-frontend
$ npm run dev

また、何かコマンドを実行する際には一度サーバを Ctrl + C で停止させてから実行してください。

Ruby のインストール

では次に、Ruby on Rails を動かすために必要な環境を整えていきましょう。
今回は、Ruby のインストールを行います。

Ruby インストール (Mac を使用している場合)

まずはプロジェクトディレクトリである ~/Desktop/workspace に移動しましょう。

$ cd ~/Desktop/workspace

次に、Ruby のインストールを行います。

本カリキュラムで使用する Ruby のバージョンは 3.1.4 ですので、ローカルにインストールしていきましょう。


3.0 以上がインストールされていれば大抵はそのまま使っても問題ありませんが、予期せぬエラーが発生する可能性がありますのでできれば 3.1.4 を使用してください。

Mac ユーザーの場合、rbenv 複数のバージョンを切り替えることが出来る rbenv を使用します。

rbenv インストール

まだ rbenv をインストールしていない方は、以下の記事を参考にインストールしてください。

https://zenn.dev/blendthink/articles/f0964e3642d33b

rbenv は環境によってインストール方法が異なる場合があるため、うまくいかない場合は他のサイトも参考にしてみましょう。


以前から rbenv をインストールしていた方は、rbenv と ruby-build のアップグレードを行いましょう。
これらのバージョンが古いと、今回必要な Ruby 3.1.4 をインストールできない場合があるためです。

$ brew upgrade rbenv ruby-build

もちろん、このタイミングで rbenv をインストールした方は不要です。

Ruby 3.1.4 インストール

rbenv のインストールが終わったら、Ruby のバージョンを指定してインストールします。

次のコマンドを実行して Ruby 3.1.4 をインストールします。

$ rbenv install 3.1.4

バージョン切り替え

特にエラー無くインストールが完了したら、workspace ディレクトリ配下で使用する Ruby のバージョンを固定します。
バージョンを固定するには rbenv local コマンドを使用します。

$ rbenv local 3.1.4

バージョン確認

最後にバージョンを確認するコマンドを実行し、想定したバージョンの Ruby を使えるようになっていることを確認します。
出力内容は環境によって多少異なりますが ruby 3.1.4 という文字列が表示されていれば成功です。

$ ruby -v
ruby 3.1.4...

Ruby インストール (Windows を使用している場合)

Windows ユーザーの場合は RubyInstaller というインストーラーを使用します。

Ruby 3.1.4 インストール

具体的な手順についてはこちらの記事を参考に、Ruby 3.1.4 用 RubyInstalelr を使用してインストールします。
※以下の記事とはバージョンが異なりますが、手順は同じです。

https://zenn.dev/shuichi/articles/ruby-install#windows-10

バージョン確認

RubyInstaller でのインストールが完了したら最後に ruby -v をコマンドを PowerShell などのターミナルで実行します。
環境によって出力内容は多少異なりますが、Ruby 3.1.4 という文字列が表示されていれば成功です。

$ ruby -v
ruby 3.1.4

Ruby on Railsのインストール

では、Ruby on Rails をインストールしていきましょう。

Gem のインストール

それでは、Rails を動かすために必要な Gem をインストールしていきましょう。

Ruby では便利なライブラリが多数存在しており、それらはGemといいます。
本カリキュラムで使用する Ruby on Rails というフレームワークも Gem の一つです。

その Gem を管理するために Bundler というツールを使用します。

Bundler の概要

それ以外にも多数の Gem を使用することになりますが、
ある Gem を使用するためには他の Gem が必要であり、
しかもそのバージョンは x.x.x 以上で… といった依存関係を管理しなければいけません。

こういった面倒な依存関係を管理するために使用するのが、この章でインストールする Bundler です。
(Bundler 自体も Gem の一つです。)

Bundler は開発現場では必ずといっていい程使用する Gem ですので、
この後出てくる GemfileGemfile.lock を含めて知っておくべき概念です。

是非、手順を進める前にこちらの参考サイトをご一読ください。
多少ボリュームはありますが、開発現場に入るかもしれない方は方は読んでおくことをおすすめします。

https://nishinatoshiharu.com/fundamental-gem-knowledge/

Bundler インストール

Ruby をインストールしてさえいれば、gem install コマンドで好きな Gem をインストールします。
ターミナルを開き、次のコマンドを実行します。
ここではバージョンを指定して Bundler をインストールしましょう。

$ gem install bundler -v "2.3.15"
Fetching bundler-2.3.15.gem
Successfully installed bundler-2.3.15
Parsing documentation for bundler-2.3.15
Installing ri documentation for bundler-2.3.15
Done installing documentation for bundler after 1 seconds
1 gem installed

インストールできたら、指定したバージョンがインストールできているかを確認します。

$ bundler -v
Bundler version 2.3.15

Gemfile 作成

Bundler を使うときには予め Gemfile という、インストールしたい gem の情報をまとめたファイルを作成します。

$ bundle init

上記のコマンドを実行したディレクトリ (workspace) 内に
Gemfile というファイルが作成されていることを確認してください。

作成された Gemfile の中身を VSCode 等、お好きなエディタで開き、一旦中身を消してから以下のように編集します。

source 'https://rubygems.org'

gem 'rails', '~> 7.0.6'

Gemfile では上記のように、インストールしたい Gem (ここでは rails) とそのバージョンを指定しています。

バージョンを何も書かなければ常に最新版がインストールされますが、
上記の書き方では rails 7.0.x の最新をインストールすることになります。

バージョン指定方法などについて興味がある方は、こちらの記事を参考にしてください。

https://ama-tech.hatenablog.com/gemfile-version-specifiers

Gem のインストール (bundle install)

Gemfile を workspace ディレクトリ内に用意できたら、Gemfile の定義に従って Gem をインストールします。

インストールするコマンドは bundle install というコマンドですが、
ローカルの環境を汚さないように workspace ディレクトリ内に Gem をインストールすることをおすすめします。

そのため、/workspace/.bundle ディレクトリ内にインストールするよう設定するよう以下のコマンドを実行しましょう。

$ bundle config set path '.bundle'

設定できたら、bundle install コマンドを実行します。

$ bundle install
...(略)
Bundle complete! xx Gemfile dependencies, xxx gems now installed.

ただし、bundle install コマンドはお使いのPCによっては互換性の問題により失敗する場合があり、
初学者の方がつまずきやすいポイントなので注意しましょう。

ターミナルにBundle complete!と表示されていれば問題なくインストールできていますが、
そうでなければ何かしらのエラーで失敗しています。

原因は様々ですのでまずはエラーメッセージで Google 検索して調べるのが近道です。

インストールした Gem を実行

さて、/.bundleディレクトリ内に Gem がインストールされました。
これで workspace ディレクトリ内ではインストール済みの Gem を使用することができます。

ただし、Bundler で依存関係を解決しながらインストールした Gem をコマンドで使用する際は、
Gem 専用コマンドの前に bundle exec を付ける必要があるので注意してください。
(厳密にいえば付けなくても動くコマンドも一部ありますが、混乱を避けるためにも常に bundle exec を付けておくことをおすすめします。)

https://qiita.com/d0ne1s/items/fa2dafcee02e963fe997

例えば rails のバージョンを確認するには通常 rails -v と実行しますが、今回の場合は bundle exec rails -v と実行します。

$ bundle exec rails -v
Rails 7.0.6

Ruby on Rails プロジェクトの作成

ここまでで Ruby と Bundler のインストールが完了しました。

次に Ruby on Rails のプロジェクトを作成します。

今回、フロントエンドは Vue.js を使用しているため、Rails の役割は API サーバーとしてのみ使用します。

こういう場合、Rails を作成するためのコマンドである rails new には --api オプションを付けて実行します。
workspace ディレクトリ内で以下のコマンドを実行してください。

$ bundle exec rails new todo-backend --api --skip-bundle --skip-test --skip-turbolinks

プロジェクトに必要な Gem のインストール

Rails のプロジェクトが作成できたら、そのディレクトリに移動してから Rails プロジェクトに必要な Gem のインストールを行います。

$ cd todo-backend
$ bundle install

Rails サーバーの起動

Gem をインストールできたら、Rails サーバーを起動してみましょう。
Rails サーバとは、Rails で作成したアプリケーションを実行するためのサーバーです。

Rails で作成したアプリケーションは、Rails サーバーを起動していないと実行できません。
今後、前回作成した Vue.js と連携させ、見た目(フロントエンド)と機能(バックエンド)を分けて開発を進めていきます。

Rails サーバーを起動するには、以下のコマンドを実行します。

$ bundle exec rails s


今回のアプリではフロントエンドとバックエンドを分けて開発を進めるため、それぞれのサーバを同時に起動する必要があります。
そのため、それぞれのサーバを起動するためには複数のターミナルを起動しておく必要があります。
macOS であれば、command + T でターミナルを複数起動できますが、それ以外の環境の場合は例えば「PowerShell 複数ターミナル」などで検索してみてください。

サーバーが起動したら、ブラウザで http://localhost:3000 にアクセスしてみてください。

以下のような画面が表示されれば成功です。

rails_初回起動

(補足) バックエンド関連コマンドの実行

今後、Rails に関するコマンドを実行する際は今回のように、ターミナルで todo-backend プロジェクトに移動してから実行してください。

$ cd ~/Desktop/workspace/todo-backend

例えば、今後何度も「サーバを起動して動作確認」という手順が出てきます。
その際は以下のコマンドを実行してあげればOKです。

$ cd ~/Desktop/workspace/todo-backend
$ bundle exec rails s

また、何かコマンドを実行する際には一度サーバを Ctrl + C で停止させてから実行してください。

フロントエンドとバックエンドの連携

Next.js(JavaScriptで書かれたWebアプリケーションを作成するためのフレームワーク)のアプリケーション(フロントエンド)が、
Rails(RubyでWebアプリケーションを開発するためのフレームワーク)のアプリケーション(バックエンド)と通信するようにします。

CORSとは?

まずはじめに、CORS(Cross-Origin Resource Sharing)という考え方を理解するために、
バックエンドのRailsアプリケーションでrack-corsというライブラリを使用します。


CORS (Cross-Origin Resource Sharing)とは、ウェブブラウザがあるオリジン(ドメイン・ポート・プロトコルの組み合わせ)からロードされたページが、
異なるオリジンのリソースに対して安全にアクセスできるようにする仕組みです。

具体的には、http://localhost:3001(フロントエンド)というアドレスからhttp://localhost:3000(バックエンド)という別のアドレスへの通信は、異なるオリジン間の通信となります。
この場合、予期せぬオリジン(リクエスト元)からの通信を防ぐため、RailsアプリケーションではデフォルトでCORSが有効になっています。

それゆえ、Next.jsからのリクエストを許可するためには、CORSの設定を行う必要があります。

rack-corsのインストール

まずは、RailsのGemfile(Rubyのプロジェクトで使用するライブラリを管理するファイル)でコメントアウトされているgem 'rack-cors'のコメントを外します。
コメントを外すことで、このライブラリをプロジェクトで利用可能にします。

gem 'rack-cors' # この行のコメントアウトを外す(# を削除する)

次に、backend ディレクトリのターミナルで以下のコマンドを実行し、新たに追加したライブラリ(gem)をインストールします。
ここでのbundle installコマンドは、Gemfileに記載されたライブラリをインストールするためのものです。

$ cd ~/Desktop/workspace/backend
$ bundle install

これにより、CORSの設定を手助けするライブラリであるrack-corsがインストールされました。

rack-corsの設定

次に、Next.jsからのリクエストを許可するための設定を行います。
この設定は、Next.jsがローカル環境であるhttp://localhost:3001で動作することを前提としています。

設定は、Railsのディレクトリ内の config/initializers/cors.rb ファイルを開き、中身を以下のように丸ごと置き換えることで行います。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3001' # Next.jsを動作させているアドレスとポート番号

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

この設定により、localhost:3001(Next.jsが動作しているアドレス)からの全ての種類のリクエスト(GET, POST, PUTなど)を許可するようになりました。

なお、Rails サーバを起動していた場合は、新たな設定を反映させるために一度 Ctrl + C でサーバを停止し、再度 bundle exec rails s コマンドでサーバを起動しておきます。

$ bundle exec rails s

以上で、フロントエンド(Next.js)とバックエンド(Rails)の連携設定は完了です。
次はいよいよ、これらのフレームワークを使って、Todo機能の実装を行っていきます。

Todo 一覧機能の実装

今回のチュートリアルでは、Todo 一覧機能を作成します。
Todoとは、Taskや、日本語では「やることリスト」を意味します。
この機能はプログラミングの基本的な機能の一つであり、多くのウェブアプリケーションに実装されています。まず、バックエンド(API)側である Ruby on Rails の実装を行います。
バックエンドとは、フロントエンド(ユーザインターフェース)から受け取ったリクエストを処理する部分です。
そしてその後、フロントエンド(UI)側である Next.js の実装を行います。
フロントエンドとは、直接ユーザと対話する部分で、具体的にはウェブブラウザ上で動作する部分を指します。

Todo 一覧ページの内容

Todo 一覧ページでは、以下の情報を表示します。

  • Todo のタイトル
  • Todo の内容
  • Todo 詳細ページへのリンク

これらは、ユーザが何をしなければならないのか一覧できるようにするための情報です。

Todo 一覧 API の実装 (Ruby on Rails)

Todo モデルの作成

まず最初に、データベースに格納するデータの形を決める「モデル」を作成します。
このチュートリアルでは、Todo モデルを作成します。

$ cd ~/Desktop/workspace/todo-backend
$ bundle exec rails generate model Todo title:string content:text
$ bundle exec rails db:migrate

上記のコマンドを実行すると、Todo というモデルが作成され、それには「タイトル」(title)と「内容」(content)という2つの属性が追加されます。
bundle exec rails generate model Todo title:string content:text の部分でモデルの名前とその属性を指定しています。
最後の bundle exec rails db:migrate で、これらの変更をデータベースに反映させます。

Todo コントローラーの作成

次に、モデルとビュー(ユーザーが見る画面)をつなぐ役割を持つ「コントローラー」を作成します。

$ bundle exec rails generate controller Todos

このコマンドで app/controllers/todos_controller.rb が作成されます。
作成された todos_controller.rb ファイルを以下のように編集します。

app/controllers/todos_controller.rb

class TodosController < ApplicationController
  # GET /todos
  def index
    # 日付が新しい順に10件まで取得する
    @todos = Todo.all.order(created_at: :desc).limit(10)

    render json: @todos
  end
end

上記のコードでは、Todo モデルから最新の10件のデータを取得し、それを保存日時の新しい順に並べ替えています。
そして render json: @todos で、取得したデータを JSON 形式で返しています。
ここでの JSON 形式とは、データを交換する際の形式の一つで、JavaScript のオブジェクトとして扱うことができます。

ルーティング設定

次に、ルーティングを設定します。
ルーティングとは、ユーザがアクセスしたURLによってどのコントローラのどのアクション(処理)を呼び出すかを決定する設定のことです。

config/routes.rb を以下のように編集します。

config/routes.rb

Rails.application.routes.draw do
  resources :todos, except: [:new, :edit]
end

上記のコードでは、resources :todos という記述で、Todo モデルに対する CRUD(Create, Read, Update, Delete)操作を行うためのルーティングを自動生成しています。
ただし、newedit は、新規作成や編集画面を作成する場合に使用するルーティングですが、このチュートリアルではそれらの画面は作成しないので、except: [:new, :edit] というオプションを指定して除外しています。

テストデータの作成

次に、実際にデータベースにデータを登録します。
テストデータは、db/seeds.rb ファイルに記述します。

db/seeds.rb を以下のように編集します。

db/seeds.rb

Todo.create(title: 'Todo 1', content: 'Todo 1 の内容')
Todo.create(title: 'Todo 2', content: 'Todo 2 の内容')
Todo.create(title: 'Todo 3', content: 'Todo 3 の内容')

上記のコードでは、Todo.create で Todo モデルのデータを作成しています。
具体的には、「Todo 1」「Todo 2」「Todo 3」という3つの Todo を作成しています。

以下のコマンドでデータベースにデータを登録します。

$ bundle exec rails db:seed

これで、Todo モデルのデータが3件登録されました。

API の動作確認

では、実際に API が動作するか確認してみましょう。

まず、Rails のサーバを起動します。

$ bundle exec rails s

これで、ローカルホストにサーバが起動します。

次に、ブラウザで http://localhost:3000/todos にアクセスします。
アクセスすると、以下のような JSON 形式のデータが表示されます。

なお、Chromeの拡張機能であるJSON Formatterを利用すると、JSONデータが見やすく表示されます。おすすめです。

[
  {
    "id": 3,
    "title": "Todo 3",
    "content": "Todo 3 の内容",
    "created_at": "2023-07-08T00:00:00.000Z",
    "updated_at": "2023-07-08T00:00:00.000Z"
  },
  {
    "id": 2,
    "title": "Todo 2",
    "content": "Todo 2 の内容",
    "created_at": "2023-07-08T00:00:00.000Z",
    "updated_at": "2023-07-08T00:00:00.000Z"
  },
  {
    "id": 1,
    "title": "Todo 1",
    "content": "Todo 1 の内容",
    "created_at": "2023-07-08T00:00:00.000Z",
    "updated_at": "2023-07-08T00:00:00.000Z"
  }
]

これで、Todo 一覧 API の実装が完了しました。
これらのデータが、次に行うフロントエンドの実装で使用されます。

Todo 一覧 UI の実装 (Next.js)

ここでは、Next.jsを使用して、Todo一覧画面の作成を行います。

トップページの作成

最初に、トップページを作成します。
トップページは pages/index.js ファイルで管理されます。
以下のようにコードを書き換えてみましょう。

todo-frontend/pages/index.tsx

// トップページ
export default function Home() {
  return (
    <div className="flex flex-col justify-center items-center">
      トップページ
    </div>
  );
}

ここで、export default function Home()とは、Homeという名前の関数を外部から利用できるように公開するための記述です。
この関数はトップページのUIを生成しています。

次に、サーバを起動して、ブラウザで http://localhost:3001 にアクセスしてみましょう。

$ cd ~/Desktop/workspace/todo-frontend
$ npm run dev

画面に「トップページ」と表示されていれば、正しく設定できています。

toppage_test

API からデータを取得する準備

次に、APIからデータを取得するための準備を行います。

APIとは、アプリケーション同士がデータをやり取りするためのインターフェースです。
APIを通じて、Todoのデータを取得することになります。

データの取得はaxiosというライブラリを使用しますので、まずはaxiosをインストールしましょう。

$ npm install axios

axiosとは、JavaScriptでAPIと通信するためのライブラリで、このライブラリを使うことでAPIとの通信を簡単に行うことができます。

Todo の型定義

次に、Todoの型定義を行います。
型定義とは、データの形状をコードで定義することを指します。

todo-frontend フォルダのルート(直下)に types フォルダを作成します。
その中に、Todo.ts というファイルを作成して、以下のように記述します。

todo-frontend/types/Todo.ts

export type TodoType = {
  id: number;
  title: string;
  content: string;
};

ここでexport type TodoType = {...};という記述は、TodoTypeという名前の型を作成し、外部から利用できるように公開することを示しています。
TodoTypeの中には、id、title、contentの3つのフィールドがあり、それぞれがnumber型、string型、string型のデータを保持することを示しています。

Todo コンポーネント (Todo.tsx) の作成

続いて、Todoの表示に関するコンポーネントを作成します。

まずは、todo-frontend フォルダのルート(直下)に components フォルダを作成します。
この中に、Todo一覧の表示を担当するコンポーネント (Todos.tsx) と、個々のTodoを表示するコンポーネント (Todo.tsx) を作成します。

まず、components フォルダの中に Todo.tsx を作成します。

todo-frontend/components/Todo.tsx

import { TodoType } from '../types/Todo';

// Todo一つを表示するコンポーネント
const Todo = ({ todo }: { todo: TodoType }) => {
  return (
    <div className="focus:outline-none mb-7 bg-white p-6 shadow rounded">
      <div className="flex items-center border-b border-gray-200 pb-6">
        <div className="flex items-start justify-between w-full">
          <div className="pl-3">
            <p className="focus:outline-none text-lg font-medium leading-5 text-gray-800">
              {todo.title}
            </p>
          </div>
        </div>
      </div>
      <div className="px-2">
        <p className="focus:outline-none text-sm leading-5 py-4 text-gray-600">{todo.content}</p>
      </div>
    </div>
  );
};

export default Todo;

上記のコードでは、Todoのタイトルと内容を表示するためのReactコンポーネントを作成しています。
これにより、Todoの情報を表示する部分を別のコンポーネントで再利用することができます。

Todos コンポーネント (Todos.tsx) の作成

次に、Todoの一覧を表示するTodosコンポーネントを作成します。
components フォルダの中に Todos.tsx を作成し、以下のように編集します。

todo-frontend/components/Todos.tsx

import { useEffect, useState } from 'react';
import axios from 'axios';
import { TodoType } from '../types/Todo';
import Todo from './Todo';
import Link from 'next/link';

// Todo一覧を表示するコンポーネント
const Todos = () => {
  // Todo一覧を管理するState
  const [todos, setTodos] = useState<TodoType[]>([]);

  // Todo一覧を取得する関数
  const fetchTodos = async () => {
    // APIからTodo一覧を取得する
    try {
      const res = await axios.get<TodoType[]>('http://localhost:3000/todos');

      // 取得したTodo一覧をStateにセットする
      setTodos(res.data);
    } catch (err) {
      console.log(err);
    }
  };

  // コンポーネントがマウントされたタイミングでTodo一覧を取得する
  useEffect(() => {
    // Todo一覧を取得する関数を呼び出す
    fetchTodos();
  }, []);

  return (
    <div className="space-y-6 w-3/4 max-w-lg pt-10">
      <label className="block text-xl font-bold text-gray-700">Todo Index</label>
      <div className="items-center justify-center">
        {todos.map((todo) => (
          <Link
            href={`todos/${todo.id}`}
            key={todo.id}
          >
            <Todo todo={todo} />
          </Link>
        ))}
      </div>
    </div>
  );
};

export default Todos;

コードの説明をしておきましょう。

まず、useStateを使用してTodoの一覧を管理するtodosというstateを設定します。

次に、axiosを使用してAPIからTodoのデータを取得するfetchTodos関数を定義します。
この関数は、APIからデータを取得し、結果をtodosステートにセットします。
そして、このfetchTodos関数をuseEffect内でコンポーネントがマウントされたときに実行します。

最後に、各TodoをTodoコンポーネントでレンダリングし、その結果を表示します。

なお、この時点ではまだ Todo の詳細ページは作成していませんが、Link コンポーネントの href には todos/${todo.id} を指定しています。
これは、後ほど作成する Todo の詳細ページのパスを指定しています。

Todo 一覧コンポーネントの読み込み

pages/index.tsxのファイルを更新して、Todosコンポーネントをレンダリングします。
これにより、アプリケーションのホームページでTodoの一覧が表示されます。

todo-frontend/pages/index.tsx

import Todos from '@/components/Todos';

// トップページ
export default function Home() {
  return (
    <div className="flex flex-col justify-center items-center">
      <Todos />
    </div>
  );
}

このコードでは、Todosコンポーネントをインポートし、<Todos /> でレンダリングしています。

動作確認

サーバを起動して、ブラウザで http://localhost:3001 にアクセスすると、作成したTodoの一覧が表示されます。
これが成功したら、Todo一覧のUIの作成は完了です。

todo_index

Todo 詳細機能の実装

さて、次のステップは、各Todoの詳細を表示する機能を実装することです。

Todo 詳細ページの内容

Todo 詳細ページは、以下の情報をユーザーに提供します:

  • Todo のタイトル
  • Todo の内容
  • Todo の編集ページへのリンク
  • Todo 一覧ページに戻るリンク

これにより、ユーザーは各Todoの詳細を確認し、必要であれば編集することができます。

Todo 詳細 API の実装 (Ruby on Rails)

Todo コントローラーの実装

最初に、詳細表示用のアクションをTodo コントローラーに追加する必要があります。
Ruby on Railsでは、showアクションは詳細表示を担当しますので、app/controllers/todos_controller.rb を以下のように編集します。

app/controllers/todos_controller.rb

class TodosController < ApplicationController
  # ...略
  # GET /todos/:id
  def show
    @todo = Todo.find(params[:id])

    render json: @todo
  end
end

この show アクションでは、まず Todo.find(params[:id]) を使って、指定された ID(params[:id])の Todo モデルをデータベースから取得します。
次に、 render json: @todo というコードで、取得した Todo モデルを JSON 形式でレンダリング(変換)して、これをクライアントに返します。

API の動作確認

では、このAPIが正しく動作するか確認してみましょう。

ブラウザで http://localhost:3000/todos/1 にアクセスします。
これは、IDが1のTodoの詳細を取得するためのURLです。

ここで、前回作成した ID が 1 の Todo のデータが表示されれば、APIは正しく動作していると言えます。
以下のような JSON 形式のデータが表示されるはずです。

{
  "id": 1,
  "title": "Todo 1",
  "content": "Todo 1 の内容",
  "created_at": "2023-07-08T00:00:00.000Z",
  "updated_at": "2023-07-08T00:00:00.000Z"
}

以上で、Todo 詳細 API の実装は完了です。
これにより、指定されたIDのTodoの詳細情報を取得できるようになりました。

Todo 詳細ページの作成 (Next.js)

今回は、既に作成済みの Todo 詳細用のコンポーネント (Todo.tsx) を使用し、Todo 詳細ページを作成します。
この詳細ページは、pages/todos/[id]/index.tsx というパスとファイル名で作成します。

まず、pages の中に todos ディレクトリを作成します。その中には [id] という名前のディレクトリを作ります。
そして、その [id] ディレクトリの中に index.tsx という名前のファイルを作成します。

ここで使用しているのは Next.js の「動的ルーティング」という機能です。
この機能は、URLの一部を動的に変更して、それに応じたページを表示するためのものです。
例えば、[id] の部分は URL における Todo の ID に置き換わり、その ID の Todo の詳細を表示するページとなります。

Todo 詳細ページの実装

次に、pages/todos/[id]/index.tsx を以下のように編集します。

import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
import Link from 'next/link';
import Todo from '@/components/Todo';
import { TodoType } from '@/types/Todo';

// Todo詳細ページを表示するコンポーネント
const TodoDetail = () => {
  // ルーティング情報を取得する
  const router = useRouter();
  const { id } = router.query;

  // Todo情報を管理するState
  const [todo, setTodo] = useState<TodoType | null>(null);

  // idが変更されたら(=Todo詳細ページを開いたら)、Todoを取得する
  useEffect(() => {
    // Todoを取得する関数
    const fetchTodo = async () => {
      try {
        // APIからTodoを取得してStateにセットする
        const res = await axios.get(`http://localhost:3000/todos/${id}`);
        setTodo(res.data);
      } catch (err) {
        console.log(err);
      }
    };

    // idが存在する場合のみ、Todoを取得する
    if (id) {
      fetchTodo();
    }
  }, [id]);

  // Todoを取得中の場合は「Loading...」を表示する
  if (!todo) {
    return <div>Loading...</div>;
  }

  return (
    <div className="flex justify-center items-center">
      <div className="flex flex-col space-y-6 w-3/4 max-w-lg pt-10">
        <label className="block text-xl font-bold text-gray-700">Todo</label>
        <Todo todo={todo} />
        <div className="flex justify-end">
          <Link
            href="/"
            className="mt-auto font-medium text-blue-600 hover:bg-blue-300 focus:outline-none"
          >
            Back
          </Link>
        </div>
      </div>
    </div>
  );
};

export default TodoDetail;

このコードは、Todoの詳細ページを表示するためのReactコンポーネントです。

主な部分はuseEffectフックで、ここで指定されたidのTodoを取得しています。
idはURLから取得され、URLがhttp://localhost:3000/todos/1であれば、idは1となります。

そして、axios.getを使用してhttp://localhost:3000/todos/${id}からTodoを取得します。
このデータはsetTodoを使用してstateに保存されます。そして、このTodoデータはTodoコンポーネントに渡され、詳細情報として表示されます。

もしまだTodoデータが取得できていなければ(つまり、Todoデータがnullの場合)、”Loading…”と表示されます。

Todo 詳細ページの動作確認

それでは、Todo 詳細ページが正常に動作するか確認してみましょう。

ブラウザで http://localhost:3000/todos/1 にアクセスし、前回作成した Todo 1 のデータが表示されればOKです。

todo_detail_page

このページでは、特定のTodoの詳細が表示されます。
また、”Back”リンクをクリックすると、Todo一覧ページ(http://localhost:3000/)に戻ることができます。

一覧ページから各Todoをクリックすると、そのTodoの詳細ページに遷移することも確認してみてください。

Todo 作成機能の実装

次は Todo 作成機能を実装していきましょう。

今回のアプリでは、Todo 作成専用のページは作らず、Todo 一覧ページで Todo を作成できるようにします。

Todo 作成 API の実装 (Ruby on Rails)

Todo コントローラーの実装

まずは、Todo コントローラーに作成用のアクションを追加します。
Rails では create アクションが作成用のアクションに対応しているので、app/controllers/todos_controller.rb を以下のように編集します。

app/controllers/todos_controller.rb

class TodosController < ApplicationController
  # ...略

  # POST /todos
  def create
    @todo = Todo.new(todo_params)

    if @todo.save
      render json: @todo, status: :created, location: @todo
    else
      render json: @todo.errors, status: :unprocessable_entity
    end
  end

  def todo_params
    params.require(:todo).permit(:title, :content)
  end
end

create アクションでは、Todo.new で Todo オブジェクトを作成し、@todo.save でデータベースに保存しています。
保存に成功した場合は、render json: @todo, status: :created, location: @todo@todo を JSON 形式で返しています。

これで、Todo 作成 API の実装は完了です。

Todo 作成 UI の実装 (Next.js)

では、次は Next.js で Todo 作成画面を作成していきましょう。

今回のアプリでは Todo 一覧画面(トップページ)の中に Todo 作成フォームを表示する形にします。

Todo 作成フォームコンポーネントの実装

では、「Todo作成フォームコンポーネント」の実装方法について説明します。
このコンポーネントは、ユーザーが新しいTodoを作成できるようにするためのもので、トップページに表示されます。

まずはじめに、componentsディレクトリにCreateTodoForm.tsxという新しいファイルを作成します。
.tsx拡張子は、ReactコンポーネントをTypeScriptで書くためのものです。

次に、このファイルに以下のコードを書き込んでみましょう:

import { useState } from 'react';
import axios from 'axios';

// Todoを作成するフォーム
const CreateTodoForm = () => {
  // フォームの入力値を管理するstate
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  // フォームの入力値を更新する関数
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      // APIを呼び出して、Todoを作成する
      await axios.post('http://localhost:3000/todos', {
        todo: {
          title,
          content,
        },
      });

      // Todoの作成に成功したら、フォームの入力値をリセットする
      setTitle('');
      setContent('');

      // Todoの作成に成功したら画面を更新する(Todoを再取得するため)
      window.location.reload();
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="space-y-6 w-3/4 max-w-lg py-10">
      <form
        onSubmit={handleSubmit}
        className="space-y-6"
      >
        <label className="block text-xl font-bold text-gray-700">New Todo</label>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="タイトル"
          className="block w-full py-2 pl-3 pr-4 text-base border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-gray-500 focus:border-gray-500 sm:text-sm"
        />
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          placeholder="本文"
          className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-gray-500 focus:border-gray-500 sm:text-sm"
        />
        <button
          type="submit"
          className="mt-3 ml-auto flex justify-center py-2 px-8 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
        >
          Create
        </button>
      </form>
    </div>
  );
};

export default CreateTodoForm;

このコードは、「CreateTodoForm」コンポーネントを定義しています。
このコンポーネントは、Todoのタイトルと内容を入力できるフォームを作成するものです。

ここでは、ReactのuseStateというフックを使用して、フォームの各入力フィールドの現在の値を追跡しています。
また、axiosを使用して、サーバーにPOSTリクエストを送信して新しいTodoを作成します。

具体的には、handleSubmit関数では、フォームの送信イベントを処理します。
この関数では、まずデフォルトのフォーム送信の挙動を抑制するためにe.preventDefault()を呼び出しています。
次に、axios.postを使用して、新しいTodoをサーバーに送信します。
ここでは、Todoのタイトルと内容をサーバーに送信しています。

Todoの作成に成功したら、フォームの入力フィールドをリセットし、ページをリロードして新しいTodoが表示されるようにします。
もし何かエラーが発生したら、それをコンソールに出力します。

このコンポーネントは、最終的にフォームのHTMLマークアップを返します。
このマークアップは、タイトルと内容の入力フィールドと、フォームの送信ボタンを含んでいます。

以上が、「CreateTodoForm」コンポーネントの詳細な説明となります。
これで、このコンポーネントを使って、新しいTodoを作成できるようになります。

Todo 作成ページの実装

では、Todo 作成フォームコンポーネントをトップページに表示するために、トップページを編集していきましょう。

pages/index.tsx を以下のように編集します。

import Todos from '@/components/Todos';
import CreateTodoForm from '@/components/CreateTodoForm';

// トップページ
export default function Home() {
  return (
    <div className="flex flex-col justify-center items-center">
      <CreateTodoForm />
      <Todos />
    </div>
  );
}

これで、Todo 作成フォームが表示されるようになりました。

動作確認

では、Todo 作成フォームが正しく動作するか確認してみましょう。

Rails サーバと Next.js サーバは起動したままだと思いますので、ブラウザで http://localhost:3000 を開いてください。
以下のように Todo 作成フォームが表示されていると思います。

作成フォームの表示

フォームに入力して「Create」ボタンを押すと、Todo が作成されて一覧に表示されることが確認できます。

フォームへの入力
作成されたTodo表示の確認

Todo 編集機能の実装

次は Todo 編集の編集機能を実装していきましょう。

編集できるページでは、Todo のタイトルと内容を編集できるようにします。

Todo 編集 API の実装 (Ruby on Rails)

Todo コントローラーの実装

まず始めに、Todoアプリケーションの編集機能を作成するために、Todo コントローラーに新たなアクション(機能)を追加します。
Railsでは、データの更新(編集)には update アクションが使用されます。
それでは、実際に app/controllers/todos_controller.rb ファイルを編集してみましょう。

app/controllers/todos_controller.rb

class TodosController < ApplicationController
  # ...略

  # PATCH/PUT /todos/:id
  def update
    @todo = Todo.find(params[:id])

    if @todo.update(todo_params)
      render json: @todo
    else
      render json: @todo.errors, status: :unprocessable_entity
    end
  end

  # ...略
end

ここで、update アクションの中身について解説します。

まず、「@todo = Todo.find(params[:id])」の部分ですが、これはURLからTodoのIDを取得し、それに対応するTodoをデータベースから見つけ出すためのコードです。
ここで使用している params[:id] は、URLの中に含まれるパラメータ(ここではTodoのID)を取得するためのものです。

次に、「if @todo.update(todo_params)」の部分ですが、これは取得したTodoの情報を更新するためのコードです。
ここで使用している todo_params は、更新するデータの内容を定義している部分で、「ストロングパラメータ」と呼ばれます。
ストロングパラメータは、セキュリティの観点から特定のパラメータのみを許可し、不必要なパラメータをフィルタリングするためのもので、以前の create アクションを作成した際に実装したものをそのまま利用します。

この update アクションは、Todoの情報が正常に更新できた場合には、更新後のTodoの情報をJSON形式で返す(render json: @todo)。
一方、更新に失敗した場合には、エラーメッセージと共に unprocessable_entity のステータスコードを返します(render json: @todo.errors, status: :unprocessable_entity)。
これにより、エラーハンドリングを行うことができます。

Todo 編集 UI の実装 (Next.js)

では、次は Next.js で Todo 編集画面を作成していきましょう。

Todo 編集コンポーネントの作成

では、Todoの編集用のコンポーネントを作成しましょう。
components フォルダ内に EditTodoForm.tsx という新しいファイルを作成します。

このファイルは次のようになります。

components/EditTodoForm.tsx

import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';

type EditTodoFormProps = {
  id: number;
};

// Todo を編集するフォーム
const EditTodoForm = ({ id }: EditTodoFormProps) => {
  const router = useRouter();

  // フォームの入力値を管理するstate
  const [title, setTitle] = useState('');

  // フォームの入力値を管理するstate
  const [content, setContent] = useState('');

  // idが変更されたら(=Todo編集ページを開いたら)、Todoを取得してフォームの初期値を設定する
  useEffect(() => {
    // idが存在しない場合は、処理を中断する
    const fetchTodo = async () => {
      try {
        // idを元にTodoを取得する
        const res = await axios.get(`http://localhost:3000/todos/${id}`);

        // フォームの初期値を設定する
        const { title, content } = res.data;
        setTitle(title);
        setContent(content);
      } catch (err) {
        console.log(err);
      }
    };

    // idが存在する場合は、Todoを取得する
    if (id) {
      fetchTodo();
    }
  }, [id]);

  // フォームの入力値を更新する関数
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      // APIを呼び出して、Todoを更新する
      await axios.put(`http://localhost:3000/todos/${id}`, { todo: { title, content } });

      // Todoの更新に成功したら、Todo詳細ページに遷移する
      router.push(`/todos/${id}`);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="space-y-6 py-16">
      <form
        onSubmit={handleSubmit}
        className="space-y-6"
      >
        <label className="block text-xl font-bold text-gray-700">Edit Todo</label>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="タイトル"
          className="block w-full py-2 pl-3 pr-4 text-base border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-gray-500 focus:border-gray-500 sm:text-sm"
        />
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          placeholder="本文"
          className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-gray-500 focus:border-gray-500 sm:text-sm"
        />
        <button
          type="submit"
          className="mt-3 ml-auto flex justify-center py-2 px-8 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
        >
          Update
        </button>
      </form>
    </div>
  );
};

export default EditTodoForm;

では、このソースコードを順に見ていきましょう。

まずはじめに、Reactの useStateuseEffect、Next.jsの useRouter というフック、そしてaxiosというライブラリをインポートしています。

  • useState はReactのフックで、状態管理(state)を関数コンポーネントで行うために使います。
  • useEffect はReactのフックで、副作用(componentのマウントやアンマウント、propsやstateの変更時など)に対する操作を管理するために使用します。
  • useRouter はNext.jsのフックで、ルーティング(画面遷移)を制御するために使用します。
  • axiosは非同期通信を行うためのJavaScriptライブラリで、これによってサーバーにデータを送信したり、サーバーからデータを取得したりします。

次に EditTodoForm という関数コンポーネントを定義します。

このコンポーネントでは、Todoのタイトルと内容を管理するためのstateを用意します。
useState フックを使って titlecontent の2つのstateを作成し、それぞれのstateに初期値として空の文字列を設定します。

次に useEffect を使って、ページを開いたときにTodoを取得し、取得したTodoのタイトルと内容をフォームの初期値として設定します。
これによって、ユーザーがすでに入力しているTodoの情報を編集できるようになります。
また、axios.get を使って、サーバーからTodoを取得します。

そして、handleSubmit という関数を定義します。
この関数は、ユーザーがフォームを送信したときに実行され送信が成功したら、ユーザーをTodo詳細ページに遷移させます。

最後に、このコンポーネントをexportします。
これにより、他のファイルからこのコンポーネントをインポートできるようになります。

以上が、Todo編集用のコンポーネントの作成手順です。

Todo 編集ページの作成

Next.js の「動的ルーティング」機能を用いて、編集ページを作成するための edit.tsx ファイルを作ります。
この機能は URL の一部を動的に変化させ、その値を元に異なる内容を表示させるものです。

詳細ページの pages/todos/[id]/index.tsxhttp://localhost:3000/todos/1 のような URL にアクセスした際に表示されます。
今回新たに作成する pages/todos/[id]/edit.tsxhttp://localhost:3000/todos/1/edit のような URL にアクセスした際に表示されます。

ここで [] は任意の値を示します。
つまり、このフォルダ名が [id] となっているため、/todos/1/todos/2 のように、URL の一部を動的に変えてページを生成することが可能となります。

それでは、pages/todos/[id]/edit.tsx ファイルを以下のように編集しましょう。

import { useRouter } from 'next/router';
import Link from 'next/link';
import EditTodoForm from '@/components/EditTodoForm';

const EditTodoPage = () => {
  // Next.js の useRouter を使って、URLのクエリパラメータを取得します
  const router = useRouter();
  const { id } = router.query;

  // クエリパラメータが取得できるまで、Loading...を表示します
  if (!id) {
    return <div>Loading...</div>;
  }

  return (
    <div className="flex justify-center items-center">
      <div className="flex flex-col w-3/4 max-w-lg">
        <EditTodoForm id={parseInt(id as string)} />
        <Link
          href="/"
          className="ml-auto font-medium text-blue-600 hover:bg-blue-300 focus:outline-none"
        >
          Back
        </Link>
      </div>
    </div>
  );
};

export default EditTodoPage;

このコードでは、まず useRouter を使って URL のクエリパラメータ(この場合は id)を取得しています。
取得した id がまだ存在しない場合(=データがまだ読み込まれていないとき)、”Loading…”という文字を表示します。

id が取得できたら、それを元に EditTodoForm コンポーネントを呼び出します。
ここで渡す id は、現在開いているページの id(URL の一部)となります。

最後に、Back と書かれたリンクを配置しています。
このリンクをクリックすると、アプリケーションのトップページに戻ることができます。
このリンクは next/link を使用して作成され、サーバーサイドではなくクライアントサイドでページ遷移を行うため、画面遷移がスムーズになります。

以上が edit.tsx の詳細な説明です。
次に進む前に、このコードがどのように動作するのか、しっかりと理解しましょう。

Todo 編集ページへのリンク追加

最後に、Todo 詳細ページに Todo 編集ページへのリンクを追加します。

todo-frontend/pages/todos/[id]/index.tsx の JSX 部分(return の中身)を以下のように編集します。

// ...(略)

  return (
    <div className="flex justify-center items-center">
      <div className="flex flex-col space-y-6 w-3/4 max-w-lg pt-10">
        <label className="block text-xl font-bold text-gray-700">Todo</label>
        <Todo todo={todo} />
        <div className="flex justify-end">
          <Link
            href={`/todos/${id}/edit`}
            className="mt-auto font-medium text-blue-600 hover:bg-blue-300 focus:outline-none mr-12"
          >
            Edit
          </Link>
          <Link
            href="/"
            className="mt-auto font-medium text-blue-600 hover:bg-blue-300 focus:outline-none"
          >
            Back
          </Link>
        </div>
      </div>
    </div>
  );
};

// ...(略)

このコードでは、Edit というリンクを追加しています。
このリンクは、/todos/1/edit のような URL にアクセスするためのリンクです。

動作確認

では、実際に動作確認をしてみましょう。

まずは、Todo 詳細ページに Todo 編集ページへのリンクが表示されていることを確認します。
http://localhost:3000/todos/1 にアクセスして、Todo 詳細ページを表示します。

Todo詳細にEditリンクが表示されている
Edit というリンクをクリックすると、Todo 編集ページに遷移します。

編集ページ

Todo 編集ページに遷移したら、タイトルと本文を編集して、Update ボタンをクリックしてみましょう。

編集入力中

Todo 詳細ページに自動的に遷移し、Todo が更新されていることを確認します。

編集完了後

Todo 削除機能の実装

次は Todo 削除の削除機能を実装していきましょう。

Todo 詳細ページに新たに削除ボタンを追加し、削除ボタンを押すと Todo が削除されるようにします。

Todo 削除 API の実装 (Ruby on Rails)

Todo コントローラーの実装

まずは、Todo コントローラーに削除用のアクションを追加します。
Rails では delete アクションが削除用のアクションに対応しているので、app/controllers/todos_controller.rb を以下のように削除します。

app/controllers/todos_controller.rb

class TodosController < ApplicationController
  # ...略

  # DELETE /todos/:id
  def destroy
    @todo = Todo.find(params[:id])
    @todo.destroy
  end

  # ...略
end

上記のコードでは、Todo.find(params[:id]) で削除対象の Todo を取得し、@todo.destroy で削除しています。

Todo 削除 UI の実装 (Next.js)

では、次は Next.js で Todo 削除画面を作成していきましょう。

Todo 削除コンポーネントの作成

Todoアイテムを削除するためのコンポーネントを作成します。
このコンポーネントは「DeleteTodoButton」という名前にして、components ディレクトリ内に新しい DeleteTodoButton.tsx ファイルとして作成します。
今回作成する DeleteTodoButton は、特定のTodoアイテムを削除するためのボタンとその機能を持つコンポーネントです。

以下のコードを DeleteTodoButton.tsx に書き込みます。

import { useRouter } from 'next/router';
import axios from 'axios';

type DeleteTodoButtonProps = {
  id: number;
};

// Todoを削除するボタン
const DeleteTodoButton = ({ id }: DeleteTodoButtonProps) => {
  const router = useRouter();

  // Todoを削除する関数
  const handleDelete = async () => {
    // 確認のダイアログを表示
    if (!confirm('本当に削除しますか?')) {
      return;
    }

    try {
      // APIを呼び出して、Todoを削除する
      await axios.delete(`http://localhost:3000/todos/${id}`);

      // 削除に成功したらトップページに遷移
      router.push('/');
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <button
      onClick={handleDelete}
      className="mt-3 ml-auto flex justify-center py-2 px-8 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
    >
      Delete
    </button>
  );
};

export default DeleteTodoButton;

このコンポーネントは、与えられたTodoのidを利用して、特定のTodoアイテムを削除する機能を提供します。
useRouter というNext.jsのフックを使って、URLの情報にアクセスし、ユーザーが「Delete」ボタンをクリックした後でアプリケーションの状態を更新します。

handleDelete 関数は非同期関数です。
削除操作が完了するまで待つことが必要であるため、非同期関数として定義しています。
この関数では、確認ダイアログを表示し、ユーザーが「OK」をクリックした場合にのみ削除操作を進めます。

その後、axios.delete メソッドを使ってTodoの削除を行います。
この操作が成功すると、ユーザーはアプリケーションのトップページにリダイレクトされます。

最後に、このコンポーネントは「Delete」ボタンをレンダリングします。
このボタンは handleDelete 関数をクリックイベントのハンドラーとして使います。

以上で、Todoアイテムを削除するためのボタンコンポーネントが完成しました。
このコンポーネントを適切な場所に配置することで、ユーザーはTodoリストから不要なアイテムを簡単に削除することができるようになります。

Todo 削除ボタンの表示

次に、Todo 詳細ページに Todo 削除ボタンを表示するようにします。

pages/todos/[id]/index.tsx を以下のように編集します。

// ...(略)
import DeleteTodoButton from '@/components/DeleteTodoButton'; // 追加

// ...(略)

  return (
    <div className="flex justify-center items-center">
      <div className="flex flex-col space-y-6 w-3/4 max-w-lg pt-10">
        <label className="block text-xl font-bold text-gray-700">Todo</label>
        <Todo todo={todo} />
        <div className="flex justify-end">
          <Link
            href={`/todos/${id}/edit`}
            className="mt-auto font-medium text-blue-600 hover:bg-blue-300 focus:outline-none mr-12"
          >
            Edit
          </Link>
          <Link
            href="/"
            className="mt-auto font-medium text-blue-600 hover:bg-blue-300 focus:outline-none"
          >
            Back
          </Link>
        </div>
        {/* 削除ボタンコンポーネントを追加 */}
        <DeleteTodoButton id={todo.id} />
      </div>
    </div>
  );
};

export default TodoDetail;

DeleteTodoButton コンポーネントをインポートし、<DeleteTodoButton id={todo.id} /> という形でコンポーネントを呼び出しています。
削除処理自体は DeleteTodoButton コンポーネント内で行われますが、削除する Todo の id が必要なため、id={todo.id} という形で id を渡しています。

動作確認

では、実際に動作確認をしてみましょう。

まず、Todo 詳細ページに遷移するため、トップページ http://localhost:3001 から任意の Todo をクリックして Todo 詳細ページに遷移します。
この時、Todo の削除ボタンが表示されていることを確認します。

詳細ページに削除ボタンが表示されている

では、実際に Todo を削除してみましょう。

Todo の削除ボタンをクリックすると、確認のダイアログが表示されるので、OK をクリックします。

削除確認ダイアログ

すると、Todo が削除されトップページに遷移しますが、削除した Todo が表示されていないことを確認します。

削除されたことの確認

以上で、Todo 削除機能の実装は完了です。

これで Next.js と Rails を使った Todo アプリの実装は完了です。
ここまでお疲れ様でした!

フルスタックエンジニアを目指している方へおすすめの記事

本記事ではごく一部の知識についてのみ、焦点をあてました。

バックエンド、またはフルスタックエンジニアを目指すには以下の記事もおすすめです。

目次