Build an Expense Tracker with Supabase

Design a PostgreSQL schema and manage database migrations with Supabase CLI.

Introduction

30 Second Summary

Every time you change your database structure, you're making a promise to your app about what the data looks like. Without a way to track those promises, one wrong click in a dashboard can break everything.

In this project, you will design a personal expense tracker database using the Supabase Dashboard UI, then capture every change as a versioned migration file that you can replay, share, and deploy to production.

What You'll Build

You'll run a single command and watch your entire database rebuild itself from scratch. Then you'll push it live and see your tables appear in a cloud dashboard, ready for a frontend to connect to.

By the end of this project, you'll have:

  • A local development stack where you can design tables visually, then run supabase db reset and watch your full schema recreate itself from versioned migration files.
  • A live remote database deployed with supabase db push, where you can open the cloud dashboard and see your expense tracker tables ready to use.
  • Firsthand experience of the exact failure that happens when you edit a remote database directly, so you understand why all schema changes must flow through migration files.
  • Secret Mission: Write raw SQL to add seed data and a CHECK constraint that rejects negative expense amounts at the database level.

Are there any prerequisites?

You'll need basic JavaScript/TypeScript knowledge, familiarity with Git basics (add, commit, push), and a Supabase account with at least one free project slot available.

The Supabase Free Plan allows 2 active projects. If you already have 2, you'll need to pause or delete one before starting.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why a migrations workflow matters for projects like Gavelogy.

Set Up Docker, CLI, and Local Stack

Without a local Supabase stack, every schema change you make goes directly to your remote database. There is no undo button, no version history, and no way to reproduce your setup from scratch.

In this step, you will spin up a full local Supabase environment on your machine using Docker and the Supabase CLI. This gives you a safe sandbox to design tables visually and capture every change as a versioned migration file.

In this step, get ready to:
  • Start Docker Desktop so the Supabase CLI can run its containers.
  • Create your project folder and install the Supabase CLI.
  • Launch the local Supabase stack and confirm Studio is running.
Start Docker Desktop

The Supabase CLI uses Docker containers to run a full local development stack. Docker Desktop must be running before you can start Supabase locally.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Docker Desktop and press Enter to open it.
  • Wait for the engine status to show Running (green icon in the system tray or bottom-left corner of the Docker Desktop window).

What is Docker Desktop doing here?

Supabase's local stack runs a PostgreSQL database, an API gateway, an auth server, and a Studio UI all inside Docker containers. When you see the green "Running" indicator, Docker is ready to launch those containers on your behalf.

Docker Desktop stuck on Starting?

If Docker asks you to accept terms or sign in, do that first. On Windows, make sure WSL 2 is enabled (Docker Desktop will prompt you if it is not). A restart of Docker Desktop usually resolves a stuck state.

help me troubleshoot Docker Desktop not starting

Create and initialize the project

Now that Docker is running, you will create a project folder on your Desktop and install the Supabase CLI as a development dependency.

  • Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows), type Terminal (macOS) or PowerShell (Windows), and press Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir expense-tracker
cd expense-tracker
  • Confirm you are in the right place by running:
pwd

You should see a path ending in /Desktop/expense-tracker.

  • Initialize a pnpm project by running:
pnpm init

This creates a package.json file in your folder.

  • Install the Supabase CLI as a dev dependency by running:
pnpm add supabase --save-dev --allow-build=supabase

Why the --allow-build flag?

pnpm version 10 and higher blocks postinstall scripts by default for security reasons. The --allow-build=supabase flag explicitly permits the Supabase package to run its setup scripts during installation.

You will see download progress in your terminal. Once it finishes, the Supabase CLI is available via pnpx supabase.

  • Initialize the Supabase project structure by running:
pnpx supabase init

This creates a supabase/ folder containing a config.toml file that defines your local stack settings.

  • Confirm everything was created by running:
ls

You should see node_modules, package.json, pnpm-lock.yaml, and a supabase folder listed.

Seeing permission errors during install?

Make sure you are running the terminal as your regular user, not as root or admin. If you see EACCES errors on macOS, check that your node_modules folder is not owned by root.

help me fix pnpm permission errors during Supabase CLI install

  • Open the project in Windsurf. Press Cmd+Space (macOS) or the Windows key (Windows), type Windsurf, and press Enter.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the expense-tracker folder, and click Open.

You should see package.json and the supabase folder in the Windsurf Explorer sidebar.

