supabase-rb-rb
Rails API starter

Getting started

Clone the Rails API starter, install dependencies, point it at a Supabase project, and make your first authenticated request.

This guide takes you from a clean machine to a green test suite and a verified GET /api/v1/me round-trip. Budget 15 minutes if you already have Ruby and Docker installed; 30 if you don't.

Prerequisites

ToolVersionWhy
Rubymatches .ruby-version (Rails 8.1, currently 4.0.x)App runtime. Install with rbenv, asdf, or mise.
Bundler4.xgem install bundler after Ruby is installed.
Postgres14+Rails needs a local DB. The Supabase CLI bundles one — see below.
Supabase CLI2.xbrew install supabase/tap/supabase, or official install docs. Only needed for a fully local Supabase stack.
DockerrecentRequired by supabase start and by the Kamal deploy.
GitanyFor cloning.

You can skip Postgres if you let the Supabase CLI start one in Docker. You can skip the Supabase CLI if you point the kit at a Supabase Cloud project instead.

1. Get the code

The starter is published as a standalone repo, not a generator — copy it, don't render it:

git clone https://github.com/supabase-ruby/starter-kit-api my-api
cd my-api

Then either commit it into a fresh repo of your own, or keep the upstream remote if you want to pull future starter updates.

2. Install gems

bundle install

The first install pulls a Rails 8 baseline plus supabase-rails, rack-attack, rack-cors, rswag-*, and the test stack (rspec-rails, factory_bot_rails).

3. Run the tests

Run the spec suite before any environment setup — it's hermetic. spec/rails_helper.rb seeds SUPABASE_* test ENV with a dummy JWT secret and an in-memory JWKS so signed tokens minted by SupabaseAuthHelper verify without network calls.

bundle exec rspec
# 9 examples, 0 failures

If you see failures here, stop and read Troubleshooting → Spec suite won't start before going further — a broken hermetic suite usually means a bad Ruby/Bundler install, not a configuration problem.

4. Set environment variables

The dev server needs real Supabase URLs and keys (the spec suite doesn't). Copy the template:

cp .env.example .env

You need four values. Pick A or B depending on whether you want a fully local Supabase or a Cloud project.

A. Local Supabase via the CLI

From the project root:

supabase init     # first time only — creates ./supabase
supabase start    # boots Postgres + GoTrue + Studio in Docker

The CLI prints something like:

         API URL: http://127.0.0.1:54321
          DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
        anon key: eyJhbGciOi...
      JWT secret: super-secret-jwt-token-with-at-least-32-characters-long

Copy each into .env:

CLI key.env variable
API URLSUPABASE_URL
anon keySUPABASE_ANON_KEY
JWT secretSUPABASE_JWT_SECRET
DB URLDATABASE_URL

B. Supabase Cloud

If you already have a project at supabase.com:

.env variableDashboard location
SUPABASE_URLProject Settings → API → Project URL
SUPABASE_ANON_KEYProject Settings → API → anon / public key
SUPABASE_JWT_SECRETProject Settings → API → JWT Settings → JWT Secret
DATABASE_URLProject Settings → Database → Connection string (use the pooler)

The .env file is gitignored. dotenv-rails loads it automatically in development and test. In production, .env is not read — set the same variables through your deploy platform's secret manager (see Deployment).

5. Prepare the database

bin/rails db:prepare

Even though the kit has no domain models, Rails 8's Solid Cache / Queue / Cable schemas need to be loaded.

6. Boot the server

bin/rails s
# Listening on http://127.0.0.1:3000

7. Make your first requests

Smoke-test the two public-shape endpoints:

curl -i http://127.0.0.1:3000/healthz
# HTTP/1.1 200 OK
# {"status":"ok"}

curl -i http://127.0.0.1:3000/api/v1/me
# HTTP/1.1 401 Unauthorized
# {"error":"unauthorized"}

The 401 is the right answer — there's no Bearer token on the request.

To hit /api/v1/me successfully, you need a real Supabase JWT. The fastest way to get one in development is from supabase-js in a quick Node script:

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!,
);

const { data, error } = await supabase.auth.signInWithPassword({
  email: "alice@example.com",
  password: "correct-horse-battery-staple",
});
if (error) throw error;

console.log(data.session!.access_token);

Then call the API with that token:

TOKEN="eyJhbGciOi..."  # paste the access_token here
curl -i http://127.0.0.1:3000/api/v1/me \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
# HTTP/1.1 200 OK
# {"id":"...","email":"alice@example.com","role":"authenticated", ...}

For local Supabase, create the user first via Studio (http://127.0.0.1:54323 → Authentication → Users → "Add user"), or via supabase-js's signUp. For a Cloud project, the same flow works against the real auth endpoint.

8. Open the API docs

open http://127.0.0.1:3000/api-docs

rswag-ui renders swagger/v1/swagger.yaml. The "Authorize" button there accepts a Bearer token so you can try GET /api/v1/me directly from the page.

Next

On this page