React Admin × Rails APIで作る管理画面

# はじめに

この記事では、React Admin と Rails API を使って、シンプルな管理画面を構築する方法を紹介します。最終的には投稿（Post）とユーザー（User）を管理できる画面を作ります。

前半はほぼ公式チュートリアルに従って行い、後半は Rails API を使って自分の作ったデータへ干渉できるようにします。

https://marmelab.com/react-admin/Tutorial.html

最終的な完成イメージは次の通りです。

![admin.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/7c54dcaa-9cab-4dad-94bb-f54ffe528295.gif)


> ※ 本記事では「React Admin を使った管理画面の構築」にフォーカスして進めていきます。
Ruby on Rails 環境（Ruby や Bundler、Rails 本体など）のセットアップについては省略していますので、必要に応じてあらかじめご準備いただければと思います。

# React Admin 環境構築
まずは、React Admin の開発環境をセットアップしていきます。以下のコマンドをターミナルで実行してください。

```bash
$ npm init react-admin test-admin
$ cd test-admin
$ npm install ra-data-json-server
$ npm run dev
```

http://localhost:5173 にアクセスして次のような画面になったら正しく動いています。

![スクリーンショット 2025-04-12 14.07.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/cb9ae665-cf05-4804-abea-3539f1d3944b.png)


次にデータ取得や更新のために dataProvider を設定する必要があります。`dataProvider.ts`を作成して`src/`に追加します。

```src/dataProvider.ts
import jsonServerProvider from 'ra-data-json-server';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');

export default dataProvider;
```

この `jsonServerProvider` は、React Admin が用意している公式の REST API クライアントで、指定した URL に対して GET、POST、PUT などのリクエストを自動的に発行してくれます。

ここでは、モックデータ API として有名な JSONPlaceholder を使用しています。

https://jsonplaceholder.typicode.com

このサービスでは、投稿データ（`/posts`）やユーザー情報（`/users`）など、管理画面に必要なサンプルデータが RESTful API 形式で提供されています。

試しに、https://jsonplaceholder.typicode.com/posts にアクセスすると次のようなデータが得られると思います。

```
[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  
  ...

  {
    "userId": 10,
    "id": 99,
    "title": "temporibus sit alias delectus eligendi possimus magni",
    "body": "quo deleniti praesentium dicta non quod\naut est molestias\nmolestias et officia quis nihil\nitaque dolorem quia"
  },
  {
    "userId": 10,
    "id": 100,
    "title": "at nam consequatur ea labore ea harum",
    "body": "cupiditate quo est a modi nesciunt soluta\nipsa voluptas error itaque dicta in\nautem qui minus magnam et distinctio eum\naccusamus ratione error aut"
  }
]
```

このデータを次のステップで綺麗に表示していきます。

# 投稿管理画面の作成

### 一覧画面の作成

まずは投稿（Post）の一覧を表示するために、次のようにフォルダ/ファイルを追加します。

`src/` に `pages` フォルダ、そしてその下に `posts` フォルダを作成し、そこに `PostList.tsx` を追加します。
React Admin の `List` コンポーネントと `Datagrid` を使って、投稿の ID、タイトル、本文、ユーザーを一覧表示できるようにします。

```src/pages/posts/PostList.tsx
import { List, Datagrid, TextField, ReferenceField, EditButton } from "react-admin";

const PostList = () => (
    <List>
        <Datagrid>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="body" />
            <ReferenceField source="userId" reference="users" />
            <EditButton />
        </Datagrid>
    </List>
);

export default PostList;
```

次に、`App.tsx` を編集してこの画面をルーティングに追加します。

```src/App.tsx
import { Admin, Resource } from "react-admin";
import dataProvider from './dataProvider';

import PostList from './pages/posts/PostList';

export const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="posts"
            list={PostList}
        />
    </Admin>
);
```

ここまで実装すると、投稿の一覧が表形式で表示されるようになります。

![スクリーンショット 2025-04-12 14.11.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/7b288b0d-530a-4d76-9d2b-03f01c6c5125.png)

### 詳細・編集・新規作成ページの追加

同様に次のファイルも作成して、詳細ページ、編集ページ、新規作成ページも作ります。

```src/pages/posts/PostShow.tsx
import { Show, SimpleShowLayout, TextField, ReferenceField } from "react-admin";

const PostShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="body" />
            <ReferenceField source="userId" reference="users" />
        </SimpleShowLayout>
    </Show>
);

export default PostShow;
```

