Introducing rails-api-vite-easy-stack: A Monorepo Boilerplate for Building Rails APIs with Vite + React SPA
I’m thrilled to introduce my latest passion project, rails-api-vite-easy-stack, a monorepo boilerplate designed to streamline development for those who love Rails and are excited by the simplicity and speed of Vite + React SPAs. While this project draws on my experience as a seasoned Rails engineer, it represents a personal exploration into combining these technologies rather than a production-ready setup. So, consider this an open invitation to explore, experiment, and customize as you see fit.
🏗️ Project Structure
Here's a quick look at the project layout:
- Backend: A Rails application running in API mode with a GraphQL API. The setup leverages Docker and RSpec for development and testing.
- Frontend: A Vite-powered React SPA using Bun as the package manager, ensuring fast builds and smooth developer experience.
- GraphQL Schema Management: Organized separately to simplify backend-frontend type synchronization.
The entire monorepo structure can be summarized like this:
.
├── backend                  # Rails GraphQL API
├── frontend                 # Vite + React SPA
└── graphql-schema           # Shared GraphQL schema
🚀 Backend Highlights
The backend is a Rails API that comes with:
- 
Authentication: Leveraging the new Rails 8 authentication generator. It’s tailored for API mode, using cookie-based sessions managed via ActionController::Cookiesfor simplicity and security.
- 
Static Asset Handling: The StaticControlleris used to serve the SPA’sindex.html, enabling seamless client-side routing.
- 
Custom Signup Flow: Since the Rails 8 generator doesn’t handle signups out of the box, I implemented a custom flow reminiscent of devise, complete with email verification and password setup.
Here's a peek at how the backend's static asset management is handled:
# config/routes.rb
[
  "/login",
  "/me",
  "/signup"
].each { get _1, to: "static#index" }
# app/controllers/static_controller.rb
class StaticController < ApplicationController
  def index
    render plain: Rails.public_path.join('index.html').read, layout: false
  end
end
⚡ Frontend Features
The frontend is built with Vite and React, and here’s why it’s awesome:
- Live Reloading: Thanks to Vite’s development server, frontend changes are reflected instantly.
- 
GraphQL Type Safety: Using graphql-codegen, the frontend automatically generates TypeScript types from the GraphQL schema, ensuring type-safe queries and mutations.
To start the frontend, use:
# cd ./frontend
bun run dev
Proxy Setup for Development
To avoid cross-origin issues, Vite’s proxy forwards API requests to the Rails backend:
// vite.config.ts
server: {
  proxy: {
    '/graphql': 'http://localhost:3000',
  },
}
🧩 Synchronizing Backend and Frontend
Keeping backend and frontend in sync is a breeze with this workflow:
- Update GraphQL Schema: Generate the schema in the backend:
   bin/rails graphql:schema:idl
- 
Generate TypeScript Types: Use graphql-codegenin the frontend:
   bun run graphql-codegen
This integration ensures that both backend and frontend speak the same language.
🛠️ System Tests
System tests in the Rails backend use Capybara and Puma, simulating end-to-end flows. Here’s an example test for the signup process:
it 'signup -> mail verification -> set password' do
  visit '/login'
  expect(page).to have_content('Login')
  click_link 'Create an account'
  fill_in "email", with: email
  click_button "Sign up"
  expect(page).to have_content('Inviting')
  perform_enqueued_jobs(only: ActionMailer::MailDeliveryJob)
  mail_message = ActionMailer::Base.deliveries.sole
  url = URI.parse(extract_a_href_from_message(mail_message:))
  visit url.request_uri
  fill_in "password", with: SecureRandom.alphanumeric
  click_button "Set Password"
  expect(page).to have_content("hello, It's me!")
end
For more examples, check the spec/system directory in the repo.
📦 Deployment
Though the deployment process is still evolving, here are the essentials:
- Build the Frontend:
   # cd ./frontend
   bun run build:move
- Build the Backend Docker Image:
   # cd ./backend
   docker build -t my-spa .
The built frontend assets are synced into the Rails public directory, making it easy for Rails to serve the entire SPA.
🙏 A Word of Caution
This boilerplate is a starting point, not a silver bullet. Please customize and test thoroughly before considering it for production use. As always, feedback and contributions are welcome!
For the complete code and to dive deeper, visit rails-api-vite-easy-stack.
Thank you for taking the time to read about rails-api-vite-easy-stack! I’d love to hear your thoughts on this approach. Do you have any questions about the setup or ideas for improving it? Perhaps you've tried something similar and have insights or challenges to share. Feel free to drop your comments below—I’m looking forward to an engaging discussion and learning from your experiences!" 
Happy coding! ✨
 

 
    
Top comments (0)