DIVX テックブログ

catch-img

React+RailsAPIでSPAの処理の流れを解説してみた


目次[非表示]

  1. 1.はじめに
  2. 2.SPAとは
  3. 3.SPAの処理の流れを見てみよう
    1. 3.1.Todoアプリ
      1. 3.1.1.構成
      2. 3.1.2.事前準備
        1. 3.1.2.1.Dockerでコンテナを立ち上げる
        2. 3.1.2.2.CORS(Cross-Origin Resource Sharing)の設定
        3. 3.1.2.3.データベースの作成
    2. 3.2.Todoの一覧表示と保存処理
      1. 3.2.1.初回リクエストを送る
      2. 3.2.2.HTML,CSS,JavaScriptなどの静的ファイルを返す
      3. 3.2.3.Todoの一覧表示
        1. 3.2.3.1.todo_frontendの実装
      4. 3.2.4.todo_backendの実装
      5. 3.2.5.Todoの保存処理
        1. 3.2.5.1.todo_frontendの実装
        2. 3.2.5.2.todo_backendの実装
        3. 3.2.5.3.データベースを確認する
  4. 4.終わりに
  5. 5.採用情報
  6. 6.参考資料

はじめに

こんにちは。株式会社divxのエンジニア二階堂です。

昨今のWebアプリケーション開発では、フロントエンドとバックエンドを分けて開発することが増えており、その中の代表的な手法の1つである「SPA」があります。

僕自身、業務でSPAを開発する過程でフロントエンド、バックエンド、データベースの間でどのような処理や通信が行われているのか曖昧な理解で進めていたので、この機会に深堀りしたいと思い本記事を執筆しました。また、SPA開発はHowto系のコードだけの情報が多くそれだけの理解では難しかったので、処理の流れの図と共にコードを読み解きたいと思いました。

本記事では、まず「SPAとは何か」について説明します。
そして、実際にフロントエンドをReact、バックエンドをRailsのAPIを使用した簡単なTodoアプリを例にTodoの一覧表示とTodoを保存する処理の流れを図と共に挙動検証したりしながら解説します。

SPAの処理の流れを「なんとなく知っていた方」や「これから学びたい方」向けの内容なので、本記事をきっかけに今後のお役に立てれば幸いです。

SPAとは

SPAはシングルページアプリケーション(以下、SPA) の略で、Webアプリケーションの実装手法の一種です。

以下の図のように、ブラウザから初回リクエスト時のみ単一のWebページを読み込み、それ以降はJavaScript APIなどを通じて必要なデータのみをJSON形式などでサーバーから取得してJavaScriptで差分のみを更新します。

SPAとは


従来のWebページの読み込みでは、ユーザーが何らかの操作(ボタンやリンクのクリックなど)が行われるたびにサーバーは適切なWebページ全体の更新を行いますが、SPAでは1回のアクセスでWebページ全体を取得した後は、差分のみを表示させることでページの高速化やユーザーに軽快に動作させることが期待できます。

一方で、SEO などで不利になったり、開発コストがかかったり、状態の保守、操作の改善などの労力も必要になったりします。

SPAの処理の流れを見てみよう


SPAの仕組みがわかった上で、実際にフロントエンドをReact、バックエンドをRailsのAPIモードで簡単なTodoアプリを使用して流れを見ていきます。

Todoの一覧表示と保存処理の流れを動作確認や検証を挟みつつ解説していきます。

Todoアプリ

Todoの一覧表示と作成ができる簡易的なアプリケーションです。

以下、完成イメージになります。

SPAの処理の流れを見てみよう


※本記事では、Todoアプリを例にSPA構成の処理の流れを確認していくことが主題なので、1つ1つのコードの説明や「こっちの書き方や構成の方がいい」などのご意見もあるかと思いますが、今回は言及しません。


構成

以下、Todoアプリケーションの構成です。