```src/pages/posts/PostEdit.tsx
import { Edit, SimpleForm, TextInput, ReferenceInput } from "react-admin";

const PostEdit = () => (
    <Edit>
        <SimpleForm>
            <TextInput source="title" />
            <TextInput source="body" />
            <ReferenceInput source="userId" reference="users" />
        </SimpleForm>
    </Edit>
);

export default PostEdit;
```

```src/pages/posts/PostCreate.tsx
import { Create, SimpleForm, TextInput, ReferenceInput } from "react-admin";

const PostCreate = () => (
    <Create>
        <SimpleForm>
            <TextInput source="title" />
            <TextInput source="body" multiline rows={5} />
            <ReferenceInput source="userId" reference="users" />
        </SimpleForm>
    </Create>
);

export default PostCreate;
```

`App.tsx` に詳細・編集・作成ページを追加してルーティングを有効にします。

```diff_typescript:App.tsx
import { Admin, Resource } from "react-admin";
import dataProvider from './dataProvider';

import PostList from './pages/posts/PostList';
+ import PostShow from './pages/posts/PostShow';
+ import PostEdit from './pages/posts/PostEdit';
+ import PostCreate from './pages/posts/PostCreate';

export const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="posts"
            list={PostList}
+           show={PostShow}
+           edit={PostEdit}
+           create={PostCreate}
        />
    </Admin>
);
```

![スクリーンショット 2025-04-12 14.14.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/4aa07cf6-9146-4f8a-872f-0541609e4fa9.png)


![スクリーンショット 2025-04-12 14.14.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/8f444ab7-c556-4dd2-897e-7a78f40e10d5.png)

![スクリーンショット 2025-04-12 14.15.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/7db72790-9213-4997-9193-1d6c91b4d037.png)



ここまでの実装が完了すると、一覧から投稿の詳細を確認したり、編集画面・作成画面へ遷移できるようになります。

