Build Rails 8 API + React + Vite from Scratch – Part 2

For this specific project, I recommend a single Git repository containing two applications:

rails_react_user_management/
├── backend/      # Rails API
└── frontend/     # React + Vite

This is cleaner than forcing Vite into Rails views because we explicitly want an API-only Rails backend. React’s current guidance supports using Vite as a build tool when building React from scratch, and Vite has a first-class Rails integration, but that integration is more useful when Rails is also serving the frontend. (React)

For our learning project, we’ll therefore keep the frontend and backend clearly separated while keeping them in one repo.

1. Prerequisites

Check:

ruby -v
rails -v
node -v
npm -v

Current Vite requires Node.js 20.19+ or 22.12+. (vitejs)


2. Create the Rails API

mkdir rails_react_user_management
cd rails_react_user_management

rails new backend --api -d postgresql
cd backend

The important part is:

--api

This tells Rails to create an API-only application rather than a traditional Rails application with views/assets. That’s exactly what we want.

Create the database:

bin/rails db:create

Start Rails:

bin/rails server

API:

http://localhost:3000

3. Create our User API

Let’s create a simple User model:

bin/rails generate model User name:string email:string
bin/rails db:migrate

Create controller:

bin/rails generate controller Users

app/controllers/users_controller.rb:

class UsersController < ApplicationController
  def index
    render json: User.all
  end

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

  def create
    user = User.create!(user_params)
    render json: user, status: :created
  end

  private

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

Routes:

# config/routes.rb

Rails.application.routes.draw do
  scope "/api" do
    resources :users
  end
end

Now:

curl http://localhost:3000/api/users

returns:

[]

Our backend is ready.


4. Create React + Vite frontend

Go back to the root:

cd ..

Create React using Vite:

npm create vite@latest frontend -- --template react
cd frontend
npm install

This creates:

frontend/
├── src/
│   ├── App.jsx
│   └── main.jsx
├── public/
├── index.html
├── package.json
└── vite.config.js

React’s documentation currently recommends a build tool such as Vite when building a React application from scratch and Create React App is deprecated. (React)


5. Start React

npm run dev

You should get:

http://localhost:5173

So now we have:

React/Vite
localhost:5173
↓ HTTP
Rails API
localhost:3000

6. Connect React to Rails

Modify:

// frontend/src/App.jsx

import { useEffect, useState } from "react";

function App() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("http://localhost:3000/api/users")
      .then(response => response.json())
      .then(data => setUsers(data));
  }, []);

  return (
    <div>
      <h1>Users</h1>

      {users.map(user => (
        <div key={user.id}>
          {user.name} - {user.email}
        </div>
      ))}
    </div>
  );
}

export default App;

But there’s an important issue here:

5173 → 3000

The browser sees these as different origins, so we’ll eventually need CORS or a development proxy.

For development, I prefer the Vite proxy approach.

We can also configure cors from Rails end and make it work:


7. Configure Vite proxy

// frontend/vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],

  server: {
    proxy: {
      "/api": "http://localhost:3000",
    },
  },
});

Now React can simply call:

fetch("/api/users")

instead of:

fetch("http://localhost:3000/api/users")

Vite receives:

GET /api/users

and proxies it to:

http://localhost:3000/api/users

So our architecture becomes:

Browser
   │
   ▼
Vite :5173
   │
   │ /api/*
   ▼
Rails :3000
   │
   ▼
PostgreSQL

8. Seed some users

Let’s add some data.

# backend/db/seeds.rb

User.create!([
  { name: "Abhilash", email: "abhilash@example.com" },
  { name: "John", email: "john@example.com" }
])

Run:

cd backend
bin/rails db:seed

Then:

curl http://localhost:3000/api/users

You should get:

[
  {
    "id": 1,
    "name": "Abhilash",
    "email": "abhilash@example.com"
  },
  {
    "id": 2,
    "name": "John",
    "email": "john@example.com"
  }
]

Refresh React:

http://localhost:5173

and the users should appear.


9. Final project structure

rails_react_user_management/
│
├── backend/
│   ├── app/
│   │   ├── controllers/
│   │   │   └── users_controller.rb
│   │   └── models/
│   │       └── user.rb
│   │
│   ├── config/
│   │   └── routes.rb
│   ├── db/
│   └── Gemfile
│
├── frontend/
│   ├── src/
│   │   ├── App.jsx
│   │   └── main.jsx
│   ├── public/
│   ├── package.json
│   └── vite.config.js
│
└── README.md

10. What we’re learning from this project

We’ll gradually turn this into a proper int.-ready application:

                    React
                      │
             ┌────────┴────────┐
             │                 │
        Components          Hooks
             │                 │
             └────────┬────────┘
                      │
                 API calls
                      │
                      ▼
                Rails API
                      │
             ┌────────┴────────┐
             │                 │
        Controllers       Services
             │                 │
             └────────┬────────┘
                      │
                 ActiveRecord
                      │
                      ▼
                  PostgreSQL

Then we’ll add React Router, reusable components, forms, CRUD, loading/error states, custom hooks, useContext, Redux Toolkit, API abstraction, authentication, pagination, optimistic updates, testing, and production deployment.

For the int., the important architectural distinction is:

Rails is the API/backend and React is the client application. Vite is the frontend development/build tool.

Vite provides the development server and production build pipeline; its current production build uses Rolldown and outputs optimized static assets. (vitejs)

Next, we should implement the actual User Management UI – User list + Add User + Edit + Delete – using proper React components and API calls, because that will give you the hands-on React knowledge needed for the int.

Unknown's avatar

Author: Abhilash

Hi, I’m Abhilash! A seasoned web developer with 15 years of experience specializing in Ruby and Ruby on Rails. Since 2010, I’ve built scalable, robust web applications and worked with frameworks like Angular, Sinatra, Laravel, Node.js, Vue and React. Passionate about clean, maintainable code and continuous learning, I share insights, tutorials, and experiences here. Let’s explore the ever-evolving world of web development together!

Leave a Reply