Loading...
Loading...
> Type-safe database access with Prisma ORM, including models, queries, migrations, and best practices for PostgreSQL.
Fabrk uses Prisma ORM with PostgreSQL for type-safe database operations. The system includes pre-built models for users, organizations, payments, and more. It provides type-safe queries with full TypeScript support, migration management for schema changes, seeding scripts for development data, and connection pooling for production.
DESC: Add your PostgreSQL connection string to .env.local
1$DATABASE_URL="postgresql://user:password@localhost:5432/fabrk?schema=public"23$# For production with connection pooling (e.g., Supabase):4$DATABASE_URL="postgresql://user:password@host:6543/postgres?pgbouncer=true"5$DIRECT_URL="postgresql://user:password@host:5432/postgres"
DESC: Push the schema to your database
1$# Push schema changes (development)2$npm run db:push34$# Generate Prisma Client5$npx prisma generate67$# Seed with test data8$npm run db:seed910$# Reset and reseed11$npm run db:reset
DESC: Browse and edit data with the visual GUI
1$npm run db:studio2$# Opens at http://localhost:5555
Use the singleton client from src/lib/db/index.ts
1import { prisma } from "@/lib/db";23// Find user by email4const user = await prisma.user.findUnique({5 where: { email: "user@example.com" },6 include: { organizations: true },7});89// Create with relations10const newUser = await prisma.user.create({11 data: {12 email: "new@example.com",13 name: "New User",14 organizations: {15 create: {16 organization: {17 create: {18 name: "My Team",19 slug: "my-team",20 },21 },22 role: "OWNER",23 },24 },25 },26});
Use Prisma in API routes
1// src/app/api/v1/users/route.ts2import { NextResponse } from "next/server";3import { prisma } from "@/lib/db";4import { auth } from "@/lib/auth";56export async function GET() {7 const session = await auth();8 if (!session?.user) {9 return NextResponse.json({ error: "Unauthorized" }, { status: 401 });10 }1112 const user = await prisma.user.findUnique({13 where: { id: session.user.id },14 select: {15 id: true,16 name: true,17 email: true,18 image: true,19 createdAt: true,20 },21 });2223 return NextResponse.json(user);24}
Use transactions for atomic operations
1// Transfer ownership atomically2const result = await prisma.$transaction(async (tx) => {3 // Remove current owner4 await tx.organizationMember.update({5 where: { id: currentOwnerId },6 data: { role: "MEMBER" },7 });89 // Set new owner10 const newOwner = await tx.organizationMember.update({11 where: { id: newOwnerId },12 data: { role: "OWNER" },13 });1415 // Log the change16 await tx.auditLog.create({17 data: {18 action: "OWNERSHIP TRANSFERRED",19 organizationId: orgId,20 userId: session.user.id,21 },22 });2324 return newOwner;25});
Implement cursor-based pagination
1const pageSize = 20;23const users = await prisma.user.findMany({4 take: pageSize + 1, // Fetch one extra to check for next page5 cursor: cursor ? { id: cursor } : undefined,6 skip: cursor ? 1 : 0, // Skip the cursor itself7 orderBy: { createdAt: "desc" },8});910const hasNextPage = users.length > pageSize;11const data = hasNextPage ? users.slice(0, -1) : users;12const nextCursor = hasNextPage ? data[data.length - 1].id : null;1314return { data, nextCursor, hasNextPage };
PROTECT YOUR DATABASE CREDENTIALS
CRITICAL: The DATABASE_URL contains your database password and grants full access to your data. Leaking it allows attackers to read, modify, or delete all data.
Tip: Use separate databases for development, staging, and production. Never connect to production from local dev environment.
Fabrk includes these pre-built models in prisma/schema.prisma:
1// Authentication2model User {3 id String @id @default(cuid())4 name String?5 email String @unique6 emailVerified DateTime?7 password String?8 image String?9 role Role @default(USER)10 sessionVersion Int @default(0)11 createdAt DateTime @default(now())12 updatedAt DateTime @updatedAt1314 accounts Account[]15 sessions Session[]16 payments Payment[]17 organizations OrganizationMember[]18}1920// Payments21model Payment {22 id String @id @default(cuid())23 userId String24 stripeSessionId String @unique25 stripePriceId String26 amount Int27 currency String28 status String29 createdAt DateTime @default(now())3031 user User @relation(fields: [userId], references: [id])32}3334// Multi-tenancy35model Organization {36 id String @id @default(cuid())37 name String38 slug String @unique39 createdAt DateTime @default(now())40 updatedAt DateTime @updatedAt4142 members OrganizationMember[]43 invites OrganizationInvite[]44}4546model OrganizationMember {47 id String @id @default(cuid())48 userId String49 organizationId String50 role OrgRole @default(MEMBER)51 joinedAt DateTime @default(now())5253 user User @relation(fields: [userId], references: [id])54 organization Organization @relation(fields: [organizationId], references: [id])5556 @@unique([userId, organizationId])57}
For production, use migrations instead of db:push:
1$# Create a new migration2$npm run db:migrate -- --name add_user_preferences34$# Apply migrations in production5$npx prisma migrate deploy67$# Check migration status8$npx prisma migrate status
Update user data with validation. Use select to return only needed fields and avoid exposing sensitive data like passwords.
Add deletedAt DateTime? field to models and filter with where: { deletedAt: null } for recoverable deletes.
Use Prisma's full-text search with PostgreSQL: where: { name: { search: "query" } } for searching user names or content.
Calculate totals with prisma.payment.aggregate({ _sum: { amount: true } }) for dashboards and reports.
@/lib/dbselect to fetch only needed fieldspassword in API responsesdb:push only in development