Start the local Supabase stack

With Docker running and the CLI installed, you can now launch the full local Supabase stack. The first run downloads several Docker images, which may take 2-5 minutes depending on your internet speed.

  • Start the local Supabase services by running this command in your terminal:
pnpx supabase start

What is happening during the download?

Supabase is pulling Docker images for PostgreSQL, an API gateway, an auth server, a storage service, and the Studio dashboard UI. Once downloaded, future starts take only a few seconds.

When the command finishes, it prints a table of local service URLs and keys. Look for the Studio URL at http://localhost:54323.

  • Open http://localhost:54323 in your browser to see the local Supabase Studio Dashboard.

You should see a dark-themed dashboard with a sidebar showing options like Table Editor, SQL Editor, and Authentication.

Supabase start failing with Docker errors?

Confirm Docker Desktop is still showing the green "Running" status. If you see a "Cannot connect to the Docker daemon" message, restart Docker Desktop and wait for it to fully start before trying again.

help me fix Supabase CLI Docker connection errors

Before you run the next command, what do you think it will show you?

  • Verify your local stack is healthy by running:
pnpx supabase status

You should see a grouped table of local service URLs. Confirm that Studio shows http://localhost:54323, the API shows http://localhost:54321, and the DB is at localhost:54322.

Your local development environment is ready. Next up, you will design your first table visually in the Studio Dashboard and capture it as a migration file.

Design Your Categories Table

Your local Supabase stack is running and the Dashboard is ready. Now it's time to create your first table.

Every expense needs a category. Food, Transport, Bills. You'll create a categories table visually in the Dashboard UI, then capture that work as a version-controlled migration file.

In this step, get ready to:
  • Create a categories table using the local Dashboard Table Editor.
  • Capture the table as a SQL migration file using pnpx supabase db diff.
  • Verify the migration can recreate your table from scratch.
Create the categories table in the Dashboard

The Table Editor in Supabase Studio lets you design tables visually without writing SQL. You'll use it to define the columns, types, and constraints for your categories table.

  • Switch to the local Studio Dashboard in your browser at http://localhost:54323.
  • Click Table Editor in the left sidebar.
  • Click New Table in the centre of the page.
  • In the Name field, type categories.
  • Uncheck Enable Row Level Security (RLS). You will cover RLS in a future project.

Now set up the columns. The table starts with a default id column. Configure it and add three more:

  • Set the id column to type int8, check Primary, and set the default to Is Identity with Generated Always selected.
  • Add a new column named name with type text. Uncheck the Is Nullable toggle to make it NOT NULL.
  • Add a new column named color with type text. Set the default value to '#6B7280'.
  • Add a new column named created_at with type timestamptz. Set the default value to now().
  • Click Save at the bottom of the panel.

You should see categories appear in the left sidebar of the Table Editor with all four columns listed.

What do these columns do?

  • id is an auto-incrementing number that uniquely identifies each category. "Generated always" means the database assigns it automatically.
  • name holds the category label ("Food", "Transport"). NOT NULL means every category must have a name.
  • color stores a hex colour code for UI display. The default #6B7280 is a neutral grey.
  • created_at records when the row was inserted. now() fills it in automatically.

Don't see the New Table button?

Make sure you are on the local Dashboard at http://localhost:54323, not the remote Supabase Dashboard at supabase.com. The local stack must be running for Studio to load.

If the page is blank or won't load, switch back to your terminal and confirm that pnpx supabase status still shows Studio running.

Why can't I see the Table Editor in my local Dashboard?

Capture the change as a migration file

Your table exists in the local database, but only as live state. If you reset the database, it disappears. The db diff command compares the current database against your migration history and generates the SQL difference as a new migration file.

  • Generate a migration file from your Dashboard changes by running this command in your terminal:
pnpx supabase db diff -f create_categories

You should see a message confirming a new file was created in supabase/migrations/.

  • Open the new migration file in Windsurf. Look in the supabase/migrations/ folder for a file ending in _create_categories.sql.

The file contains the SQL that was auto-generated from your visual table creation. It should look like this:

create table if not exists public.categories (
  id bigint primary key generated always as identity,
  name text not null,
  color text default '#6B7280',
  created_at timestamptz default now()
);

What is a migration file?

A migration file is a timestamped SQL script stored in supabase/migrations/. Each file records one schema change. The timestamp prefix determines execution order.