フロントエンド

  • React:18.0.17
  • ChakraUI:2.3.2
  • axios:0.27.2

バックエンド

  • Ruby:3.1
  • Ruby on Rails:7.0.1
  • データベース
  • MySQL:5.7

開発環境

  • Docker Engine:20.10.12
  • Docker Compose:1.29.2
  • MacBook Air (M1, 2020)
  • macOS Big Sur:11.6.4

事前準備

Dockerでコンテナを立ち上げる

フロントエンドのアプリケーションをtodo_frontend(React)
バックエンドのアプリケーションをtodo_backend(Rails API)※apiモードでRuby on Railsを構築  ※Dockerについての説明は割愛します。

Dockerでコンテナを立ち上げる

※Dockerについての説明は割愛します。

CORS(Cross-Origin Resource Sharing)の設定

デフォルトでは、別のオリジンから別のオリジンのサーバーへのアクセス(GET, POSTなど)は制限されています。 よって、todo_frontendからのアクセスの許可をtodo_backendで設定します。
rack-corsというgemを使って、http://localhost:8000(todo_frontendで使用するオリジン)からの通信を許可します。

todo_backend/config/initializers/cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:8000'

resource "*",
  headers: :any,
  methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
データベースの作成

todo_backendのdbコンテナ内にTodoを保存するtodosテーブルを作成しています。
titleカラムにTodo名が保存されます。

データベースの作成

事前準備は以上です。

Todoの一覧表示と保存処理

Todoの一覧表示と保存処理の図をもとにどのような処理や通信が行われているのか解説します。

まず、以下、赤枠線の「初回リクエストを送る」「HTML,CSS,JavaScriptなどの静的ファイルを返す」のリクエストとレスポンスは、初回のみの処理なので先に解説します。 

Todoの一覧表示と保存処理


初回リクエストを送る

初回はブラウザからWebサーバーに対してリクエストを送ります。
todo_frontendのコンテナが立ち上がっているのでhttp://localhost:8000からWebサーバーにリクエストを送ります。

HTML,CSS,JavaScriptなどの静的ファイルを返す

この時Webサーバーが返すHTML,CSS,JavaScriptは、todo_frontendでReactを使って作成したSPAの本体部分になります。

CSSフレームワークのChakraUIを使用してtodo_frontendでTodoが表示される部分や入力フォームなどの見た目を作成します。

ChakraUIをインストールし、ChakraProviderをAppコンポーネントを囲みます。


todo_frontend/frontend/src/index.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { ChakraProvider } from "@chakra-ui/react";

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<ChakraProvider>
<App />
</ChakraProvider>
</React.StrictMode>
);

Todoが表示されるTodoコンポーネントを作成します。

以下の赤枠の部分になります。(例として、「筋トレ」というTodoを追加した場合以下のように表示されます。) 

Todoコンポーネント

todo_frontend/frontend/src/component/Todo.jsx

import { Text, Flex } from "@chakra-ui/react";
import { CloseIcon } from "@chakra-ui/icons";

const Todo = (props) => {

return (
<Flex mb="16px" justifyContent="space-between" alignItems="center">
<Text>・{props.title}</Text>
<CloseIcon />
</Flex>
);
};

export default Todo;

propsを使うことでコンポーネントに対してデータを渡すことができます。

Todoコンポーネントは、引数にpropsを受け取ることでtitle(Todo名)というデータを親コンポーネントから受け取れるようにしています。

次に、Todoの入力フォームを作成します。

以下の青枠の部分になります。 

HTML,CSS,JavaScriptなどの静的ファイルを返す


AppコンポーネントにTodoコンポーネントをimportします。

todo_frontend/frontend/src/App.jsx

import Todo from "./component/Todo";
import {
Flex,
Center,
Box,
CheckboxGroup,
Text,
Input,
Button,
} from "@chakra-ui/react";

