Switch to Supabase
How to change the database provider to Supabase.
Supabase is an open source Firebase alternative providing a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, and Storage.
next-forge
uses Neon as the database provider with Prisma as the ORM as well as Clerk for authentication. This guide will provide the steps you need to switch the database provider from Neon to Supabase. This guide is based on a few existing resources, including Supabase's guide and Prisma's guide.
For authentication, see the Supabase Auth migration guide to switch from Clerk to Supabase Auth with organization management, user roles, and more.
Here's how to switch from Neon to Supabase for your next-forge
project.
1. Sign up to Supabase
Create a free account at supabase.com. You can manage your projects through the Dashboard or use the Supabase CLI.
We'll be using both the Dashboard and CLI throughout this guide.
2. Create a Project
Create a new project from the Supabase Dashboard. Give it a name and choose your preferred region. Once created, you'll get access to your project's connection details. Head to the Settings page, then click on Database.
We'll need to keep track of the following for the next step:
- The Database URL in
Transaction
mode, with the port ending in6543
. We'll call thisDATABASE_URL
. - The Database URL in
Session
mode, with the port ending in5432
. We'll call thisDIRECT_URL
.
3. Update the environment variables
Update the .env
file with the Supabase connection details. Make sure you add ?pgbouncer=true&connection_limit=1
to the end of the DATABASE_URL
value.
DATABASE_URL="postgres://postgres:postgres@127.0.0.1:54322/postgres?pgbouncer=true&connection_limit=1"
DIRECT_URL="postgres://postgres:postgres@127.0.0.1:54322/postgres"
pgbouncer=true
disables Prisma from generating prepared statements. This is required since our connection pooler does not support prepared statements in transaction mode yet. The connection_limit=1
parameter is only required if you are using Prisma from a serverless environment.
4. Replace the dependencies
Prisma doesn't have a Supabase adapter yet, so we just need to remove the Neon adapter and connect to Supabase directly.
First, remove the Neon dependencies from the project...
pnpm remove @neondatabase/serverless @prisma/adapter-neon ws @types/ws --filter @repo/database
... and add the Supabase dependencies:
pnpm install -D supabase --filter @repo/database
5. Update the database package
Update the database
package. We'll remove the Neon extensions and connect to Supabase directly, which should automatically use the environment variables we set earlier.
import 'server-only';
import { PrismaClient } from '@prisma/client';
export const database = new PrismaClient();
export * from '@prisma/client';
6. Update the Prisma schema
Update the prisma/schema.prisma
file so it contains the DIRECT_URL
. This allows us to use the Prisma CLI to perform other actions on our database (e.g. migrations) by bypassing Supavisor.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
relationMode = "prisma"
}
Now you can run the migration from the root of your next-forge
project:
pnpm run migrate
7. Set up Row Level Security (RLS)
Row Level Security (RLS) is a powerful PostgreSQL feature that allows you to control access to database rows based on the authenticated user. This is essential for multi-tenant applications where users should only see their own data.
RLS policies use auth.uid()
to get the authenticated user's ID from Supabase Auth. Make sure you've completed the Supabase Auth migration first.
Enable RLS on your tables
First, enable RLS on the tables you want to protect. You can do this via the Supabase dashboard or by running SQL migrations:
-- Enable RLS on organizations table
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
-- Enable RLS on organization_members table
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;
-- Enable RLS on any other tables that need protection
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
Create RLS policies
Once RLS is enabled, create policies that define who can access what data:
Organization policies
-- Policy: Users can only view organizations they're members of
CREATE POLICY "Users can view their organizations"
ON organizations FOR SELECT
USING (
id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid()
)
);
-- Policy: Only organization owners can update organizations
CREATE POLICY "Owners can update their organizations"
ON organizations FOR UPDATE
USING (
id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid() AND role = 'owner'
)
);
-- Policy: Only organization owners can delete organizations
CREATE POLICY "Owners can delete their organizations"
ON organizations FOR DELETE
USING (
id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid() AND role = 'owner'
)
);
-- Policy: Any authenticated user can create an organization
CREATE POLICY "Authenticated users can create organizations"
ON organizations FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
Organization member policies
-- Policy: Users can view members of their organizations
CREATE POLICY "Users can view organization members"
ON organization_members FOR SELECT
USING (
organization_id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid()
)
);
-- Policy: Owners and admins can add members
CREATE POLICY "Owners and admins can add members"
ON organization_members FOR INSERT
WITH CHECK (
organization_id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')
)
);
-- Policy: Owners and admins can update member roles
CREATE POLICY "Owners and admins can update members"
ON organization_members FOR UPDATE
USING (
organization_id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')
)
);
-- Policy: Owners and admins can remove members
CREATE POLICY "Owners and admins can remove members"
ON organization_members FOR DELETE
USING (
organization_id IN (
SELECT organization_id
FROM organization_members
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')
)
);
Testing RLS policies
You can test your RLS policies using the Supabase SQL Editor with the following pattern:
-- Set the user ID for testing
SELECT auth.uid(); -- This will be NULL initially
-- To test as a specific user, you would typically:
-- 1. Make requests through your application with that user's session
-- 2. Or use Supabase's testing tools in the dashboard
-- Check what organizations a user can see
SELECT * FROM organizations;
-- This query will automatically be filtered by your RLS policies
Common RLS patterns
User-owned data
For data that belongs directly to a user (like user profiles or settings):
CREATE POLICY "Users can only access their own data"
ON user_data FOR ALL
USING (user_id = auth.uid());
Tenant isolation
For multi-tenant data where access is determined by an organization or tenant ID:
CREATE POLICY "Users can only access their tenant's data"
ON tenant_data FOR ALL
USING (
tenant_id IN (
SELECT tenant_id FROM user_tenants WHERE user_id = auth.uid()
)
);
Public read, authenticated write
For data that everyone can read but only authenticated users can modify:
-- Read policy (no authentication required)
CREATE POLICY "Anyone can read"
ON public_data FOR SELECT
USING (true);
-- Write policy (authentication required)
CREATE POLICY "Authenticated users can write"
ON public_data FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
When RLS is enabled on a table, all access is denied by default. You must create policies to allow access. Make sure to test thoroughly to avoid accidentally blocking legitimate access.
For more information, see the Supabase Row Level Security guide.