Together, your migration files form a recipe that can rebuild your entire database from scratch. This means you can commit them to Git, share them with teammates, and reproduce the same database on any machine.

Diff command shows no changes?

This means the database state matches what the existing migrations already describe. Confirm you saved the table in the Dashboard before running the diff. Try clicking Table Editor in the sidebar and checking that categories appears.

Why does supabase db diff show no changes after I created my table?

Verify your migration works from scratch

A migration only proves its worth if it can rebuild your table from nothing. The db reset command drops the entire local database and re-runs every migration file in order.

Before you run this, what do you think will happen to the categories table? Will it come back, or disappear?

  • Reset your local database from scratch by running:
pnpx supabase db reset

The table comes back. Your migration file successfully recreated the entire categories table from scratch.

  • Switch back to http://localhost:54323 in your browser.
  • Click Table Editor in the left sidebar.
  • Confirm that categories still appears with all four columns (id, name, color, created_at).

Table missing after reset?

Open the migration file in Windsurf and check for syntax errors. Common issues include a missing comma between column definitions or a typo in timestamptz. The terminal output from db reset will show the exact SQL error if the migration failed to apply.

My table disappeared after running supabase db reset. How do I fix my migration?

✔️ Awesome, I've got everything!

Great. Double check you have exactly one migration file in supabase/migrations/ and that categories shows up in your local Dashboard after the reset.

ⓧ I'd like to double check the full code

Your migration file should contain exactly this SQL:

create table if not exists public.categories (
  id bigint primary key generated always as identity,
  name text not null,
  color text default '#6B7280',
  created_at timestamptz default now()
);

Your first table is locked in as a migration file. Next up, you'll add a transactions table with a foreign key linking each expense back to a category.

Add the Transactions Table

Your categories table is in place and captured as a migration. Now you need somewhere to store actual expenses.

Each transaction needs an amount, a description, a date, and a link back to the category it belongs to. That link is called a foreign key. It tells PostgreSQL to reject any transaction that references a category ID that does not exist, keeping your data consistent.

In this step, get ready to:
  • Create a transactions table with a foreign key linking to categories.
  • Capture the new table as a second migration file.
  • Reset the database from scratch and verify both tables work together.
Create the transactions table in the Dashboard

You already know the workflow from creating categories. This time you will add a column that references another table.

  • Switch back to the local Supabase Studio Dashboard at http://localhost:54323.
  • Click Table Editor in the left sidebar.
  • Click New Table.
  • Enter transactions as the table name.
  • Uncheck Enable Row Level Security (you will cover RLS in a future project).
  • Set up the columns as described below. The id column is created automatically as the primary key. Configure it as int8, Is Identity checked, set to Generated Always.

Add the following columns using the Add Column button:

  • Name: amount | Type: numeric | Uncheck Is Nullable.
  • Name: description | Type: text | Leave Is Nullable checked.
  • Name: category_id | Type: int8 | Leave Is Nullable checked.
  • Name: transaction_date | Type: date | Default Value: now().
  • Name: created_at | Type: timestamptz | Default Value: now().

Now add the foreign key relationship on category_id:

  • Click the link icon next to the category_id column (it looks like a small chain link).
  • Select categories as the referenced table.
  • Select id as the referenced column.
  • Click Save to create the table.

You should see transactions appear in the Table Editor sidebar alongside categories.

What is a foreign key?

A foreign key is a column that points to the primary key of another table. Here, category_id in transactions points to id in categories.

PostgreSQL uses this link to enforce referential integrity. If you try to insert a transaction with a category_id of 99 but no category with id = 99 exists, the database rejects the insert. Your data stays consistent without any application code to police it.

Capture the migration
  • Generate a migration file from your Dashboard changes by running this command:
pnpx supabase db diff -f create_transactions

The command compares your current local database against the migrations that have already been applied and outputs the difference as a new SQL file.

  • Open the newly created file in Windsurf. It lives at supabase/migrations/ with a timestamp prefix and the name create_transactions.sql.

You should see a CREATE TABLE statement for transactions including the foreign key reference to categories(id).

What does this file do?

This migration file is a complete recipe for recreating your transactions table from scratch. Anyone who runs your migrations gets the exact same schema, including the foreign key constraint. The timestamp in the filename determines the order migrations run, so categories is always created before transactions.

✔️ Awesome, I've got everything!