const App = () => {

return (
<Box mt="64px">
<Center>
<Box>
<Box mb="24px">
<Text fontSize="24px" fontWeight="bold">
Todoリスト
</Text>
</Box>
<Flex mb="24px">
<Input
placeholder="Todoを入力"
/>
<Box ml="16px">
<Button colorScheme='blue'>
追加
</Button>
</Box>
</Flex>
<CheckboxGroup>
<Todo title="筋トレ"/>
</CheckboxGroup>
</Box>
</Center>
</Box>
);
};

export default App;

Appコンポーネント内でTodoコンポーネントにpropsとしてtitleという値を渡しています。
そうすることでtitleがTodoコンポーネント内に渡されてTodo名として表示されます。
上記は例として、titleに筋トレという文字列を渡しています。

http://localhost:8000にリクエストするとtodo_frontend(React)で作成した見た目の部分がレスポンスされ、以下のように表示されます。 

HTML,CSS,JavaScriptなどの静的ファイルを返す


以上でSPAの見た目部分は完成です。

Todoの一覧表示

以降の処理はtodo_frontendとtodo_backendとのAPI通信になります。

todo_frontendからHTTPリクエストのGETメソッドでtodo_backendにリクエストを送って、データベースに保存されているTodoを参照して一覧を表示する機能です。

以下、赤枠線が処理の流れになります。 

Todoの一覧表示


では、コードと処理の流れを解説します。

todo_frontendの実装

todo_frontendからtodo_backendにリクエストを送り、レスポンスされるTodoのJSONデータを受け取って一覧を表示させます。

axiosでリクエストを送ってデータを取得する

axiosを使ってデータの取得や追加を行います。
axiosとは、PromiseベースのHTTPクライアントです。HTTPリクエストのGETメソッドやPOSTメソッドなどを使ってサーバーからデータの取得、サーバーへの通信を通してデータの追加などができます。

Todoのデータを取得したいので、HTTPリクエストのGETメソッドを使ってtodo_backendにリクエストを送ります。

todo_frontend/frontend/src/App.jsx

import { useState, useEffect } from "react";
import Todo from "./component/Todo";
import {
Flex,
Center,
Box,
CheckboxGroup,
Text,
Input,
Button,
} from "@chakra-ui/react";
import axios from "axios";

const App = () => {

const [todos, setTodos] = useState([]);

const getTodos = () => {
axios.get("http://localhost:3001/api/todos")
.then((response) => {
setTodos(response.data);
})
.catch((error) => {
window.alert(error.message)
})
};
};

useEffect(() => {
getTodos();
}, []);

return (
<Box mt="64px">
<Center>
<Box>
<Box mb="24px">
<Text fontSize="24px" fontWeight="bold">
Todoリスト
</Text>
</Box>
<Flex mb="24px">
<Input
placeholder="Todoを入力"
/>
<Box ml="16px">
<Button colorScheme='blue'>
追加
</Button>
</Box>
</Flex>
<CheckboxGroup>
{todos.map((todo, index) => {
return (
<Todo
id={todo.id}
key={index}
title={todo.title}
/>
);
})}
</CheckboxGroup>
</Box>
</Center>
</Box>
);
};

export default App;

それぞれ解説します。

axiosをインストールし、App.jsxにインポートします。

import axios from "axios";

useStateでTodoの値とstateの更新用で関数を宣言します。

const [todos, setTodos] = useState([]);

getTodosという関数を定義し、ここでaxiosを使いGETメソッドでHTTPリクエストを送りTodoの一覧を取得します。

GETメソッドの通信は、axios.getを使用します。

第一引数にパラメータ付きのURLを指定し、.then()で通信が成功した際の処理を書きます。 .catch()ではエラー時の処理を書きます。

const getTodos = () => {
axios.get("http://localhost:3001/api/todos")
.then((response) => {
setTodos(response.data);
})
.catch((error) => {
window.alert(error.message)
})
};
};


axios.get("http://localhost:3001/api/todos")でtodo_backendとAPI通信を行っています。

