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::Cookies
for simplicity and security. -
Static Asset Handling: The
StaticController
is 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-codegen
in 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)