Great. Double check you have saved the file and that it includes the foreign key reference.

ⓧ I'd like to double check the full code

Your migration file should look like this:

create table if not exists public.transactions (
  id bigint primary key generated always as identity,
  amount numeric not null,
  description text,
  category_id bigint references public.categories(id),
  transaction_date date default now(),
  created_at timestamptz default now()
);

Diff command produced an empty file?

This means there is no difference between your database state and your existing migrations. Make sure you saved the transactions table in the Dashboard before running the diff.

If you accidentally ran pnpx supabase db reset before diffing, your Dashboard changes were wiped. Recreate the table in the Dashboard and run the diff again.

Still stuck? Why is my supabase db diff producing an empty migration file?

Test the full migration sequence from scratch

You now have two migration files. Time to prove they can recreate your entire database from zero.

  • Before you run the next command, predict what will happen. Will both tables appear, or just one?
  • Reset your local database from scratch by running:
pnpx supabase db reset

This drops everything and re-runs all migrations in timestamp order. Both categories and transactions should reappear in the Dashboard.

  • Return to the Table Editor at http://localhost:54323 and confirm both tables are listed.
  • Click the transactions table, then open its settings (gear icon or column view). Under Foreign Keys, confirm the relationship to categories.id is visible.

Now test that the foreign key actually works by inserting data.

  • Click the categories table in Table Editor.
  • Click Insert Row.
  • Enter Food for name and #EF4444 for color. Click Save.
  • Note the id value that was generated for your new category (it should be 1).
  • Switch to the transactions table and click Insert Row.
  • Enter 12.50 for amount, Lunch for description, and 1 for category_id. Click Save.

The row saves successfully because category 1 exists. The foreign key constraint is working.

What if I used a category_id that does not exist?

Try inserting a transaction with category_id set to 999. The Dashboard shows an error because no category with that ID exists. This is referential integrity in action.

Seeing an error when inserting into transactions?

Make sure you inserted a row into categories first. The foreign key requires the referenced category to exist before you can reference it.

If the error mentions a column mismatch, double check that category_id is type int8 (matching the id column in categories).

Need more help? Why is my foreign key insert failing in Supabase Table Editor?

Your expense tracker schema is taking shape. You have two tables with a proper relationship between them, and both are captured as versioned migration files. Next up, you will deploy these migrations to a live remote Supabase project so your schema is running in the cloud.

Deploy to Your Remote Supabase Project

Your expense tracker schema rebuilds perfectly from scratch on your local machine. Both migration files run in order, and supabase db reset proves they're reproducible every time.

The problem is that a local database only lives on your computer. To connect a real frontend app to your schema, you need it running in the cloud. In this step, you'll push your migration files to a live Supabase project and see your tables appear in the remote dashboard.

In this step, get ready to:
  • Create a new free Supabase project in the cloud.
  • Authenticate and link your local project to the remote one.
  • Push your migrations to the remote database and verify they arrived.
Create a new remote Supabase project

Your free Supabase account allows 2 active projects. You already have one (Gavelogy), so you have one slot remaining for this learning project.

  • Open https://supabase.com/dashboard in your browser.
  • Click New Project.
  • Enter expense-tracker-learn as the project name.
  • Choose a strong database password and save it somewhere safe (you will not need it again for this project, but it is good practice).
  • Select a region close to you from the dropdown.
  • Click Create new project.
  • Wait for the project to finish provisioning. You will see a green "Active" status when it is ready (this usually takes 1-2 minutes).

Why a separate project?

Using a dedicated learning project keeps your Gavelogy production database untouched. You can safely experiment, break things, and delete this project later without any risk.

Authenticate and link your local project

The Supabase CLI needs to know which remote project your local folder belongs to. You will log in first, then link the two together.

  • Authenticate the CLI with your Supabase account by running this command:
pnpx supabase login

What happens when you run this?

The CLI automatically opens a browser tab pointed at the Supabase dashboard. If you are already signed in, it completes the authentication instantly. If not, sign in to the dashboard first, then the CLI picks up the session automatically.

Once complete, you will see a success message in your terminal confirming the login.

Browser did not open?

If the browser tab did not open automatically, look in your terminal output for a URL. Copy and paste it into your browser manually. Make sure you are signed in to the Supabase dashboard in that browser.

Why is my Supabase CLI login not working?

  • Link your local project to the remote one by running:
pnpx supabase link

What does this command do?

