{"id":22504887,"url":"https://github.com/hoshinotsuyoshi/rails-api-vite-easy-stack","last_synced_at":"2025-09-30T07:31:39.937Z","repository":{"id":259049883,"uuid":"864858450","full_name":"hoshinotsuyoshi/rails-api-vite-easy-stack","owner":"hoshinotsuyoshi","description":"A Monorepo Boilerplate for Rails 8 GraphQL API with Vite + React SPA","archived":false,"fork":false,"pushed_at":"2024-11-08T14:36:55.000Z","size":451,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-11T13:31:34.647Z","etag":null,"topics":["graphql-api","rails","rails-api","spa","vite"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hoshinotsuyoshi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-29T11:03:33.000Z","updated_at":"2024-11-08T14:36:59.000Z","dependencies_parsed_at":"2024-10-22T19:53:53.416Z","dependency_job_id":null,"html_url":"https://github.com/hoshinotsuyoshi/rails-api-vite-easy-stack","commit_stats":null,"previous_names":["hoshinotsuyoshi/rails-api-vite-easy-stack"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoshinotsuyoshi%2Frails-api-vite-easy-stack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoshinotsuyoshi%2Frails-api-vite-easy-stack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoshinotsuyoshi%2Frails-api-vite-easy-stack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoshinotsuyoshi%2Frails-api-vite-easy-stack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hoshinotsuyoshi","download_url":"https://codeload.github.com/hoshinotsuyoshi/rails-api-vite-easy-stack/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234715616,"owners_count":18875905,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql-api","rails","rails-api","spa","vite"],"created_at":"2024-12-07T00:10:26.692Z","updated_at":"2025-09-30T07:31:34.551Z","avatar_url":"https://github.com/hoshinotsuyoshi.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n  :rocket: rails-api-vite-easy-stack :rocket:\n\u003c/h1\u003e\n\n# A Monorepo Boilerplate for Rails GraphQL API with Vite + React SPA\n\nThis monorepo hosts two main applications:\n- A **Rails GraphQL API** running in API mode located in `./backend`\n- A **Vite + React Single Page Application (SPA)** located in `./frontend`\n\n\u003e [!NOTE]\n\u003e While I'm a seasoned Rails engineer (with a love for this tech stack ❤️), this project isn't necessarily something I use in production at work. It's a passion project, and some nuances of my expertise may not be fully captured here. Please use with caution and at your own risk. This repo is provided \"as is,\" without any warranties.\n\n## Project Structure\n\n```bash\n$ tree -L 2\n.\n├── backend                  # Rails GraphQL API (API mode)\n│   ├── Dockerfile           # Dockerfile for the Rails backend\n│   ├── Gemfile              # Gem dependencies\n│   ├── Gemfile.lock\n│   ├── README.md\n│   ├── app                  # Rails application code\n│   ├── config               # Rails configuration\n│   ├── public               # Contains compiled frontend assets\n│   ├── spec                 # RSpec tests\n│   └── ...\n├── frontend                 # Vite + React SPA\n│   ├── README.md\n│   ├── package.json         # Frontend dependencies and scripts\n│   ├── src                  # React source code\n│   ├── dist                 # Build output for the frontend app\n│   └── ...\n└── graphql-schema           # GraphQL schema files\n    └── backend_schema.graphql\n```\n\n## 💪 Backend Development\n\nThe backend is a Rails application running in API mode. Below are the key steps for setting up and running the backend:\n\n1. **Managing Dependencies**\n   To install backend dependencies, use Bundler to install all gems from the `Gemfile`:\n   ```bash\n   # cd ./backend\n   bundle install\n   ```\n\n2. **Database Setup**\n   Ensure the database is properly set up. You can spin up the required services using Docker Compose:\n   ```bash\n   # cd ./backend\n   docker compose up -d\n   ```\n\n3. **Initial Setup**\n   To initialize the application (e.g., creating the database, running migrations), run the setup script:\n   ```bash\n   # cd ./backend\n   bin/setup # Sets up the database and runs migrations\n   ```\n\n4. **Start Server**\n   To start the Rails server for development, use the following command:\n   ```bash\n   # cd ./backend\n   bin/dev\n   ```\n\n5. **Run Tests**\n   To run the test suite, use RSpec:\n   ```bash\n   # cd ./backend\n   bundle exec rspec\n   ```\n\u003e [!TIP]\n\u003e Please note that tests in `spec/system/scenarios` will not work correctly unless you first run `bun run build:move` in the `./frontend` directory.\n\n## 💪 Frontend Development\n\nThe frontend is a Vite-powered React SPA, and Bun is used as the package manager. The primary build scripts are defined in `frontend/package.json`:\n\nKey commands include:\n- `bun run dev`: Starts the Vite development server for live preview.\n- `bun run build`: Builds the production assets for deployment.\n- `bun run build:move`: Builds the frontend and moves the build artifacts into the Rails public directory.\n- `bun run graphql-codegen`: Generates TypeScript types from the GraphQL schema.\n\n## 🧩 GraphQL Schema Management\n\nThis project adopts a **Code-First** approach to defining GraphQL schemas using the `graphql-ruby` gem. Here’s how the backend and frontend integrate using GraphQL schemas:\n\n1. **Update GraphQL Schema in Backend**\n   Use the available rake task in the backend to update the GraphQL schema and output it to the `graphql-schema` directory:\n   ```bash\n   # cd ./backend\n   bin/rails graphql:schema:idl\n   ```\n\n2. **Generate TypeScript Types in Frontend**\n   Run the following command in the frontend to generate TypeScript types based on the updated GraphQL schema:\n   ```bash\n   # cd ./frontend\n   bun run graphql-codegen\n   ```\n\n   This process ensures that the types are correctly synchronized between the backend and frontend, facilitating type-safe GraphQL queries in the frontend.\n\n## 🚛 Deployment Process\n\n\u003e [!TIP]\n\u003e This section is still under construction. 🚧\n\nThe deployment process involves building the frontend, syncing the build artifacts to the Rails `public/` directory using `rsync`, and building the Docker image for the Rails API.\n\n### Steps:\n\n1. **Build the frontend and Move frontend build artifacts to Rails**\n   Navigate to the `frontend` directory and run the build command using Bun. This compiles the React app and outputs the assets to `backend/public/assets`:\n   ```bash\n   # cd frontend\n   bun run build:move\n   ```\n\n2. **Build the Docker image**\n   After the assets are moved, the Rails backend can be built into a Docker image:\n   ```bash\n   # cd backend\n   docker build -t my-spa .\n   ```\n\n3. **Deploy**\n   Deploy the application using your preferred method (e.g., Docker Compose, Kubernetes, or any CI/CD pipeline).\n\n## ✨ Key Features of the Rails Application\n\nThe Rails API serves as the backend for the SPA and manages authentication and routing for the client. Below are some technical highlights of the Rails setup.\n\n---\n\n### 1. Authentication\n\n#### Based on Rails 8's `bin/rails generate authentication`\nThis application utilizes the Rails 8 authentication generator. Some methods that are unnecessary for API mode have been commented out. During login mutations, filtered `request` information is exposed via `context`, allowing mutations in `app/graphql/mutations` to manage session data.\n\n#### Secure Cookie-Based Sessions\nThis application uses `Set-Cookie` with `http-only` attributes for secure session management in a same-origin setup. This avoids the complexities of configuring CORS headers or dealing with JWT token expiration and storage in client-side local storage.\n\n#### API Mode and Cookie Management\nThis application uses `ActionController::Cookies` to enable cookie-based sessions even in API mode, facilitating client-side authentication flows.\n\n```ruby\n# app/controllers/application_controller.rb\nclass ApplicationController \u003c ActionController::API\n  include ActionController::Cookies\nend\n```\n\n---\n\n### 2. Routing and Static Assets Management\n\nGiven that the backend and frontend run on the same origin, routing conflicts have been carefully avoided. The Rails API primarily operates through a single endpoint: `POST /graphql`. All other paths are reserved for frontend use 😀.\n\n#### backend side:\n\nThe `StaticController` serves the frontend's `index.html` for specified routes. This design helps in future-proofing for custom 404 pages or path-specific `Cache-Control` headers.\n\n```ruby\n# config/routes.rb\n[\n  \"/login\",\n  \"/me\",\n  \"/signup\"\n].each { get _1, to: \"static#index\" }\n```\n\n```ruby\n# app/controllers/static_controller.rb\nclass StaticController \u003c ApplicationController\n  def index\n    render plain: Rails.public_path.join('index.html').read, layout: false\n  end\nend\n```\n\n#### frontend side:\n\nIn development mode, Vite's proxy is used to forward API requests from the frontend to the backend. This allows running the Rails API and Vite development servers simultaneously, preventing cross-origin issues.\n\nHere’s how it’s set up in the `vite.config.ts`:\n\n```js\nserver: {\n  proxy: {\n    '/graphql': 'http://localhost:3000',\n  },\n}\n```\n\nIn production, Rails serves the frontend’s static assets directly, and API requests are handled natively by the Rails backend.\n\n---\n\n### 3. Signup Flow\n\nSince Rails 8’s authentication generator doesn’t provide a signup mechanism, this application demonstrates a custom flow. It includes features similar to `devise`’s `confirmable` and `registerable`. For details, check the `signup` and `verify_email_address` mutations.\n\n---\n\n### 4. System Test\n\nRails system tests are executed using Capybara and Puma. Transactional database cleaning ensures isolated tests, allowing easy testing of both frontend and backend interactions in an integrated environment.\n\nHere’s a sample test simulating the signup flow:\n\n```ruby\n  # simulates a complete signup flow, including email verification and password setup\n  it 'signup -\u003e mail verification -\u003e set password' do\n    visit '/login'\n\n    expect(page).to have_content('Login')\n    click_link 'Create an account'\n\n    expect(page).to have_content('Signup')\n    fill_in \"email\", with: email\n    expect(ActionMailer::Base.deliveries).to be_empty\n    click_button \"Sign up\"\n    expect(page).to have_content('Inviting')\n\n    perform_enqueued_jobs(only: ActionMailer::MailDeliveryJob)\n    mail_message = ActionMailer::Base.deliveries.sole\n    url = URI.parse(extract_a_href_from_message(mail_message:))\n    visit url.request_uri\n\n    expect(page).to have_content('Email verification successful!')\n    expect(page).not_to have_content('Email verification successful!')\n    expect(page).to have_content('New Password')\n    expect(page).to have_content('Confirm Password')\n    password = SecureRandom.alphanumeric\n    fill_in \"password\", with: password\n    fill_in \"confirmPassword\", with: password\n    click_button \"Set Password\"\n    expect(page).to have_content(\"hello, It's me!\")\n    expect(page).to have_content(email)\n  end\n```\n\nFor more, see the `spec/system` test files.\n\n## LICENSE\n\nSee `LICENSE` file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoshinotsuyoshi%2Frails-api-vite-easy-stack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhoshinotsuyoshi%2Frails-api-vite-easy-stack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoshinotsuyoshi%2Frails-api-vite-easy-stack/lists"}