.then()には通信成功時の処理を書きます。
レスポンスされるJSONデータは response.data の中に入っているので、setTodosの引数に指定しstate を更新します。

.catch()の中はエラー時にアラートを表示させる処理です。

mapメソッドで繰り返し処理をする

以下では、useStateで宣言したtodosをmapメソッドで繰り返し処理をして、差分を更新します。

<CheckboxGroup>
{todos.map((todo) => {
return (
<Todo
key={todo.id}
title={todo.title}
/>
);
})}
</CheckboxGroup>

Todoコンポーネントにpropsとして下記の値を渡しています。
key:Reactで配列処理する場合、配列内の項目に安定した識別性を与えるためkeyという属性に値を渡す必要があります。todoのidをkeyとして指定しています。
title:Todo名を渡しています。

そして、useEffectの中でgetTodos関数を実行し、第二引数に[]を指定することで、コンポーネントの初回のレンダリング時にTodoの一覧を取得します。

useEffect(() => {
getTodos();
}, []);


todo_backendの実装

ルーティングを設定する

todo_frontendからGETメソッドでhttp://localhost:3001/api/todosにリクエストがきたら、todosコントローラーのindexアクションで処理を行うようにルーティングを設定します。

todo_backend/config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    resources :todos, only: [:index]
  end
end

indexアクションを追加する

todosコントローラーにindexアクションを定義します。

todo_backend/app/controllers/api/todos_controller.rb

class Api::TodosController < ApplicationController
def index
todos = Todo.all
render json: todos
end
end

indexアクション内の処理は、allメソッドでtodosテーブル内のレコード、つまりTodoの一覧を全て取得し、それをtodosという変数に格納しています。そして、render json: todosでJSONデータとしてtodo_frontendにレスポンスする処理を行っています。

検証として、テストデータをデータベースに作成してTodoの一覧が取得できJSONでレスポンスされるか確認してみます。

Todoのテストデータを3つ作成します。


todo_backend/db/seeds.rb

3.times do |i|
Task.create(title: "Todo#{i + 1}")
end


apiコンテナに入ってrails db:seedコマンドでテストデータを作成します。

todo_backend % docker-compose exec api bash
root@76c1655c2226:/app# rails db:seed


curlコマンドでGETメソッドを指定し、http://localhost:3001/api/todosからJSONが返るか確認します。 

root@d78259d5f4ef:/app# curl -X GET http://localhost:3001/api/todos


コマンドを実行すると、以下のようにJSON形式で出力されたことがわかります。

[
{
"id": 1,
"title": "Todo1",
"created_at": "2022-10-15T01:47:25.418Z",
"updated_at": "2022-10-15T01:47:25.418Z"
},
{
"id": 2,
"title": "Todo2",
"created_at": "2022-10-15T01:47:25.426Z",
"updated_at": "2022-10-15T01:47:25.426Z"
},
{
"id": 3,
"title": "Todo3",
"created_at": "2022-10-15T01:47:25.431Z",
"updated_at": "2022-10-15T01:47:25.431Z"
}
]

実際にJSONデータが出力できたので、todo_frontendでこのTodoのテストデータを受け取り画面に表示されれば正常です。

レスポンス成功時にの処理内にconsole.log(response.data);を仕込んでTodoのテストデータが取得できているのか確認してみます。

todo_frontend/frontend/src/App.jsx

const getTodos = () => {
axios.get("http://localhost:3001/api/todos")
.then((response) => {
console.log(response.data);
setTodos(response.data)
})
.catch((error) => {
window.alert(error.message)
})
};


検証ツールを開きコンソールを確認すると、todo_backendからJSONでレスポンスされたTodoのテストデータを取得できています。


todo_backendの実装

http://localhost:8000にアクセスするとTodoのテストデータが表示されました。 

todo_backendの実装


以上でTodoの一覧表示は完成です。


