Initial commit
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
# Supabase Integration with Prisma
|
||||
|
||||
## Connection Configuration
|
||||
|
||||
### Database URLs
|
||||
|
||||
Supabase provides two types of connection strings:
|
||||
|
||||
**Direct Connection (Port 5432)** - For migrations:
|
||||
```env
|
||||
DATABASE_URL="postgresql://postgres:[PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres"
|
||||
```
|
||||
|
||||
**Pooled Connection (Port 6543)** - For queries via pgBouncer:
|
||||
```env
|
||||
DIRECT_URL="postgresql://postgres:[PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true"
|
||||
```
|
||||
|
||||
**Schema configuration:**
|
||||
```prisma
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DIRECT_URL") // Pooled for app queries
|
||||
directUrl = env("DATABASE_URL") // Direct for migrations
|
||||
}
|
||||
```
|
||||
|
||||
### Why Two Connections?
|
||||
|
||||
- **Direct (5432)**: Supports all PostgreSQL features, required for migrations
|
||||
- **Pooled (6543)**: Better performance, connection pooling, but limited features
|
||||
- Prisma uses direct for migrations, pooled for queries
|
||||
|
||||
## Row Level Security (RLS) Considerations
|
||||
|
||||
### Prisma vs Supabase Auth
|
||||
|
||||
**Key concept**: Prisma connects as the `postgres` user, which **bypasses RLS policies**.
|
||||
|
||||
This means:
|
||||
- Prisma can read/write all data regardless of RLS
|
||||
- Use Prisma in Server Components/Actions where you control access
|
||||
- RLS still protects data accessed via Supabase client
|
||||
|
||||
### Using Prisma with RLS
|
||||
|
||||
For RLS to work with Prisma, you need to set the user context:
|
||||
|
||||
```typescript
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { getCurrentUser } from '@/lib/auth/utils';
|
||||
|
||||
export async function getUserPosts() {
|
||||
const user = await getCurrentUser();
|
||||
|
||||
// Set RLS context (requires custom configuration)
|
||||
await prisma.$executeRaw`SET LOCAL rls.user_id = ${user.id}`;
|
||||
|
||||
// Now RLS policies can use current_setting('rls.user_id')
|
||||
const posts = await prisma.post.findMany();
|
||||
|
||||
return posts;
|
||||
}
|
||||
```
|
||||
|
||||
**Better approach**: Use Prisma for admin operations, Supabase client for user operations:
|
||||
|
||||
```typescript
|
||||
// Admin operation - bypasses RLS
|
||||
import { prisma } from '@/lib/prisma';
|
||||
await prisma.post.findMany(); // Gets all posts
|
||||
|
||||
// User operation - respects RLS
|
||||
import { createServerClient } from '@/lib/supabase/server';
|
||||
const supabase = await createServerClient();
|
||||
const { data } = await supabase.from('posts').select('*'); // Only user's posts
|
||||
```
|
||||
|
||||
## Schema Management
|
||||
|
||||
### Prisma Manages Schema
|
||||
|
||||
Use Prisma as source of truth for schema:
|
||||
|
||||
```bash
|
||||
# 1. Update schema.prisma
|
||||
# 2. Generate migration
|
||||
npx prisma migrate dev --name add_posts
|
||||
|
||||
# 3. Migration is applied to Supabase database
|
||||
```
|
||||
|
||||
### Supabase Features
|
||||
|
||||
Some Supabase features are managed outside Prisma:
|
||||
|
||||
**RLS Policies** - Define in Supabase dashboard or SQL:
|
||||
```sql
|
||||
CREATE POLICY "Users can view own posts"
|
||||
ON posts FOR SELECT
|
||||
USING (auth.uid() = author_id);
|
||||
```
|
||||
|
||||
**Storage Buckets** - Use Supabase dashboard/API
|
||||
|
||||
**Realtime** - Configure in Supabase dashboard
|
||||
|
||||
**Edge Functions** - Deploy separately
|
||||
|
||||
### Hybrid Approach
|
||||
|
||||
1. Use Prisma for schema, migrations, and admin operations
|
||||
2. Use Supabase client for user-facing operations with RLS
|
||||
3. Define RLS policies separately from Prisma
|
||||
|
||||
## Working with Supabase Auth
|
||||
|
||||
### Linking to auth.users
|
||||
|
||||
Don't create foreign key to `auth.users` (different schema):
|
||||
|
||||
```prisma
|
||||
model Profile {
|
||||
id String @id @db.Uuid // Same as auth.users.id
|
||||
email String @unique
|
||||
|
||||
// No foreign key to auth.users - different schema
|
||||
}
|
||||
```
|
||||
|
||||
**Create profile on signup** via database trigger:
|
||||
|
||||
```sql
|
||||
-- Create profile when user signs up
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.profiles (id, email)
|
||||
VALUES (NEW.id, NEW.email);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.handle_new_user();
|
||||
```
|
||||
|
||||
### Syncing Profile Data
|
||||
|
||||
Keep profiles in sync with auth.users:
|
||||
|
||||
```sql
|
||||
-- Update profile when email changes
|
||||
CREATE OR REPLACE FUNCTION public.handle_user_update()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
UPDATE public.profiles
|
||||
SET email = NEW.email
|
||||
WHERE id = NEW.id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE TRIGGER on_auth_user_updated
|
||||
AFTER UPDATE ON auth.users
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.email IS DISTINCT FROM NEW.email)
|
||||
EXECUTE FUNCTION public.handle_user_update();
|
||||
```
|
||||
|
||||
## Migrations in Supabase
|
||||
|
||||
### Local Development
|
||||
|
||||
1. Pull current schema from Supabase:
|
||||
```bash
|
||||
npx prisma db pull
|
||||
```
|
||||
|
||||
2. Make changes to schema.prisma
|
||||
|
||||
3. Create migration:
|
||||
```bash
|
||||
npx prisma migrate dev --name my_changes
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
Apply migrations during deployment:
|
||||
|
||||
```bash
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
**GitHub Actions example:**
|
||||
```yaml
|
||||
- name: Run Prisma migrations
|
||||
run: npx prisma migrate deploy
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.SUPABASE_DATABASE_URL }}
|
||||
```
|
||||
|
||||
### Migration History
|
||||
|
||||
Prisma creates `_prisma_migrations` table to track applied migrations. Don't modify this table.
|
||||
|
||||
## Realtime with Prisma
|
||||
|
||||
Supabase Realtime works with Prisma-managed tables:
|
||||
|
||||
1. Create table via Prisma migration
|
||||
2. Enable realtime in Supabase dashboard for specific tables
|
||||
3. Subscribe to changes via Supabase client
|
||||
|
||||
```typescript
|
||||
// Enable realtime on a table (in SQL editor or dashboard)
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE posts;
|
||||
|
||||
// Subscribe to changes
|
||||
const supabase = createClient();
|
||||
const channel = supabase
|
||||
.channel('posts')
|
||||
.on('postgres_changes', { event: '*', schema: 'public', table: 'posts' },
|
||||
(payload) => {
|
||||
console.log('Change received!', payload);
|
||||
}
|
||||
)
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### Development
|
||||
|
||||
```env
|
||||
# .env.local
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:54322/postgres"
|
||||
DIRECT_URL="postgresql://postgres:postgres@localhost:54322/postgres"
|
||||
```
|
||||
|
||||
### Staging/Production
|
||||
|
||||
```env
|
||||
# .env.production
|
||||
DATABASE_URL="postgresql://postgres:[PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres"
|
||||
DIRECT_URL="postgresql://postgres:[PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Prisma for schema management** - Single source of truth
|
||||
2. **Use Supabase client for RLS-protected queries** - User-facing operations
|
||||
3. **Use Prisma for admin operations** - Bulk updates, analytics
|
||||
4. **Define RLS policies separately** - Not managed by Prisma
|
||||
5. **Use triggers for auth.users integration** - Auto-create profiles
|
||||
6. **Enable realtime selectively** - Only on tables that need it
|
||||
7. **Test migrations locally first** - Use Supabase CLI for local dev
|
||||
8. **Monitor connection pool** - Use pooled connection for queries
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Migration fails with permission error**: Ensure DATABASE_URL uses postgres user with sufficient privileges.
|
||||
|
||||
**RLS blocks Prisma queries**: This is expected. Use Supabase client for RLS-protected data.
|
||||
|
||||
**Connection pool exhausted**: Use pooled connection (DIRECT_URL) for application queries.
|
||||
|
||||
**Realtime not working**: Check table is published (`ALTER PUBLICATION supabase_realtime ADD TABLE tablename`).
|
||||
|
||||
**Auth user ID doesn't match profile**: Ensure trigger exists and is executed on user creation.
|
||||
|
||||
## Supabase CLI Integration
|
||||
|
||||
Use Supabase CLI for local development:
|
||||
|
||||
```bash
|
||||
# Start local Supabase
|
||||
npx supabase start
|
||||
|
||||
# Link to remote project
|
||||
npx supabase link --project-ref your-project-ref
|
||||
|
||||
# Pull remote schema
|
||||
npx supabase db pull
|
||||
|
||||
# Generate types for Supabase client
|
||||
npx supabase gen types typescript --local > types/database.ts
|
||||
```
|
||||
|
||||
Prisma and Supabase CLI can coexist:
|
||||
- Prisma for schema management and migrations
|
||||
- Supabase CLI for local development and type generation
|
||||
Reference in New Issue
Block a user