Supabase + React Router: Local Development and Migrations (Part 1)
Kevin Julián Martínez Escobar

Kevin Julián Martínez Escobar @kevinccbsg

Location:
Spain
Joined:
Dec 31, 2019

Supabase + React Router: Local Development and Migrations (Part 1)

Publish Date: Jul 3
1 2

Welcome to a new series where we explore Supabase and its integration with React Router. Throughout the posts, we’ll cover topics like:

In this first post, we’ll focus on running Supabase locally. Most tutorials guide you through the Supabase dashboard, but if you want to test advanced features or collaborate with a team, working locally is essential.

We’ll also show how to include database changes as migrations in your repository so every developer can reproduce the exact setup.

Requirements

Make sure you have:

  • Docker installed
  • Node.js version 20 or higher

Setting Up a Local Supabase Project

We’ll start by setting up our base project. You can clone the repo here:

git clone git@github.com:kevinccbsg/react-router-tutorial-supabase.git
cd react-router-tutorial-supabase
git checkout 01-setup
npm i
Enter fullscreen mode Exit fullscreen mode

Then run the following command to initialize Supabase locally. This setup follows the official Supabase documentation:

npx supabase init
Enter fullscreen mode Exit fullscreen mode

You’ll be asked a couple of questions; you can safely skip them (press N):

Generate VS Code settings for Deno? [y/N]
Generate IntelliJ Settings for Deno? [y/N]
Finished supabase init.
Enter fullscreen mode Exit fullscreen mode

This command creates a supabase/ directory with a config.toml file. This file contains your local Supabase configuration. You can adjust ports, email settings, and more. Read more here.

To spin up your local Supabase services (Postgres, Studio, etc.), run:

npx supabase start
Enter fullscreen mode Exit fullscreen mode

This might take a couple of minutes. Once done, you should see output like this:

         API URL: http://127.0.0.1:54321
     GraphQL URL: http://127.0.0.1:54321/graphql/v1
  S3 Storage URL: http://127.0.0.1:54321/storage/v1/s3
          DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
      Studio URL: http://127.0.0.1:54323
    Inbucket URL: http://127.0.0.1:54324
      JWT secret: super-secret-jwt-token-with-at-least-32-characters-long
        anon key: anon_key
service_role key: service_role_key
   S3 Access Key: S3_Access_Key
   S3 Secret Key: S3_Access_Key
       S3 Region: local
Enter fullscreen mode Exit fullscreen mode

You can now open http://127.0.0.1:54323 to access Supabase Studio locally.

Each developer in your team will need to run the same setup locally. Later, we’ll explain how to apply changes to the database using migrations so everyone stays in sync.

You can always check the status of your services with:

npx supabase status
Enter fullscreen mode Exit fullscreen mode

Creating Migrations

Let’s create our first migration. We could write raw SQL, but let’s do it using the Supabase Studio for this example.

We’re building a simple app where each user has a list of contacts.

Data Models

interface Contact {
  id: string;
  firstName: string;
  lastName: string;
  username: string;
  email: string;
  phone: string;
  favorite: boolean;
  avatar?: string;
}

interface User {
  id: string;
  email: string;
}
Enter fullscreen mode Exit fullscreen mode

Creating Tables

Start by creating the profiles and contacts tables through the Supabase Studio:

Profiles table:

Profiles table

Contacts table:

contact table intro
contact table fields

Here’s the SQL definition:

create table public.profiles (
  id uuid not null default gen_random_uuid (),
  created_at timestamp with time zone not null default now(),
  email text not null,
  constraint profiles_pkey primary key (id)
) TABLESPACE pg_default;

create table public.contacts (
  id uuid not null default gen_random_uuid (),
  created_at timestamp with time zone not null default now(),
  first_name text not null,
  last_name text not null,
  username text not null,
  email text not null,
  phone text not null,
  favorite boolean not null,
  avatar text null,
  profile_id uuid not null,
  constraint contacts_pkey primary key (id),
  constraint contacts_profile_id_fkey foreign KEY (profile_id) references profiles (id) on delete CASCADE
) TABLESPACE pg_default;
Enter fullscreen mode Exit fullscreen mode

Now that our tables are ready, let’s persist those changes as a migration.

Saving Changes as a Migration

Run this command to create a new migration file:

npx supabase migration new create_profiles_contacts_tables
Enter fullscreen mode Exit fullscreen mode

This creates a file inside the supabase/migrations/ folder, but it’s empty by default. To generate the actual SQL for the migration based on the changes we made in Supabase Studio:

npx supabase db diff --schema public > migration.sql
Enter fullscreen mode Exit fullscreen mode

This generates a migration.sql file with all the SQL commands. Copy its contents into the newly created migration file.

⚠️ Tip: Add migration.sql to your .gitignore file. It’s just a temp file.

To apply migrations or reset the local database (useful when syncing with teammates):

npx supabase db reset
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this first part, we’ve set up a local Supabase environment using the CLI, explored how to create tables through Supabase Studio, and learned how to track those changes using migrations.

This setup ensures every developer on your team can work with the same local database structure and share changes through version control.

In the next post, we’ll dive into authentication with Supabase and show how to use React Router to protect routes based on user sessions.

Comments 2 total

  • Josep
    JosepJul 25, 2025

    In a prod environment, where do you store those migrations ? Do you usually have a repo for auth or you keep this Supabase migrations in the frontend repo ?

    Thanks for these amazing series of posts, Kevin !

    • Kevin Julián Martínez Escobar
      Kevin Julián Martínez EscobarJul 25, 2025

      That's a good question. Since Supabase is a backend-as-a-service, you can store your migrations — just like we do in this example.

      In a future post, we'll cover how to run these migrations against your Supabase production instance using a few simple commands. We'll also show how to automate that process with a GitHub Action triggered when code is pushed to main.

Add comment