Todoの保存処理


todo_frontendからHTTPリクエストのPOSTメソッドでtodo_backendにリクエストを送って、入力したTodoをデータベースに保存します。

以下、赤枠線が処理の流れになります。 

Todoの保存処理


では、コードと処理の流れを解説します。

todo_frontendの実装

axiosでリクエストを送ってデータを追加する

フォームに入力したTodoをtodo_backendに送る処理です。

todo_frontend/frontend/src/App.tsx

import { useState, useEffect } from "react";
import Todo from "./component/Todo";
import {
Flex,
Center,
Box,
CheckboxGroup,
Text,
Input,
Button,
} from "@chakra-ui/react";
import axios from "axios";

const App = () => {

const [todos, setTodos] = useState([]);
const [title, setTitle] = useState("");

const getTodos = () => {
axios.get("http://localhost:3001/api/todos")
.then((response) => {
setTodos(response.data);
console.log(response.data);
})
.catch((error) => {
window.alert(error.message)
})
};

const createTodo = () => {
axios.post("http://localhost:3001/api/todos", {
title: title
})
.then(() => {
setTitle("");
getTodos();
})
.catch((error) => {
window.alert(error.message)
})
};

useEffect(() => {
getTodos();
}, []);

return (
<Box mt="64px">
<Center>
<Box>
<Box mb="24px">
<Text fontSize="24px" fontWeight="bold">
Todoリスト
</Text>
</Box>
<Flex mb="24px">
<Input
placeholder="Todoを入力"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Box ml="16px">
<Button colorScheme='blue' onClick={createTodo}>
追加
</Button>
</Box>
</Flex>
<CheckboxGroup>
{todos.map((todo) => {
return (
<Todo
key={todo.id}
title={todo.title}
/>
);
})}
</CheckboxGroup>
</Box>
</Center>
</Box>
);
};

export default App;


追加したコードをそれぞれ解説します。

フォームに入力された値を管理するstateです。

const [title, setTitle] = useState("");

Inputタグのvalueにtitleの値を参照させます。
onChangeイベントでtitleの値が変更した時に、Inputの値を引数としてsetTitleを実行しています。それによって、フォームに入力された値が変わるたびにstateのtitleがsetTitleで更新されます。

<Flex mb="24px">
<Input
placeholder="Todoを入力"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Box ml="16px">
<Button colorScheme='blue' onClick={createTodo}>
追加
</Button>
</Box>
</Flex>

「追加」ボタンがクリックされたら、onClickイベントでcreateTodo関数を実行させます。

Todoを作成するcreateTodoという関数を定義します。

const createTodo = () => {
axios.post("http://localhost:3001/api/todos", {
title: title
})
.then(() => {
setTitle("");
getTodos();
})
.catch((error) => {
window.alert(error.message)
})
};

axiosを使ってHTTPリクエストのPOSTメソッドでhttp://localhost:3001/api/todostitleを付与してリクエストを送っています。

このtitleは、前述したconst [title, setTitle] = useState("");のstateです。
フォームの値が変更されるたびに、stateのtitleがsetTitleで更新されるので、titleとして送信されることになります。このtitleがtodo_backendのtodosコントローラーのparamsに入るようなイメージです。

リクエストが成功したら.then()の中で、setTitle("");を実行することでフォームの値を空にし、getTodos();を実行することで、作成したTodoを含めた一覧を再取得しています。
.catch()の中はエラー時にアラートを表示させる処理です。

todo_backendの実装

ルーティングを設定する
todo_frontendからPOSTメソッドでhttp://localhost:3001/api/todosにリクエストがきたら、todosコントローラーのcreateアクションで処理を行うようにルーティングを設定します。

todo_backend/config/routes.rb

Rails.application.routes.draw do
namespace :api do
resources :todos, only: [:index, :create]
end
end


createアクションを追加する
todosコントローラーにcreateアクションを定義します。

