Max Rohowsky, Ph.D.

215 views

Supabase Local Development

This post is summary of how to setup a local development environment for Supabase. There's a strong emphasis on the database because that's usually the tricky part. I like to use this post as a cheat sheet for my own reference.

Running Supabase locally

The Supabase command line interface (CLI) lets you to run the entire stack locally. Let's start by creating a new project:

npx supabase init

This creates a supabase folder with a .temp folder and the config.toml file. The former has unimportant stuff, the latter has important configuration stuff.

To start the Supabase stack, you must launch Docker Desktop and run the command below. This takes time the first time because the Docker images need to be downloaded.

npx supabase start

At the time of publication, the supabase_vector_supabase-playground container breaks. If you encounter a problem with it, set enabled = false under analytics in the config.toml file.

Congrats! You've just created a local development environment for Supabase.

Next, to check the status and get the local development URLs, run:

npx supabase status

To stop the stack, exchange <project_name> with the actual name of your project and run:

npx supabase stop --project-id <project_name>

Using Google auth locally

While testing, it helps to setup Auth to work locally. Here's a quick guide to setup Google Auth on a local Supabase instance. Setting up other Auth providers is similar.

Google Developer Console

Begin by creating new credentials for an OAuth 2.0 client ID in the APIs and services section of the Google Developer Console. Start the setup by clicking on the "Create credentials" button.

Google OAuth 2.0 client ID
Google OAuth 2.0 client ID

During the setup, add the following redirect URI:

http://localhost:54321/auth/v1/callback

That's the standard path for the callback. For testing purposes, the redirect points to your local Supabase instance (see Redirect URI). After walking though the setup, you will get a client ID and client secret (see Client ID and secret):

Redirect URI on Google Developer Console
Redirect URI on Google Developer Console

Supabase configuration

Back in the project, open the config.toml file and add the following to the auth_providers section:

[auth.external.google]
enabled = true
client_id = "env(GOOGLE_CLIENT_ID)"
secret = "env(GOOGLE_CLIENT_SECRET)"
redirect_uri = "http://127.0.0.1:54321/auth/v1/callback"
url = ""
skip_nonce_check = false

Now, create an .env file in the root of the project and add the id and secret you got from the Google Developer Console:

GOOGLE_CLIENT_ID="insert_id_here"
GOOGLE_CLIENT_SECRET="insert_secret_here"

For these changes to take effect, you need to stop and start Supabase. More details on local auth can be found in the Supabase documentation.

NextJS login components

The Google auth flow works as follows:

  • User clicks login button which opens Google auth page
  • After successful login user is redirected to Supabase callback: localhost:54321/auth/v1/callback
  • Supabase processes OAuth and redirects to the callback in the NextJS app
  • App callback exchanges code for session and redirects to final destination

Here's an example of a Google login button component that uses the Supabase client to sign-in with Google.

// .../components/google-login.tsx

'use client';

import { FcGoogle } from "react-icons/fc";
import { Button } from "@/components/ui/button";
import { createClient } from "@/utils/supabase/client";
import { AppRoutes } from "@/routes";

export const GoogleLogin = () => {
  const supabase = createClient();

  const onClick = async () => {
    supabase.auth.signInWithOAuth({
      provider: "google",
      options: {
        redirectTo: `${origin}/api/auth/callback?redirect=${encodeURIComponent("/dashboard")}`,
      },
    });
  }

  return (
    <div className="flex items-center w-full gap-x-2">
      <Button
        size="lg"
        className="w-full"
        variant="outline"
        onClick={onClick}
      >
        <FcGoogle className="h-5 w-5" />
      </Button>
    </div>
  )
}

Local database migrations

Migrations let us version control changes — such as creating tables, adding columns, or inserting data — to a database. This ensures that database changes are consistently applied across different environments.

Create a new migration

Migrations are .sql files stored in the supabase/migrations folder. To create a new and empty migration file, run:

npx supabase migration new example_migration

This will create a new file with the name structure <timestamp>_example_migration.sql inside the supabase/migrations folder.

You can add any SQL statements to the migration file, such as:

-- Example migration file

create table if not exists example_table (
    id uuid default gen_random_uuid() primary key,
    message text not null
);

insert into example_table (message) values ('Hello, world!');

This migration file creates the example_table with an id and a message column and inserts a row with data into the table.

Apply a migration locally

By applying a migration, the SQL code in the migration files gets applied to the database on the local Supabase instance. To apply a migration such as the one above, run:

npx supabase migration up

Afterwards, you'll see the changes applied in the Supabase studio?Similar to the dashboard on the Supabase platform but tailored to local development. As you can see from the studio snapshot below, the table and row have been created from the content of the migration file:

Applying a migration
Applying a migration

Pull local schema changes