なお、今は外部のモック API（[JSONPlaceholder](https://jsonplaceholder.typicode.com)）を使用しているため、編集や新規作成を行っても実際にはサーバー側に保存されません。そのため、扱えるのは一覧と詳細のみとなります。

# Rails API の設定

このステップでは、React Admin のフロントエンドと連携するための Rails API サーバーを構築していきます。まずはデータベース設計として `Post` モデルと `User` モデルを用意し、それらのリレーション設定、初期データの投入までを行います。

### モデル作成

まずは、投稿情報を管理する `Post` モデルと、それに紐づくユーザー情報を管理する `User` モデルを作成します。

```
$ rails g model Post
$ rails g model User
```

それぞれのマイグレーションファイルを編集して、必要なカラムを追加します。

```diff_ruby:db/migrate/2025xxxxxxxxxx_create_posts.rb
class CreatePosts < ActiveRecord::Migration[7.1]
    def change
        create_table :posts do |t|
+           t.string :title
+           t.text :body
+           t.integer :user_id
            t.timestamps
        end
    end
end
```

```diff_ruby:db/migrate/2025xxxxxxxxxx_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.1]
    def change
        create_table :users do |t|
+           t.string :name
+           t.string :email
            t.timestamps
        end
    end
end
```

カラムの追加が完了したら、マイグレーションを実行してテーブルを作成します。

```
$ rails db:migrate
```

加えて、`User` は複数の `Post` を持つ（`1:N`）、という関係性をモデルに定義します。
これで、ユーザーと投稿がリレーションで繋がり、Rails 側での関連付けができるようになります。

```app/models/post.rb
class Post < ApplicationRecord
    belongs_to :user
end
```

```app/models/user.rb
class User < ApplicationRecord
    has_many :posts, dependent: :destroy
end
```


また、今回は一覧画面を作るので初期データをあらかじめ作っておきます。

```db/seeds.rb
User.create!(name: "Alice", email: "alice@example.com")
User.create!(name: "Bob", email: "bob@example.com")
User.create!(name: "Charlie", email: "charlie@example.com")

User.all.each do |user|
    4.times do |n|
        user.posts.create!(
            title: "テストタイトル#{n + 1}",
            body: "テスト本文#{n + 1}"
        )
    end
end
```

```
$ rails db:seed
```


これで初期データの投入が完了しました。

この段階で、React Admin から扱うための基本的なデータ構造が整いました。次はこれらのデータを外部へ公開するために、API エンドポイントの実装に進んでいきます。


### コントローラー作成

ここからは、React Admin 側と通信するための Rails 側の API コントローラーを作成していきます。
前ステップで作成したモデルに対して、一覧取得・詳細取得・作成・更新・削除といった典型的な REST 操作を提供するエンドポイントを整備します。

```
$ rails g controller api/v1/posts
$ rails g controller api/v1/users
```

このコマンドで、それぞれのエンドポイントに対応するコントローラーファイルとルーティングが生成されます。

続いて、`Api::V1::PostsController` に RESTful なアクションを実装していきます。

```app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
    def index
        start_params = params[:_start].present? ? params[:_start].to_i : 0
        end_params = params[:_end].present? ? params[:_end].to_i : 10
        limit = end_params - start_params
        posts = Post.offset(start_params).limit(limit)
        response.set_header('X-Total-Count', Post.all.count)
        render json: posts
    end

    def show
        post = Post.find(params[:id])
        render json: post
    end

    def create
        post_new = Post.new(post_params)
        if post_new.save
            render json: post_new
        else
            render json: { status: 'error', errors: post_new.errors.full_messages }
        end
    end
    
    def update
        post = Post.find(params[:id])
        if post.update(post_params)
            render json: post
        else
            render json: { status: 'error', errors: post.errors.full_messages }
        end
    end

    def destroy
        post = Post.find(params[:id])
        post.destroy
        render json: post
    end
    
    private
    def post_params
        params.require(:post).permit(:title, :body, :user_id)
    end
end
```

同様に、ユーザー一覧などを取得できる `Api::V1::UsersController` を実装します。

```app/controllers/api/v1/users_controller.rb
class Api::V1::UsersController < ApplicationController
    def index
        start_params = params[:_start].present? ? params[:_start].to_i : 0
        end_params = params[:_end].present? ? params[:_end].to_i : 10
        limit = end_params - start_params
        users = User.offset(start_params).limit(limit)
        response.set_header('X-Total-Count', User.all.count)
        render json: users
    end

    def show
        user = User.find(params[:id])
        render json: user
    end

    def create
        user_new = User.new(user_params)
        if user_new.save
            render json: user_new
        else
            render json: { status: 'error', errors: user_new.errors.full_messages }
        end
    end

    def update
        user = User.find(params[:id])
        if user.update(user_params)
            render json: user
        else
            render json: { status: 'error', errors: user.errors.full_messages }
        end
    end

    def destroy
        user = User.find(params[:id])
        user.destroy
        render json: user
    end

    private
    def user_params
        params.require(:user).permit(:name, :email)
    end
end
```

最後に、ルーティングファイルを編集して API を有効にします。

```config/routes.rb
Rails.application.routes.draw do
    namespace :api do
        namespace :v1 do
            resources :posts, except: %i[new edit]
            resources :users, except: %i[new edit]
        end
    end
end
```

これで、React Admin 側から http://localhost:3000/api/v1/posts や http://localhost:3000/api/v1/users に対してリクエストを送ることで、Rails 側のデータと連携できるようになりました。

### その他設定

フロントエンドと Rails API を連携させる際、サーバー側の設定もいくつか必要になります。そのままでは CSRF や CORS の制約に引っかかってしまうため、以下のように設定を加えていきます。

まずは Rails を API 専用モードに設定します。これにより、不要なビューやアセット、CSRF 保護などが無効化され、API サーバーとしてシンプルになります。

```config/application.rb
module App
    class Application < Rails::Application
        ...
        config.api_only = true
    end
end
```

次に、CORS（オリジン間リソース共有）を許可する設定を行います。Rails では `rack-cors` というミドルウェアを使うのが一般的です。

```config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
    allow do
        origins "http://localhost:5173" # React Admin の開発サーバーURL

        resource "*",
            headers: :any,
            expose: ['X-Total-Count'],
            methods: [:get, :post, :put, :patch, :delete, :options, :head]
    end
end
```

もし `config/initializers/cors.rb` が存在しない場合は、`Gemfile` に以下を追記してから `bundle install` を実行してください。

```diff:Gemfile
+ gem "rack-cors"
```

```
$ bundle install
```

これでフロントエンドと接続する準備が完了しました。

次のステップでは、React Admin 側の `dataProvider` をローカルの Rails API に切り替え、実際に CRUD 処理を動かしてみます。

# 投稿管理画面の修正

ここからは、フロントエンド（React Admin）を Rails のローカル API サーバーに接続するように切り替えていきます。

前ステップまでは https://jsonplaceholder.typicode.com を使っていましたが、ここからは自分で立てた Rails API を使って、投稿やユーザーの追加・削除などができるようになります。

```src/dataProvider.ts
import jsonServerProvider from 'ra-data-json-server';

const dataProvider = jsonServerProvider('http://localhost:3000/api/v1');
 
export default dataProvider;
```

また、Rails のデフォルトでは外部キー名はスネークケース（`user_id`）となっているため、React Admin 側のフィールド指定もそれに合わせる必要があります。

```diff_typescript:src/pages/posts/PostList.tsx
import { List, Datagrid, TextField, ReferenceField, EditButton } from "react-admin";

const PostList = () => (
    <List>
        <Datagrid>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="body" />
+           <ReferenceField source="user_id" reference="users" />
            <EditButton />
        </Datagrid>
    </List>
);
export default PostList;
```

この修正は一覧表示に限らず、詳細表示（`PostShow.tsx`）、編集（`PostEdit.tsx`）、新規作成（`PostCreate.tsx`）など、該当フィールドを使用している全ての画面に適用してください。

![スクリーンショット 2025-04-12 16.37.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/ac5e32ff-4423-4298-8e07-7353be0bbccb.png)


`rails db:seed` で投入したテストデータが、上のように一覧画面で表示されていれば成功です。

# ユーザー管理画面の作成

加えて `User` の管理画面も作ってみましょう。

```src/pages/UserList.tsx
import { List, Datagrid, TextField, EditButton } from "react-admin";

const UserList = () => (
    <List>
        <Datagrid>
            <TextField source="id" />
            <TextField source="name" />
            <TextField source="email" />
            <EditButton />
        </Datagrid>
    </List>
);
export default UserList;
```

```src/pages/UserShow.tsx
import { Show, SimpleShowLayout, TextField } from "react-admin";

const UserShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="id" />
            <TextField source="name" />
            <TextField source="email" />
        </SimpleShowLayout>
    </Show>
);

export default UserShow;
```

```src/pages/UserEdit.tsx
import { Edit, SimpleForm, TextInput } from "react-admin";

const UserEdit = () => (
    <Edit>
        <SimpleForm>
            <TextInput source="name" />
            <TextInput source="email" />
        </SimpleForm>
    </Edit>
);

export default UserEdit;
```

```src/pages/UserCreate.tsx
import { Create, SimpleForm, TextInput } from "react-admin";

const UserCreate = () => (
    <Create>
        <SimpleForm>
            <TextInput source="name" />
            <TextInput source="email" />
        </SimpleForm>
    </Create>
);

export default UserCreate;
```

最後に、`App.tsx` にユーザーリソースを追加して表示されるようにします。

```diff_typescript:App.tsx
import { Admin, Resource } from "react-admin";
import dataProvider from './dataProvider';

import PostList from './pages/posts/PostList';
import PostShow from './pages/posts/PostShow';
import PostEdit from './pages/posts/PostEdit';
import PostCreate from './pages/posts/PostCreate';

+ import UserList from './pages/users/UserList';
+ import UserShow from './pages/users/UserShow';
+ import UserEdit from './pages/users/UserEdit';
+ import UserCreate from './pages/users/UserCreate';

export const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="posts"
            list={PostList}
            show={PostShow}
            edit={PostEdit}
            create={PostCreate}
        />
+       <Resource name="users"
+           list={UserList}
+           show={UserShow}
+           edit={UserEdit}
+           create={UserCreate}
+       />
    </Admin>
);
```

表示を確認して、ユーザー一覧や詳細、編集・作成ページが問題なく表示されていれば完了です。

![スクリーンショット 2025-04-12 16.42.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/52e1990a-e50c-4cec-9fc1-555e1ee7b239.png)

![スクリーンショット 2025-04-12 16.42.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/2976853f-71b7-4210-b291-13afbb90b0a4.png)

![スクリーンショット 2025-04-12 16.42.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/981bf9d0-944c-4ad7-8c7e-fc2c2c5d8862.png)

![スクリーンショット 2025-04-12 16.42.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3705454/9ec61b86-28c7-4087-b549-fcf915a2bd26.png)


これで、投稿とユーザーの CRUD 管理ができるシンプルな管理画面がひと通り完成しました。

# おわりに

今回は React Admin と Rails API による実用的な管理画面作成を行いました。

- 外部サービス（`jsonplaceholder`）からスタート

- ローカル API に切り替え

- 投稿とユーザーの一覧 / 詳細 / 編集 / 作成まで対応

という一連の流れを通して、React Admin の基本的な使い方と、Rails API の連携方法が理解できたと思います。

ぜひ自分のプロジェクトにも活用してみてください！

# 参考サイト

https://marmelab.com/react-admin/

https://qiita.com/dashogo/items/1ab1edeb64a06f7d74c6