todo_backend/app/controllers/api/todos_controller.rb

class Api::TodosController < ApplicationController
def index
todos = Todo.all
render json: todos
end

def create
todo = Todo.new(todo_params)

if todo.save
  render json: { status: 200, todo: todo }
else
  render json: { status: 500, message: "Todoの作成に失敗しました" }
end
end

private

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

それぞれ解説します。

createアクションの中でtodo_frontendから送られてくるパラメーターをtodo_paramsとして引数に指定しています。

保存処理時に条件分岐を書いてます。
成功したら、ステータスコード200番とtodoのJSONデータを返します。
失敗したら、ステータスコード500番とエラーメッセージのJSONデータを返します。

def create
todo = Todo.new(todo_params)

if todo.save
render json: { status: 200, todo: todo }
else
render json: { status: 500, message: "Todoの作成に失敗しました" }
end
end

private

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

実際にtodo_frontendから値が送られているのか、createアクション内にbinding.pryを仕込んで確認してみます。

   def create
    todo = Todo.new(todo_params)
        binding.pry

    if todo.save
      render json: { status: 200, todo: todo }
    else
      render json: { status: 500, message: "タスクの作成に失敗しました" }
    end
  end

docker attachでtodo_backend_apiコンテナにアタッチします。

todo_backend % docker attach todo_backend_api_1


検証として、「筋トレ」と入力して追加ボタンをクリックすると止まるので、確認してみます。 

todo_frontendの実装

todo_backend % docker attach todo_backend_api_1
Started POST "/api/todos" for 172.27.0.1 at 2022-10-19 02:27:19 +0000
Processing by Api::TodosController#create as HTML
Parameters: {"title"=>"筋トレ", "todo"=>{"title"=>"筋トレ"}}

From: /app/app/controllers/api/todos_controller.rb:15 Api::TodosController#create:

11: def create
12:   todo = Todo.new(todo_params)
13:   binding.pry
14: 
=> 15: if todo.save
16: render json: { status: 200, todo: todo }
17: else
18: render json: { status: 500, message: "タスクの作成に失敗しました" }
19: end
20: end
[1] pry(#<Api::TodosController>)> params
=> #<ActionController::Parameters {"title"=>"筋トレ", "controller"=>"api/todos", "action"=>"create", "todo"=>#<ActionController::Parameters {"title"=>"筋トレ"} permitted: false>} permitted: false>
[2] pry(#<Api::TodosController>)> todo_params
=> #<ActionController::Parameters {"title"=>"筋トレ"} permitted: true>


todo_frontendから送られてきたparamsのtitleの値が「筋トレ」となっています。
これでtodo_frontendからtodo_backendに正常な値が送られていることが確認できました。

exitで終了すると、todoテーブルに保存されます。
その後、todo_frontendのcreateTodoではリクエストに成功したらgetTodosが実行されます。
よって、保存が完了したらindexアクション内のtodoテーブルに保存されているTodoの一覧を取得する処理が行われているのがわかります。

[3] pry(#<Api::TodosController>)> exit
TRANSACTION (5.1ms) BEGIN
↳ app/controllers/api/todos_controller.rb:15:in create' Todo Create (8.5ms) INSERT INTOtodos(title,created_at,updated_at) VALUES ('筋トレ', '2022-10-20 02:41:36.213869', '2022-10-20 02:41:36.213869') ↳ app/controllers/api/todos_controller.rb:15:increate'
TRANSACTION (3.6ms) COMMIT
↳ app/controllers/api/todos_controller.rb:15:in `create'
Completed 200 OK in 856140ms (Views: 2.2ms | ActiveRecord: 35.3ms | Allocations: 1319975)

Started GET "/api/todos" for 172.27.0.1 at 2022-10-20 02:41:36 +0000
Processing by Api::TodosController#index as HTML
Todo Load (1.2ms) SELECT todos.* FROM todos
↳ app/controllers/api/todos_controller.rb:7:in `index'
Completed 200 OK in 9ms (Views: 6.2ms | ActiveRecord: 1.2ms | Allocations: 912)

データベースを確認する

データベースに正常に値が保存されているかdbコンテナに入って、todoテーブルを確認してみます。

todo_backend % docker-compose exec db bash
bash-4.2# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.38 MySQL Community Server (GPL)

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| app_development |
| app_test |
| mysql |
| performance_schema |
| sys |
+--------------------+
6 rows in set (0.05 sec)

mysql> use app_development;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+---------------------------+
| Tables_in_app_development |
+---------------------------+
| ar_internal_metadata |
| schema_migrations |
| todos |
+---------------------------+
3 rows in set (0.01 sec)

mysql> describe todos;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
| created_at | datetime(6) | NO | | NULL | |
| updated_at | datetime(6) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.05 sec)

mysql> select * from todos;
+----+-----------+----------------------------+----------------------------+
| id | title | created_at | updated_at |
+----+-----------+----------------------------+----------------------------+
| 1 | 筋トレ | 2022-10-19 02:41:36.213869 | 2022-10-19 02:41:36.213869 |
+----+-----------+----------------------------+----------------------------+
1 row in set (0.02 sec)


todoテーブルにTodo(筋トレ)が保存されているのがわかります。

また、検証ツールのNetWorkタブで保存処理成功時のrender json: { status: 200, todo: todo }がレスポンスされているか確認できました。

データベースを確認する


バリデーションを設定する

空入力のときには保存されないようにバリデーションを設定してみます。

todo_backend/app/models/todo.rb

class Todo < ApplicationRecord
validates :title, presence: true
end

検証として、フォームを空のまま追加ボタンを押すとバリデーションで引っ掛かりリクエストに失敗します。

検証ツールのNetWorkタブで保存処理失敗時のrender json: { status: 500, message: "タスクの作成に失敗しました" }のレスポンスが確認できました。

バリデーションを設定する


以上でTodoの保存機能は完成です。

最後に1通り挙動確認をしてみます。

検証として、「プログラミング」と「掃除」のTodoを追加してみます。 

バリデーションを設定する


再度、getTodosの中にconsole.log(response.data);を仕込んで確認すると、正常にJSONデータがレスポンスされています。 

バリデーションを設定する


dbコンテナ内にも正常に保存されています。 

バリデーションを設定する


以上で、Todoの一覧表示と保存処理は完成しました。

終わりに

今回はSPAの処理の図を書き、実際にその通りの流れになるのかをTodoアプリを使用して検証してみました。

図を書くことで頭の中でコードだけを追っていくよりもはるかに自分自身の理解の手助けになりました。以前、Ruby on RailsのMVCモデルのイメージがつかなかった時に図で説明を受けたときに、パズルのピースがカチッとはまったような感覚があったからです。今後も何かしら詰まった時や理解し難い時などには、簡単にでも図を書いてみようと思います。

そして、基礎的ではありますがconsole.logやbinding.pryを使用することで、何の値がどのタイミングでリクエストやレスポンスが行われているのか目視できます。それによって詰まった時にどこが原因なのか横着せずに調べる癖を付けられるようになりました。基礎的なことを忘れずに今後も引き続き活用していきたいと思います。

最後まで読んでいただきありがとうございました。

採用情報

株式会社divxでは一緒に働ける仲間を募集しています。
興味があるかたはぜひ採用ページを御覧ください。

  採用情報 | 株式会社divx(ディブエックス) 可能性を広げる転職を。DIVXの採用情報ページです。 株式会社divx(ディブエックス)

参考資料

お気軽にご相談ください


ご不明な点はお気軽に
お問い合わせください

サービス資料や
お役立ち資料はこちら

DIVXメディアカテゴリー

テックブログ タグ一覧

採用ブログ タグ一覧

人気記事ランキング

GoTopイメージ