Prisma Setup

Complete PostgreSQL database setup with Prisma ORM.

Dependencies#

npm install prisma @prisma/client npm install -D prisma

Environment Variables#

# .env DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public" # For Neon (serverless) DATABASE_URL="postgresql://user:password@ep-xxx.us-east-1.aws.neon.tech/mydb?sslmode=require"

Initialize Prisma#

npx prisma init

Schema Definition#

1// prisma/schema.prisma 2generator client { 3 provider = "prisma-client-js" 4} 5 6datasource db { 7 provider = "postgresql" 8 url = env("DATABASE_URL") 9 directUrl = env("DIRECT_URL") // For migrations with connection pooling 10} 11 12// User model 13model User { 14 id String @id @default(cuid()) 15 clerkId String @unique 16 email String @unique 17 name String? 18 imageUrl String? 19 role Role @default(USER) 20 21 // Relations 22 posts Post[] 23 comments Comment[] 24 25 // Timestamps 26 createdAt DateTime @default(now()) 27 updatedAt DateTime @updatedAt 28 29 @@index([email]) 30} 31 32enum Role { 33 USER 34 ADMIN 35 MODERATOR 36} 37 38// Post model 39model Post { 40 id String @id @default(cuid()) 41 title String 42 slug String @unique 43 content String? 44 published Boolean @default(false) 45 46 // Relations 47 authorId String 48 author User @relation(fields: [authorId], references: [id], onDelete: Cascade) 49 comments Comment[] 50 tags Tag[] 51 52 // Timestamps 53 publishedAt DateTime? 54 createdAt DateTime @default(now()) 55 updatedAt DateTime @updatedAt 56 57 @@index([authorId]) 58 @@index([slug]) 59 @@index([published, createdAt]) 60} 61 62// Comment model 63model Comment { 64 id String @id @default(cuid()) 65 content String 66 67 // Relations 68 postId String 69 post Post @relation(fields: [postId], references: [id], onDelete: Cascade) 70 authorId String 71 author User @relation(fields: [authorId], references: [id], onDelete: Cascade) 72 73 // Self-relation for replies 74 parentId String? 75 parent Comment? @relation("CommentReplies", fields: [parentId], references: [id]) 76 replies Comment[] @relation("CommentReplies") 77 78 createdAt DateTime @default(now()) 79 updatedAt DateTime @updatedAt 80 81 @@index([postId]) 82 @@index([authorId]) 83} 84 85// Tag model (many-to-many) 86model Tag { 87 id String @id @default(cuid()) 88 name String @unique 89 slug String @unique 90 posts Post[] 91 92 @@index([slug]) 93}

Prisma Client Setup#

1// lib/prisma.ts 2import { PrismaClient } from '@prisma/client'; 3 4const globalForPrisma = globalThis as unknown as { 5 prisma: PrismaClient | undefined; 6}; 7 8export const prisma = 9 globalForPrisma.prisma ?? 10 new PrismaClient({ 11 log: process.env.NODE_ENV === 'development' 12 ? ['query', 'error', 'warn'] 13 : ['error'], 14 }); 15 16if (process.env.NODE_ENV !== 'production') { 17 globalForPrisma.prisma = prisma; 18}

Generate and Push#

1# Generate Prisma Client 2npx prisma generate 3 4# Push schema to database (development) 5npx prisma db push 6 7# Create migration (production) 8npx prisma migrate dev --name init 9 10# Apply migrations in production 11npx prisma migrate deploy

Common Queries#

Create#

1// Create user 2const user = await prisma.user.create({ 3 data: { 4 clerkId: 'clerk_123', 5 email: 'user@example.com', 6 name: 'John Doe', 7 }, 8}); 9 10// Create with relations 11const post = await prisma.post.create({ 12 data: { 13 title: 'My Post', 14 slug: 'my-post', 15 content: 'Content here...', 16 author: { 17 connect: { id: userId }, 18 }, 19 tags: { 20 connectOrCreate: [ 21 { 22 where: { slug: 'javascript' }, 23 create: { name: 'JavaScript', slug: 'javascript' }, 24 }, 25 ], 26 }, 27 }, 28 include: { 29 author: true, 30 tags: true, 31 }, 32});

Read#

1// Find unique 2const user = await prisma.user.findUnique({ 3 where: { email: 'user@example.com' }, 4}); 5 6// Find with relations 7const post = await prisma.post.findUnique({ 8 where: { slug: 'my-post' }, 9 include: { 10 author: { 11 select: { id: true, name: true, imageUrl: true }, 12 }, 13 comments: { 14 include: { author: true }, 15 orderBy: { createdAt: 'desc' }, 16 take: 10, 17 }, 18 tags: true, 19 }, 20}); 21 22// Find many with filters 23const posts = await prisma.post.findMany({ 24 where: { 25 published: true, 26 author: { role: 'ADMIN' }, 27 tags: { some: { slug: 'javascript' } }, 28 }, 29 orderBy: { createdAt: 'desc' }, 30 take: 20, 31 skip: 0, 32}); 33 34// Count 35const count = await prisma.post.count({ 36 where: { published: true }, 37});

