Rails + React で簡単なTODOリストを作ってみる

# 初めに 
Ruby on rails を勉強するためにまずは簡単なアプリを作ろうと思う。
今回はTODOリストを作ってみる。

この記事を見た有識者の方へ
何か間違ってたらコメントください🙇‍♂️

# 構成
#### フロントエンド(React)
* Viteを使ってTypeScript対応のReactアプリを作成
* axios を使ってRails APIと通信
#### バックエンド（Rails API）
* postgreSQLを使用
* CRUDでAPI実装
* DBマイグレーションでDB作成したい
* Reactと通信できるようにCORS設定する
### 接続について
* RailsのCORS設定(フロントのローカルホストを許可)
* Reactでaxios.get('http://localhost:3000/tasks') してデータ取得

# 1. バックエンド（Rails API）構築
### ① Rails APIプロジェクトを作成
まず、RailsのAPIモードで新規プロジェクトを作成する。
コマンドプロンプト上で下記を実行
``` sh:sh
rails new todo-api --api -d postgresql
cd todo-api
```
### ② 必要なGemを追加
Gemfile を開いて、CORS設定用の rack-cors を追加（フロントと通信できるようにする）。
``` ruby:Gemfile
gem 'rack-cors'
```
下記を実行してインストール。
``` sh:sh
bundle install --gemfile
```
  
インストール後、config/initializers/cors.rb に設定を追加。
(※自分の環境ではコメントアウト状態になっていたのでそれを解除した)
``` ruby:config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:5173' # Reactのローカル環境
    resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options]
  end
end
```

### ③ データベースをセットアップ
まず、データベースを作成する。
``` sh:sh
rails db:create
```
### ④ Taskモデルを作成
以下のコマンドで Task モデルを作成する。
```sh:sh
rails g model Task title:string completed:boolean
rails db:migrate
```
※自分の環境ではmigrateコマンド実行時にエラーが発生した。
下記のファイルを編集することでエラーが解消。
``` yml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  username: your_db_user
  password: your_db_password
  host: localhost

development:
  <<: *default
  database: todo_api_development

test:
  <<: *default
  database: todo_api_test
```
### ⑤ Controller とルーティングを設定
コントローラーを作成。
``` sh:sh
rails g controller Tasks
```
config/routes.rb にエンドポイントを追加
``` ruby:config/routes.rb
Rails.application.routes.draw do
  resources :tasks
end
```
app/controllers/tasks_controller.rb を編集して、CRUDを実装
``` ruby:app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  before_action :set_task, only: [:show, :update, :destroy]

  # GET /tasks
  def index
    tasks = Task.all
    render json: tasks
  end

  # POST /tasks
  def create
    task = Task.new(task_params)
    if task.save
      render json: task, status: :created
    else
      render json: task.errors, status: :unprocessable_entity
    end
  end

  # GET /tasks/:id
　def show
    render json: @task
　end

  # PATCH/PUT /tasks/:id
  def update
    if @task.update(task_params)
      render json: @task
    else
      render json: @task.errors, status: :unprocessable_entity
    end
  end

  # DELETE /tasks/:id
  def destroy
    @task.destroy
    head :no_content
  end

  private

  def set_task
    @task = Task.find(params[:id])
  end

  def task_params
    params.require(:task).permit(:title, :completed)
  end
end
```
### ⑥ Railsサーバーを起動
``` sh:sh
rails s
```
これで http://localhost:3000/tasks にアクセスすると、空の配列 [] が返ってくる。

# 2. フロントエンド（React）構築
### ① Reactプロジェクトを作成
Viteを使ってTypeScript対応のReactアプリを作成する。
``` sh:sh
npm create vite@latest todo-frontend --template react-ts
cd todo-frontend
npm install
```
### ② 必要なライブラリをインストール
axios を使ってRails APIと通信する。
``` sh:sh
npm install axios
```
### ③ API通信用の関数を作成
src/api.ts を作成し、Rails APIと通信する関数を定義。
``` typescript:src/api.ts
import axios from "axios";

const API_URL = "http://localhost:3000/tasks";

export const getTasks = async () => {
  const res = await axios.get(API_URL);
  return res.data;
};

export const addTask = async (title: string) => {
  const res = await axios.post(API_URL, { title, completed: false });
  return res.data;
};

export const updateTask = async (id: number, completed: boolean) => {
  const res = await axios.patch(`${API_URL}/${id}`, { completed });
  return res.data;
};

export const deleteTask = async (id: number) => {
  await axios.delete(`${API_URL}/${id}`);
};
```

### ④ タスク一覧を表示
src/App.tsx にReactのコードを書く。
```src/App.tsx
import { useState, useEffect } from "react";
import { getTasks, addTask, updateTask, deleteTask } from "./api";

type Task = {
  id: number;
  title: string;
  completed: boolean;
};

function App() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState("");

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

  const fetchTasks = async () => {
    const data = await getTasks();
    setTasks(data);
  };

  const handleAddTask = async () => {
    if (!newTask) return;
    const task = await addTask(newTask);
    setTasks([...tasks, task]);
    setNewTask("");
  };

  const handleToggleTask = async (id: number, completed: boolean) => {
    await updateTask(id, !completed);
    setTasks(tasks.map(task => task.id === id ? { ...task, completed: !completed } : task));
  };

  const handleDeleteTask = async (id: number) => {
    await deleteTask(id);
    setTasks(tasks.filter(task => task.id !== id));
  };

  return (
    <div>
      <h1>TODO List</h1>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
      />
      <button onClick={handleAddTask}>Add Task</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <input
              type="checkbox"
              checked={task.completed}
              onChange={() => handleToggleTask(task.id, task.completed)}
            />
            {task.title}
            <button onClick={() => handleDeleteTask(task.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;
```

#  3. アプリを起動
### ① Rails API（バックエンド）を起動

```sh
rails s
```
### ② React（フロントエンド）を起動
```sh
npm run dev
```
### ③ 動作確認
* ブラウザで http://localhost:5173 にアクセス
* タスクを追加・削除・更新できるかチェック
下記のような画面が表示されればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1234357/49128ad5-b530-43c1-bf8b-6f2a66e4e95a.png)
# 課題
* UIがしょぼいので強化したい
* 認証機能を追加してもいいかも
* AWSも勉強したいのでAWSでデプロイなんかしてみたい