2026-03-173 phút đọcVI
- 0.Chuỗi bài: Supabase từ setup đến deploy
- 1.Tổng quan Supabase
- 2.Project setup, CLI và biến môi trường
- 3.Thiết kế schema Postgres và bảng
- 4.Migrations: viết và áp dụng
- 5.Auth: JWT, session và tích hợp backend
- 6.Prisma + Supabase: kết nối và đồng bộ schema(bài này)
- 7.Row Level Security (RLS) và policy
- 8.API: PostgREST, custom API và khi nào dùng gì
- 9.Realtime và Edge Functions
- 10.Deploy, CI/CD và multi-project
Prisma + Supabase: connect and sync schema
Introduction
Many teams use Prisma as the ORM for Node/TypeScript. Supabase provides Postgres; Prisma connects to Postgres via a connection string. This post covers configuring the Prisma datasource for Supabase, using introspect (DB → schema) vs migrate (schema → DB), and avoiding conflicts with Supabase migrations.
Goal: Connect Prisma to Supabase safely and be clear about “who is the source of truth” for schema (Supabase migrations vs Prisma migrate).
Features
Prisma and Supabase
- Prisma: ORM with schema in
schema.prisma; generates a TypeScript/JavaScript client for queries. It can create migrations (Prisma Migrate) or only introspect from an existing DB. - Supabase: Postgres + migrations in
supabase/migrations/. PostgREST and RLS depend on the actual DB schema. - Connection: Prisma datasource uses the Postgres connection string (from Supabase Dashboard → Settings → Database). Use the connection pooler (port 6543) for serverless; direct (5432) for long-running processes if needed.
Introspect vs migrate
- Introspect: DB already exists (schema from Supabase migrations) →
prisma db pullgeneratesschema.prismafrom the DB. Prisma does not create migrations; source of truth remains Supabase migrations. - Migrate: Prisma creates migrations from changes in
schema.prismaand applies them to the DB. With Migrate, Prisma is the source of truth; conflicts are likely if Supabase migrations also change the same tables. Recommendation: Pick one: either Supabase migrations (Prisma introspect only), or Prisma Migrate (no Supabase migrations for that DB).
Workflow / process
- Get connection string: Supabase Dashboard → Settings → Database → Connection string (URI). Use pooler (Transaction mode) for serverless; replace password with an env variable.
- Configure Prisma:
datasource db { provider = "postgresql", url = env("DATABASE_URL") }. Do not commit DATABASE_URL (it contains the password). - If schema already exists (Supabase migrations): Run
prisma db pull→ adjust schema.prisma if needed (e.g. remove system tables) →prisma generate. Do not useprisma migrate deployon the same DB. - If starting fresh and choosing Prisma Migrate: Define models in schema.prisma →
prisma migrate dev→ for deploy useprisma migrate deploy. Leave Supabase migrations empty or don’t use them for this DB. - Generate client: After any schema change (pull or migrate), run
prisma generateto use the client in code.
Convention: One source of truth for schema; avoid changing the same database with both Supabase migrations and Prisma Migrate.
Sample code
schema.prisma (after introspect from Supabase)
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL") // optional: direct 5432 for migrate
}
model conversations {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
user_id String @map("user_id") @db.Uuid
title String? @db.Text
created_at DateTime @default(now())
updated_at DateTime @default(now())
messages messages[]
@@map("conversations")
}
model messages {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
conversation_id String @map("conversation_id") @db.Uuid
role String @db.Text
content String @db.Text
created_at DateTime @default(now())
conversations conversations @relation(fields: [conversation_id], references: [id], onDelete: Cascade)
@@map("messages")
}.env (do not commit)
# Pooler (port 6543) for serverless / many connections
DATABASE_URL="postgresql://postgres.[ref]:[PASSWORD]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true"
# Direct for migrate (if using Prisma Migrate)
DIRECT_URL="postgresql://postgres.[ref]:[PASSWORD]@db.[ref].supabase.co:5432/postgres"Query with Prisma (backend)
const conversations = await prisma.conversations.findMany({
where: { user_id: userId },
orderBy: { updated_at: 'desc' },
take: 10,
});Apply in your architecture
- One source of truth for schema: Either Supabase migrations (Prisma only introspects), or Prisma Migrate; avoid two sources changing the same DB and causing schema drift and rollback pain.
- Clear migration ownership: Decide explicitly “who owns schema”; Supabase + RLS + PostgREST usually go with “Supabase migrations are source of truth”, Prisma only reads/writes via the client.
- Secrets in connection string: DATABASE_URL contains the password; server env only, never commit. Use pooler URL for serverless to stay within connection limits.
Summary
Prisma connects to Supabase via the Postgres URL; prefer introspect from an existing DB (Supabase migrations) and don’t run Prisma Migrate in parallel. Next: Row Level Security (RLS) and policies.
Next: 07 — Row Level Security (RLS)
Further reading: Prisma — Connect to Supabase