Files
myeasycms-v2/docs/development/migrations.mdoc
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

310 lines
9.6 KiB
Plaintext

---
status: "published"
label: "Migrations"
order: 1
title: "Database Migrations in the Next.js Supabase Starter Kit"
description: "Create and manage database migrations using Supabase's declarative schema and diffing tools to evolve your PostgreSQL schema safely."
---
Database migrations in Makerkit use Supabase's declarative schema approach. Define your schema in SQL files at `apps/web/supabase/schemas/`, then generate migration files that track changes over time. This keeps your schema version-controlled and deployable across environments.
{% sequence title="Database Migration Workflow" description="Create and apply schema changes safely" %}
[Edit the declarative schema](#editing-the-declarative-schema)
[Generate a migration file](#generating-a-migration-file)
[Test locally](#testing-locally)
[Push to production](#pushing-to-production)
{% /sequence %}
## Why Declarative Schema?
Makerkit uses declarative schema files instead of incremental migrations for several reasons:
- **Readable**: See your entire schema in one place
- **Mergeable**: Schema changes are easier to review in PRs
- **Recoverable**: Always know the intended state of your database
- **Automated**: Supabase generates migration diffs for you
{% alert type="warning" title="Avoid Supabase Studio for Schema Changes" %}
Don't use the hosted Supabase Studio to modify your schema. Changes made there won't be tracked in your codebase. Use your local Supabase instance and generate migrations from schema files.
{% /alert %}
## Schema File Organization
Schema files live in `apps/web/supabase/schemas/`:
```
apps/web/supabase/
├── config.toml # Supabase configuration
├── seed.sql # Seed data for development
├── schemas/ # Declarative schema files
│ ├── 00-extensions.sql
│ ├── 01-enums.sql
│ ├── 02-accounts.sql
│ ├── 03-roles.sql
│ ├── 04-memberships.sql
│ ├── 05-subscriptions.sql
│ └── your-feature.sql # Your custom schema
└── migrations/ # Generated migration files
├── 20240101000000_initial.sql
└── 20240115000000_add_projects.sql
```
Files are loaded alphabetically, so prefix with numbers to control order.
## Editing the Declarative Schema
### Adding a New Table
Create a schema file for your feature:
```sql {% title="apps/web/supabase/schemas/20-projects.sql" %}
-- Projects table for team workspaces
create table if not exists public.projects (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references public.accounts(id) on delete cascade,
name text not null,
description text,
status text not null default 'active' check (status in ('active', 'archived')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- Enable RLS
alter table public.projects enable row level security;
-- RLS policies
create policy "Users can view their account's projects"
on public.projects
for select
using (
account_id in (
select account_id from public.accounts_memberships
where user_id = auth.uid()
)
);
create policy "Users with write permission can insert projects"
on public.projects
for insert
with check (
public.has_permission(auth.uid(), account_id, 'projects.write'::app_permissions)
);
-- Updated at trigger
create trigger set_projects_updated_at
before update on public.projects
for each row execute function public.set_updated_at();
```
### Modifying an Existing Table
Edit the schema file directly. For example, to add a column:
```sql {% title="apps/web/supabase/schemas/20-projects.sql" %}
create table if not exists public.projects (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references public.accounts(id) on delete cascade,
name text not null,
description text,
status text not null default 'active' check (status in ('active', 'archived')),
priority integer not null default 0, -- New column
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
```
### Adding Indexes
Add indexes for frequently queried columns:
```sql
-- Add to your schema file
create index if not exists projects_account_id_idx
on public.projects(account_id);
create index if not exists projects_status_idx
on public.projects(status)
where status = 'active';
```
## Generating a Migration File
After editing schema files, generate a migration that captures the diff:
```bash
# Generate migration from schema changes
pnpm --filter web supabase:db:diff -f add_projects
```
This creates a timestamped migration file in `apps/web/supabase/migrations/`:
```sql {% title="apps/web/supabase/migrations/20260119000000_add_projects.sql" %}
-- Generated by Supabase CLI
create table public.projects (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references public.accounts(id) on delete cascade,
name text not null,
description text,
status text not null default 'active' check (status in ('active', 'archived')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
alter table public.projects enable row level security;
-- ... policies and triggers
```
{% alert type="error" title="Always Review Generated Migrations" %}
The diffing tool has [known caveats](https://supabase.com/docs/guides/local-development/declarative-database-schemas#known-caveats). Always review generated migrations before applying them. Check for:
- Destructive operations (DROP statements)
- Missing or incorrect constraints
- Order of operations issues
{% /alert %}
## Testing Locally
Apply and test your migration locally before pushing to production:
```bash
# Stop Supabase if running
pnpm run supabase:web:stop
# Start with fresh database
pnpm run supabase:web:start
# Or reset to apply all migrations
pnpm run supabase:web:reset
```
### Verify the Schema
Check that your changes applied correctly:
```bash
# Open local Supabase Studio
open http://localhost:54323
```
Navigate to **Table Editor** and verify your table exists with the correct columns.
### Run Database Tests
If you have pgTAP tests, run them to verify RLS policies:
```bash
pnpm --filter web supabase:test
```
See [Database Tests](/docs/next-supabase-turbo/development/database-tests) for writing tests.
## Pushing to Production
After testing locally, push migrations to your remote Supabase instance:
```bash
# Link to your Supabase project (first time only)
pnpm --filter web supabase link --project-ref your-project-ref
# Push migrations
pnpm --filter web supabase db push
```
### Migration Commands Reference
| Command | Description |
|---------|-------------|
| `pnpm run supabase:web:start` | Start local Supabase |
| `pnpm run supabase:web:stop` | Stop local Supabase |
| `pnpm run supabase:web:reset` | Reset and apply all migrations |
| `pnpm --filter web supabase:db:diff -f <name>` | Generate migration from schema diff |
| `pnpm --filter web supabase db push` | Push migrations to remote |
| `pnpm --filter web supabase:typegen` | Regenerate TypeScript types |
## Regenerating TypeScript Types
After schema changes, regenerate the TypeScript types:
```bash
pnpm --filter web supabase:typegen
```
This updates `packages/supabase/src/database.types.ts` with your new tables and columns. Import types in your code:
```tsx
import type { Database } from '@kit/supabase/database';
type Project = Database['public']['Tables']['projects']['Row'];
type NewProject = Database['public']['Tables']['projects']['Insert'];
```
## Common Patterns
### Adding a Lookup Table
```sql
-- Status enum as lookup table
create table if not exists public.project_statuses (
id text primary key,
label text not null,
sort_order integer not null default 0
);
insert into public.project_statuses (id, label, sort_order) values
('active', 'Active', 1),
('archived', 'Archived', 2),
('deleted', 'Deleted', 3)
on conflict (id) do nothing;
```
### Adding a Junction Table
```sql
-- Many-to-many relationship
create table if not exists public.project_members (
project_id uuid not null references public.projects(id) on delete cascade,
user_id uuid not null references auth.users(id) on delete cascade,
role text not null default 'member',
created_at timestamptz not null default now(),
primary key (project_id, user_id)
);
```
### Data Migration
For data transformations, use a separate migration:
```sql {% title="apps/web/supabase/migrations/20260120000000_backfill_priority.sql" %}
-- Backfill priority based on status
update public.projects
set priority = case
when status = 'active' then 1
when status = 'archived' then 0
else 0
end
where priority is null;
```
## Troubleshooting
**Diff shows no changes**: Ensure your schema file is being loaded. Check file naming (alphabetical order matters).
**Migration fails on production**: The diff tool may generate invalid SQL. Review and manually fix the migration file.
**Type mismatch after migration**: Regenerate types with `pnpm --filter web supabase:typegen`.
**RLS policy errors**: Check that your policies reference valid columns and functions. Test with [database tests](/docs/next-supabase-turbo/development/database-tests).
## Related Resources
- [Database Schema](/docs/next-supabase-turbo/development/database-schema) for detailed schema patterns
- [Database Architecture](/docs/next-supabase-turbo/development/database-architecture) for understanding the data model
- [Database Functions](/docs/next-supabase-turbo/development/database-functions) for built-in SQL functions
- [Database Tests](/docs/next-supabase-turbo/development/database-tests) for testing migrations