Pulls schema changes from the local database. A new migration file will be created in the supabase/migrations directory. By default, the the command pulls from the remote database so --local flag is required to pull from the local database.

supabase db pull --local

If the migration history table is empty, pg_dump is used to capture the content of the remote schemas. However, if the migration table has entries, this command will diff schema changes equivalent to npx supabase db diff.

Diff local schema changes

The diff command compares the schema of a target database against a shadow database?The shadow database is created by applying the migration files from your local migrations directory in a separate container generated from the SQL files in your local project's migrations directory.

For convenience, you can add the -f flag to save the schema diff as a new migration file.

npx supabase db diff -f new_changes_migration

Here's an example of a migration file that contains the diff after adding a column to the example_table table (mentioned earlier) in the Supabase studio:

-- Example 'diff'

alter table "public"."example_table" add column "user" text;

It basically created a shadow database from the SQL files in migrations folder from my editor and then compared it to the local database schema. After that, it evaluated the difference and created a new migration file.

Dump the local database

A dump let's us get the schema and data from the database. Here's how to dump the local database schema, data and a specific schema:

npx supabase db dump --local -f supabase/dump_schema.sql

When using flags with arguments, place the argument immediately after its corresponding flag. For example:
npx supabase db dump --local -f filename.sql
npx supabase db dump -f --local filename.sql

Reset the local database

A reset recreates the local Postgres container and applies all local migrations found in supabase/migrations directory

npx supabase db reset

If test data is defined in supabase/seed.sql, it will be seeded after the migrations are run.

The seed file does not belong into the migrations folder!

Remote database migrations

The remote database CLI commands are similar to local ones, with a few key differences. For ease of reference, I've dedicated a separate section for the remote database migrations.

Linking to a hosted project

Applying database migrations to the Supabase platform requires a link between your local project and the Supabase platform.

Begin by connecting the Supabase CLI to your Supabase account with the following command:

npx supabase login

After login, create the link by inserting your project_id (aka. project ref.) into:

npx supabase link --project-ref <project_id>

You'll be prompted for the database password which can be found in Settings / Database.

To remove the link, run:

npx supabase unlink

Apply a remote migration

Applying a migration to a linked remote database is done using:

npx supabase db push

Once pushed, you can check that the migration version is up to date for the local and remote database by running:

npx supabase migration list

Pull remote schema changes

Pulls schema changes from a linked remote database. A new migration file will be created under supabase/migrations directory. By default, the the command pulls from the remote database (i.e., --linked flag is optional).

npx supabase db pull

Diff remote schema changes

In contrast to diff'ing against the local database, we need to add the --linked flag to diff against the remote database.

npx supabase db diff -f new_changes_migration --linked

Dump the remote database

The CLI commands are similar to the ones we used for the local database except that we don't use the --local flag:

npx supabase db dump -f supabase/dump_schema.sql

Reset the remote database

To reset the remote database using the migrations in your supabase/migrations folder, include the --linked flag:

npx supabase db reset --linked

Careful with this one. Make a data and schema backup before running this command.

Generating TypeScript Types

To generate TypeScript types for the local or remote database, run:

npx supabase gen types --local --lang=typescript > ./types.ts

More information on how to use the generated types file can be found in these two blog YouTube videos:

  1. YouTube video.
  2. YouTube video.

Storage

Storage buckets let us store files (e.g., PDF, images, etc.) in Supabase's cloud storage.

Setup a Bucket

The easiest way to setup a bucket is to use Supabase Studio. Navigate to storage and click on "create bucket". For the code example below to work, make the bucket public.

It's important to set a bucket policy that allows us to upload files. By default, none are set and we'll get an error when trying to upload a file.

Creating a bucket in Supabase Studio
Creating a bucket in Supabase Studio

The policy set in the image above allows anyone to upload JPG images into the images bucket.

NextJS Upload Components

Below is a code example that shows how to upload a file to a bucket. The client component handles the upload form and the route handler handles the file upload to storage.

The Supabase client is necessary to handle the interaction between the NextJS app and the Supabase storage bucket.

'use client'

export default function FileUpload() {

  // API Route handler
  async function handleUpload(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);

    try {
      const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData,
      });

      const result = await response.json();

      if (result.success) alert('API Route Upload successful!');
      else alert('API Route Upload failed!');

    } catch (error) {
      alert(`Error uploading file: ${error}`);
    }
  }

  return (
    <div className="p-4 space-y-8">
      <form onSubmit={handleUpload}>
        <input
          type="file"
          name="file"
          accept="image/jpeg"
        />
        <button
          type="submit"
          className="px-4 
          py-2 
          bg-green-500 
          text-white 
          rounded 
          disabled:bg-gray-400"
        >
          Upload with API Route
        </button>
      </form>
    </div>
  );
}