Update#

1// Update one 2const updated = await prisma.post.update({ 3 where: { id: postId }, 4 data: { 5 title: 'Updated Title', 6 published: true, 7 publishedAt: new Date(), 8 }, 9}); 10 11// Update many 12const result = await prisma.post.updateMany({ 13 where: { authorId: userId }, 14 data: { published: false }, 15}); 16 17// Upsert 18const user = await prisma.user.upsert({ 19 where: { clerkId: 'clerk_123' }, 20 update: { name: 'Updated Name' }, 21 create: { 22 clerkId: 'clerk_123', 23 email: 'new@example.com', 24 name: 'New User', 25 }, 26});

Delete#

1// Delete one 2await prisma.post.delete({ 3 where: { id: postId }, 4}); 5 6// Delete many 7await prisma.comment.deleteMany({ 8 where: { postId }, 9}); 10 11// Soft delete (using a field) 12await prisma.post.update({ 13 where: { id: postId }, 14 data: { deletedAt: new Date() }, 15});

Transactions#

1// Interactive transaction 2const result = await prisma.$transaction(async (tx) => { 3 const post = await tx.post.create({ 4 data: { title: 'New Post', slug: 'new-post', authorId: userId }, 5 }); 6 7 await tx.user.update({ 8 where: { id: userId }, 9 data: { postCount: { increment: 1 } }, 10 }); 11 12 return post; 13}); 14 15// Batch transaction 16const [posts, users] = await prisma.$transaction([ 17 prisma.post.findMany({ where: { published: true } }), 18 prisma.user.findMany({ where: { role: 'ADMIN' } }), 19]);

Middleware#

1// lib/prisma.ts 2import { PrismaClient } from '@prisma/client'; 3 4const prisma = new PrismaClient(); 5 6// Soft delete middleware 7prisma.$use(async (params, next) => { 8 if (params.model === 'Post') { 9 if (params.action === 'delete') { 10 params.action = 'update'; 11 params.args.data = { deletedAt: new Date() }; 12 } 13 if (params.action === 'deleteMany') { 14 params.action = 'updateMany'; 15 params.args.data = { deletedAt: new Date() }; 16 } 17 } 18 return next(params); 19}); 20 21// Query logging middleware 22prisma.$use(async (params, next) => { 23 const before = Date.now(); 24 const result = await next(params); 25 const after = Date.now(); 26 27 console.log(`Query ${params.model}.${params.action} took ${after - before}ms`); 28 29 return result; 30}); 31 32export { prisma };

Scripts#

1// package.json 2{ 3 "scripts": { 4 "db:generate": "prisma generate", 5 "db:push": "prisma db push", 6 "db:migrate": "prisma migrate dev", 7 "db:migrate:deploy": "prisma migrate deploy", 8 "db:studio": "prisma studio", 9 "db:seed": "prisma db seed", 10 "db:reset": "prisma migrate reset" 11 }, 12 "prisma": { 13 "seed": "tsx prisma/seed.ts" 14 } 15}

Seed Script#

1// prisma/seed.ts 2import { PrismaClient } from '@prisma/client'; 3 4const prisma = new PrismaClient(); 5 6async function main() { 7 // Create admin user 8 const admin = await prisma.user.upsert({ 9 where: { email: 'admin@example.com' }, 10 update: {}, 11 create: { 12 clerkId: 'admin_clerk_id', 13 email: 'admin@example.com', 14 name: 'Admin User', 15 role: 'ADMIN', 16 }, 17 }); 18 19 // Create tags 20 const tags = await Promise.all( 21 ['JavaScript', 'TypeScript', 'React', 'Next.js'].map((name) => 22 prisma.tag.upsert({ 23 where: { slug: name.toLowerCase().replace(/\./g, '') }, 24 update: {}, 25 create: { 26 name, 27 slug: name.toLowerCase().replace(/\./g, ''), 28 }, 29 }) 30 ) 31 ); 32 33 // Create sample posts 34 await prisma.post.createMany({ 35 data: [ 36 { 37 title: 'Getting Started with Next.js', 38 slug: 'getting-started-nextjs', 39 content: 'Learn how to build apps with Next.js...', 40 published: true, 41 publishedAt: new Date(), 42 authorId: admin.id, 43 }, 44 { 45 title: 'TypeScript Best Practices', 46 slug: 'typescript-best-practices', 47 content: 'Tips for writing better TypeScript...', 48 published: true, 49 publishedAt: new Date(), 50 authorId: admin.id, 51 }, 52 ], 53 skipDuplicates: true, 54 }); 55 56 console.log('Seed completed'); 57} 58 59main() 60 .catch((e) => { 61 console.error(e); 62 process.exit(1); 63 }) 64 .finally(async () => { 65 await prisma.$disconnect(); 66 });