The CLI presents a list of your Supabase projects. Select the expense-tracker-learn project you just created using the arrow keys and press Enter. This writes the project reference into your local config so future commands know where to deploy.

Not seeing your project in the list?

If the project does not appear, it may still be provisioning. Wait another minute and run pnpx supabase link again. The project must show "Active" status in the dashboard before it appears in the CLI list.

Help me link my local Supabase project to the remote one.

Push migrations to the remote database

Your local project is now connected to the cloud. Time to deploy your schema.

  • Before you run this, what do you think will happen? Will the remote database get both tables, just one, or fail entirely?
  • Push all migration files to the remote database by running:
pnpx supabase db push

What does this command do?

This applies all migration files in supabase/migrations/ to your remote database in timestamp order. It tracks which migrations have already been applied, so running it again later will only apply new ones.

You should see output confirming that both migrations were applied successfully.

  • Open your remote project dashboard at https://supabase.com/dashboard.
  • Navigate to Table Editor in the left sidebar.
  • Confirm that both categories and transactions tables appear in the list.

Tables not showing in the remote dashboard?

Make sure you are looking at the correct project (expense-tracker-learn, not Gavelogy). Check the project switcher in the top left of the dashboard.

If pnpx supabase db push showed an error, make sure the link step completed successfully by running pnpx supabase link again and selecting the correct project.

Why aren't my tables showing up in the remote Supabase dashboard?

Your schema is now live in the cloud. Anyone with the project URL and keys could connect a frontend to these tables right now. In the next step, you will experience what happens when you break the rules and edit the remote database directly.

Break It, Then Fix It

Your migrations are deployed and your remote database matches your local setup perfectly. But what happens when someone skips the migration workflow and edits the remote database directly?

Every developer using Supabase makes this mistake at least once. They open the cloud dashboard, add a quick column, and suddenly their next db push fails with a confusing sync error. In this step, you will deliberately cause that failure so you understand exactly why migrations must be the single source of truth.

In this step, get ready to:
  • Simulate the common mistake by editing the remote database directly through the cloud dashboard.
  • Experience the migration push failure that results from bypassing the workflow.
  • Fix the conflict and learn the golden rule of migrations.
Simulate the common mistake

You are about to do something you should never do in a real project. You will edit the remote database directly through the cloud dashboard, bypassing your migration files entirely.

  • Open your remote Supabase project dashboard at https://supabase.com/dashboard.
  • Navigate to the Table Editor in the left sidebar.
  • Click on the transactions table.
  • Click the + button to add a new column.
  • Name it notes, set the type to text, and leave it nullable.
  • Click Save.

You should see the notes column appear in your remote transactions table. Your local migration files know nothing about this change.

What just happened?

You made a schema change directly on the remote database without going through a migration file. Your local supabase/migrations/ folder has no record of this notes column. The remote database and your migration history are now out of sync.

Try the proper workflow and watch it fail

Now you will try to add the same notes column through the correct migration workflow. This is how you should always make schema changes.

  • Switch to your local Studio Dashboard at http://localhost:54323.
  • Open Table Editor and click on the transactions table.
  • Add a new column called notes (type: text, nullable).
  • Click Save.
  • Capture this change as a migration by running:
pnpx supabase db diff -f add_notes_column

You should see a new file appear in supabase/migrations/ with add_notes_column in the filename. Open it in Windsurf to confirm it contains:

alter table public.transactions
add column notes text;

What does this migration do?

This single SQL statement tells PostgreSQL to add a new notes column of type text to the transactions table. Because it uses add column without IF NOT EXISTS, it expects the column to not already be there.

Before you push this migration, think about what will happen. The remote database already has a notes column that you added manually. Do you think this push will succeed or fail?

  • Try to push the migration to your remote database by running:
pnpx supabase db push

The push fails. You will see an error indicating that the notes column already exists on the remote database.

This failure is intentional

Your migration tries to add a column called notes, but the remote database already has that column from your manual edit. PostgreSQL refuses to create a duplicate column.

This is exactly the sync error the Supabase docs warn about. Once you bypass migrations, your local history and remote state diverge.

Fix the conflict

Since this is a learning project, the simplest fix is to undo the manual change on the remote database and then push again properly.

  • Go back to your remote project dashboard at https://supabase.com/dashboard.
  • Open Table Editor and click on the transactions table.
  • Find the notes column, click the three-dot menu on its column header, and select Delete Column.
  • Confirm the deletion.

The remote database no longer has the rogue notes column. Your migration file is now the only source of truth for that change.

What do you think will happen when you push again now that the manual column is gone?

  • Push your migration again by running:
pnpx supabase db push

This time the push succeeds. Your migration applied cleanly because the remote database no longer had a conflicting column.

  • Open your remote project's Table Editor in the browser and click on transactions.
  • Confirm the notes column is there. This time it arrived through the proper migration workflow.

Push still failing?

Make sure you actually deleted the notes column from the remote dashboard before pushing again. Refresh the Table Editor page to confirm it is gone.

If you see a different error about a previously applied migration, check that you did not accidentally delete or rename any existing migration files in supabase/migrations/.

help me fix a supabase db push error where my migration won't apply to the remote database

The golden rule of migrations

Once you are using migrations, never edit the remote database directly. All changes go through this workflow:

  • Make changes in the local Dashboard.
  • Capture them with pnpx supabase db diff.
  • Deploy with pnpx supabase db push.
  • Also confirm the notes column exists on your local transactions table by checking http://localhost:54323 Table Editor.

✔️ Awesome, I've got everything!

Great. You now have three migration files in supabase/migrations/ and a fully synced remote database. Double check you have saved all your files.

ⓧ I'd like to double check the full code

Your three migration files should contain the following SQL. Scroll up and confirm each matches.

File 1: <timestamp>_create_categories.sql

create table if not exists public.categories (
  id bigint primary key generated always as identity,
  name text not null,
  color text default '#6B7280',
  created_at timestamptz default now()
);

File 2: <timestamp>_create_transactions.sql

create table if not exists public.transactions (
  id bigint primary key generated always as identity,
  amount numeric not null,
  description text,
  category_id bigint references public.categories(id),
  transaction_date date default now(),
  created_at timestamptz default now()
);

File 3: <timestamp>_add_notes_column.sql

alter table public.transactions
add column notes text;

Secret mission

Add Seed Data and a CHECK Constraint

Your expense tracker schema works, but it has no test data and no way to prevent someone from recording a negative expense. Add seed data that loads automatically and a CHECK constraint that rejects invalid amounts at the database level.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs on the Supabase Free Plan and local Docker containers, so there are no ongoing costs.

Resources you used:

  • Remote Supabase project expense-tracker-learn (free tier, auto-pauses after 1 week of inactivity).
  • Local Supabase Docker containers (using disk space, no ongoing cost).
  • Local expense-tracker project folder on your Desktop.

Keep everything running

No action needed. Choose this if you are still actively building or plan to continue with future projects in this series.

  • Leave Docker Desktop running so your local Supabase stack stays available.
  • Your remote project remains active and accessible at https://supabase.com/dashboard.
  • Your local expense-tracker folder and all migration files stay on your Desktop.

Pause - I'll come back to this later

Shut down the local Docker containers to free up system resources. Your migration files and project folder are preserved on disk.

  • Stop the local Supabase stack by running this command in your terminal from the expense-tracker folder:
pnpx supabase stop
  • Your local data is preserved. Run pnpx supabase start again later to resume where you left off.
  • The remote project will auto-pause after 1 week of inactivity. No action needed there.

Delete - I don't want to use this again

Remove all project resources from your machine and delete the remote project from Supabase.

  • Stop and remove local Docker containers and data volumes by running:
pnpx supabase stop --no-backup
  • Delete the local project folder from your Desktop:
cd ~/Desktop
rm -rf expense-tracker
  • Delete the remote Supabase project:
  • Open your project at https://supabase.com/dashboard.
  • Navigate to Settings then General.
  • Scroll down and click Delete project.
  • Enter your project name to confirm, then click Delete.

Nice Work!

Nice Work!

You just built a professional database migrations workflow from scratch. Your expense tracker schema is version-controlled, reproducible, and deployed to a live Supabase project.

You've learned how to:

  • Design database tables visually in the Supabase Dashboard and capture every change as a versioned migration file using supabase db diff.
  • Deploy your local migrations to a live remote project with supabase db push and confirm your tables appear in the cloud.
  • Experience the exact failure that happens when you bypass migrations and edit a remote database directly, and fix it by keeping migration files as the single source of truth.
  • Secret Mission: Write raw SQL for seed data and a CHECK constraint, enforcing business rules at the database level.

Ready to quiz yourself?