Next.js ile Prisma ve PostgreSQL: Full-Stack Database Rehberi
Next.js 15 ile Prisma ORM ve PostgreSQL kullanarak production-ready database katmanı oluşturun. Schema tasarımı, migrations, relations ve performans optimizasyonları bu kapsamlı rehberde.
Next.js ile Prisma ve PostgreSQL: Full-Stack Database Rehberi
Full-stack development'ın en kritik parçalarından biri database katmanı. Doğru araçlarla çalışmak, hem geliştirme sürecini hızlandırır hem de production'da güvenilir bir sistem sağlar. Next.js + Prisma + PostgreSQL üçlüsü, modern web uygulamaları için altın standart haline geldi.
Prisma, TypeScript-first ORM olarak type safety sağlıyor. PostgreSQL, enterprise-grade güvenilirlik sunuyor. Next.js ise bu ikisini Server Components ve Server Actions ile mükemmel şekilde entegre ediyor. Bu rehberde sıfırdan production-ready bir database katmanı oluşturacağız.
İçindekiler
- Neden Prisma ve PostgreSQL?
- Kurulum ve Konfigürasyon
- Prisma Schema Tasarımı
- Migrations Yönetimi
- CRUD Operasyonları
- İlişkiler ve Nested Queries
- Server Components ile Data Fetching
- Server Actions ile Mutations
- Transaction ve Batch İşlemler
- Performans Optimizasyonu
- Connection Pooling
- Error Handling Best Practices
- Seeding ve Test Data
- Sık Sorulan Sorular
Neden Prisma ve PostgreSQL?
Prisma Avantajları
Type Safety: Schema'dan otomatik TypeScript tipleri üretilir. Yanlış field adı yazamazsınız, IDE autocompletion çalışır.
// Prisma Client tam typed
const user = await prisma.user.findUnique({
where: { id: userId },
include: { posts: true }
})
// user.posts otomatik olarak Post[] tipinde
user.posts.map(post => post.title) // ✅ Type safe
user.posts.map(post => post.invalid) // ❌ TypeScript errorDeveloper Experience: Introspection, migrations, studio ve seed gibi araçlarla development süreci hızlanır.
Query Building: Raw SQL yerine type-safe query builder. Kompleks sorgular bile okunabilir.
PostgreSQL Avantajları
- ACID compliance
- JSON/JSONB native support
- Full-text search
- Extensions (PostGIS, pg_trgm, etc.)
- Scalability ve reliability
Alternatifler
| ORM | Artılar | Eksiler |
|---|---|---|
| Prisma | Type safety, DX, migrations | Cold start, edge runtime sınırlı |
| Drizzle | Hafif, edge uyumlu, SQL-like | Daha az abstraction |
| Kysely | Type-safe SQL builder | Manuel schema sync |
| TypeORM | Decorator-based, mature | Kompleks setup |
Kurulum ve Konfigürasyon
Prisma Kurulumu
# Prisma CLI ve client
npm install prisma @prisma/client
# Prisma init
npx prisma initBu komut prisma/schema.prisma dosyası ve .env oluşturur.
PostgreSQL Bağlantısı
# .env
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
# Neon (serverless PostgreSQL)
DATABASE_URL="postgresql://user:password@ep-xxx.eu-central-1.aws.neon.tech/mydb?sslmode=require"
# Supabase
DATABASE_URL="postgresql://postgres:password@db.xxx.supabase.co:5432/postgres"Prisma Client Setup
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error']
})
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}Bu pattern, development'ta hot reload sırasında çoklu connection oluşmasını önler.
Prisma Schema Tasarımı
Temel Schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
password String
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
posts Post[]
comments Comment[]
profile Profile?
@@index([email])
@@map("users")
}
model Profile {
id String @id @default(cuid())
bio String?
avatar String?
website String?
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("profiles")
}
model Post {
id String @id @default(cuid())
title String
slug String @unique
content String
excerpt String?
published Boolean @default(false)
publishedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
categoryId String?
category Category? @relation(fields: [categoryId], references: [id])
comments Comment[]
tags Tag[]
@@index([authorId])
@@index([categoryId])
@@index([slug])
@@map("posts")
}
model Category {
id String @id @default(cuid())
name String @unique
slug String @unique
posts Post[]
@@map("categories")
}
model Tag {
id String @id @default(cuid())
name String @unique
slug String @unique
posts Post[]
@@map("tags")
}
model Comment {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
postId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
// Self-relation for nested comments
parentId String?
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
replies Comment[] @relation("CommentReplies")
@@index([authorId])
@@index([postId])
@@map("comments")
}
enum Role {
USER
AUTHOR
ADMIN
}Schema Best Practices
ID Stratejisi:
// UUID - distributed systems için
id String @id @default(uuid())
// CUID - sortable, URL-safe
id String @id @default(cuid())
// Auto-increment - simple use cases
id Int @id @default(autoincrement())Timestamps:
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete içinIndexler:
@@index([authorId]) // Foreign key
@@index([createdAt(sort: Desc)]) // Sorting
@@index([title, content]) // Composite
@@fulltext([title, content]) // Full-text (MySQL)Migrations Yönetimi
Migration Oluşturma
# Yeni migration oluştur
npx prisma migrate dev --name init
# Migration oluştur ama apply etme
npx prisma migrate dev --create-only
# Production'da apply
npx prisma migrate deployMigration Workflow
# 1. Schema değişikliği yap
# prisma/schema.prisma'yı düzenle
# 2. Migration oluştur ve apply et
npx prisma migrate dev --name add_user_avatar
# 3. Client'ı regenerate et (otomatik yapılır)
npx prisma generateMigration Dosyası
-- prisma/migrations/20260104_add_user_avatar/migration.sql
ALTER TABLE "users" ADD COLUMN "avatar" TEXT;
CREATE INDEX "users_email_idx" ON "users"("email");Dikkat Edilmesi Gerekenler
# Schema'yı DB ile senkronize et (development only!)
npx prisma db push
# Mevcut DB'den schema oluştur
npx prisma db pull
# Migration geçmişini sıfırla (dikkat!)
npx prisma migrate resetCRUD Operasyonları
Create
// Tek kayıt
const user = await prisma.user.create({
data: {
email: 'john@example.com',
name: 'John Doe',
password: hashedPassword
}
})
// İlişkili kayıtlarla birlikte
const userWithProfile = await prisma.user.create({
data: {
email: 'jane@example.com',
name: 'Jane Doe',
password: hashedPassword,
profile: {
create: {
bio: 'Software Developer',
website: 'https://jane.dev'
}
}
},
include: {
profile: true
}
})
// Çoklu kayıt
const users = await prisma.user.createMany({
data: [
{ email: 'user1@example.com', name: 'User 1', password: hash1 },
{ email: 'user2@example.com', name: 'User 2', password: hash2 }
],
skipDuplicates: true
})Read
// Tek kayıt
const user = await prisma.user.findUnique({
where: { id: userId }
})
// İlk eşleşen
const admin = await prisma.user.findFirst({
where: { role: 'ADMIN' }
})
// Tüm kayıtlar
const users = await prisma.user.findMany({
where: {
role: 'USER',
createdAt: {
gte: new Date('2026-01-01')
}
},
orderBy: {
createdAt: 'desc'
},
take: 10,
skip: 0
})
// Select - sadece istenen alanlar
const userEmails = await prisma.user.findMany({
select: {
id: true,
email: true,
name: true
}
})
// Include - ilişkili veriler
const userWithPosts = await prisma.user.findUnique({
where: { id: userId },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5
},
profile: true,
_count: {
select: { posts: true, comments: true }
}
}
})Update
// Tek kayıt
const updatedUser = await prisma.user.update({
where: { id: userId },
data: {
name: 'John Updated',
profile: {
update: {
bio: 'Updated bio'
}
}
}
})
// Upsert - yoksa oluştur, varsa güncelle
const user = await prisma.user.upsert({
where: { email: 'john@example.com' },
update: { name: 'John Updated' },
create: {
email: 'john@example.com',
name: 'John Doe',
password: hashedPassword
}
})
// Çoklu güncelleme
const result = await prisma.user.updateMany({
where: {
role: 'USER',
createdAt: {
lt: new Date('2025-01-01')
}
},
data: {
role: 'LEGACY_USER'
}
})
// result.count = güncellenen kayıt sayısıDelete
// Tek kayıt
const deletedUser = await prisma.user.delete({
where: { id: userId }
})
// Çoklu silme
const result = await prisma.user.deleteMany({
where: {
role: 'LEGACY_USER'
}
})
// Soft delete pattern
const softDeleted = await prisma.user.update({
where: { id: userId },
data: { deletedAt: new Date() }
})İlişkiler ve Nested Queries
One-to-One
// User -> Profile
const userWithProfile = await prisma.user.findUnique({
where: { id: userId },
include: { profile: true }
})
// Profile üzerinden User
const profile = await prisma.profile.findUnique({
where: { userId },
include: { user: true }
})One-to-Many
// User -> Posts
const authorWithPosts = await prisma.user.findUnique({
where: { id: authorId },
include: {
posts: {
where: { published: true },
orderBy: { publishedAt: 'desc' },
include: {
category: true,
_count: { select: { comments: true } }
}
}
}
})
// Post -> Author
const postWithAuthor = await prisma.post.findUnique({
where: { slug: 'my-post' },
include: {
author: {
select: { id: true, name: true, email: true }
}
}
})Many-to-Many
// Post -> Tags (implicit many-to-many)
const postWithTags = await prisma.post.findUnique({
where: { id: postId },
include: { tags: true }
})
// Tag'e post ekle
await prisma.post.update({
where: { id: postId },
data: {
tags: {
connect: [
{ id: 'tag-1' },
{ id: 'tag-2' }
]
}
}
})
// Tag'den post çıkar
await prisma.post.update({
where: { id: postId },
data: {
tags: {
disconnect: [{ id: 'tag-1' }]
}
}
})
// Tag'leri yeniden ayarla
await prisma.post.update({
where: { id: postId },
data: {
tags: {
set: [{ id: 'tag-3' }, { id: 'tag-4' }]
}
}
})Nested Writes
// Kompleks nested create
const post = await prisma.post.create({
data: {
title: 'New Post',
slug: 'new-post',
content: 'Post content...',
author: {
connect: { id: authorId }
},
category: {
connectOrCreate: {
where: { slug: 'tech' },
create: { name: 'Technology', slug: 'tech' }
}
},
tags: {
connectOrCreate: [
{
where: { slug: 'javascript' },
create: { name: 'JavaScript', slug: 'javascript' }
},
{
where: { slug: 'nextjs' },
create: { name: 'Next.js', slug: 'nextjs' }
}
]
}
},
include: {
author: true,
category: true,
tags: true
}
})Server Components ile Data Fetching
Basit Fetching
// app/posts/page.tsx
import { prisma } from '@/lib/prisma'
export default async function PostsPage() {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { publishedAt: 'desc' },
include: {
author: {
select: { name: true }
},
category: true,
_count: {
select: { comments: true }
}
},
take: 10
})
return (
<div className="grid gap-6">
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
)
}Parallel Data Fetching
// app/dashboard/page.tsx
import { prisma } from '@/lib/prisma'
async function getStats() {
const [userCount, postCount, commentCount] = await Promise.all([
prisma.user.count(),
prisma.post.count({ where: { published: true } }),
prisma.comment.count()
])
return { userCount, postCount, commentCount }
}
async function getRecentPosts() {
return prisma.post.findMany({
where: { published: true },
orderBy: { publishedAt: 'desc' },
take: 5,
include: { author: { select: { name: true } } }
})
}
async function getRecentComments() {
return prisma.comment.findMany({
orderBy: { createdAt: 'desc' },
take: 5,
include: {
author: { select: { name: true } },
post: { select: { title: true, slug: true } }
}
})
}
export default async function DashboardPage() {
// Parallel fetching
const [stats, recentPosts, recentComments] = await Promise.all([
getStats(),
getRecentPosts(),
getRecentComments()
])
return (
<div className="grid gap-6">
<StatsCards stats={stats} />
<RecentPosts posts={recentPosts} />
<RecentComments comments={recentComments} />
</div>
)
}Dynamic Route ile Fetching
// app/posts/[slug]/page.tsx
import { prisma } from '@/lib/prisma'
import { notFound } from 'next/navigation'
type Props = {
params: Promise<{ slug: string }>
}
export default async function PostPage({ params }: Props) {
const { slug } = await params
const post = await prisma.post.findUnique({
where: { slug, published: true },
include: {
author: {
select: { id: true, name: true, email: true }
},
category: true,
tags: true,
comments: {
where: { parentId: null },
orderBy: { createdAt: 'desc' },
include: {
author: { select: { name: true } },
replies: {
include: { author: { select: { name: true } } }
}
}
}
}
})
if (!post) {
notFound()
}
return (
<article>
<h1>{post.title}</h1>
<PostMeta post={post} />
<PostContent content={post.content} />
<PostTags tags={post.tags} />
<CommentSection comments={post.comments} postId={post.id} />
</article>
)
}
// Static generation için
export async function generateStaticParams() {
const posts = await prisma.post.findMany({
where: { published: true },
select: { slug: true }
})
return posts.map(post => ({ slug: post.slug }))
}Server Actions ile Mutations
Create Action
// app/actions/posts.ts
'use server'
import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
const createPostSchema = z.object({
title: z.string().min(5).max(100),
content: z.string().min(100),
categoryId: z.string().optional(),
tags: z.array(z.string()).optional()
})
export async function createPost(formData: FormData) {
const session = await getSession()
if (!session) {
return { error: 'Oturum açmanız gerekiyor' }
}
const rawData = {
title: formData.get('title'),
content: formData.get('content'),
categoryId: formData.get('categoryId') || undefined,
tags: formData.getAll('tags')
}
const parsed = createPostSchema.safeParse(rawData)
if (!parsed.success) {
return {
error: 'Validasyon hatası',
errors: parsed.error.flatten().fieldErrors
}
}
const slug = generateSlug(parsed.data.title)
try {
const post = await prisma.post.create({
data: {
title: parsed.data.title,
slug,
content: parsed.data.content,
excerpt: parsed.data.content.substring(0, 160),
authorId: session.user.id,
categoryId: parsed.data.categoryId,
tags: parsed.data.tags?.length ? {
connectOrCreate: parsed.data.tags.map(tag => ({
where: { slug: generateSlug(tag) },
create: { name: tag, slug: generateSlug(tag) }
}))
} : undefined
}
})
revalidatePath('/posts')
revalidatePath('/dashboard/posts')
redirect(`/posts/${post.slug}`)
} catch (error) {
if (error.code === 'P2002') {
return { error: 'Bu başlıkta bir yazı zaten mevcut' }
}
return { error: 'Post oluşturulamadı' }
}
}Update Action
// app/actions/posts.ts
'use server'
export async function updatePost(postId: string, formData: FormData) {
const session = await getSession()
if (!session) {
return { error: 'Oturum açmanız gerekiyor' }
}
// Yetki kontrolü
const existingPost = await prisma.post.findUnique({
where: { id: postId },
select: { authorId: true }
})
if (!existingPost) {
return { error: 'Post bulunamadı' }
}
if (existingPost.authorId !== session.user.id && session.user.role !== 'ADMIN') {
return { error: 'Bu işlem için yetkiniz yok' }
}
const rawData = {
title: formData.get('title'),
content: formData.get('content'),
published: formData.get('published') === 'true'
}
try {
const post = await prisma.post.update({
where: { id: postId },
data: {
...rawData,
slug: generateSlug(rawData.title as string),
publishedAt: rawData.published ? new Date() : null
}
})
revalidatePath('/posts')
revalidatePath(`/posts/${post.slug}`)
return { success: true, post }
} catch (error) {
return { error: 'Güncelleme başarısız' }
}
}Delete Action
// app/actions/posts.ts
'use server'
export async function deletePost(postId: string) {
const session = await getSession()
if (!session) {
return { error: 'Oturum açmanız gerekiyor' }
}
const post = await prisma.post.findUnique({
where: { id: postId },
select: { authorId: true, slug: true }
})
if (!post) {
return { error: 'Post bulunamadı' }
}
if (post.authorId !== session.user.id && session.user.role !== 'ADMIN') {
return { error: 'Bu işlem için yetkiniz yok' }
}
try {
await prisma.post.delete({ where: { id: postId } })
revalidatePath('/posts')
revalidatePath('/dashboard/posts')
return { success: true }
} catch (error) {
return { error: 'Silme işlemi başarısız' }
}
}Transaction ve Batch İşlemler
Interactive Transaction
// Kullanıcı silme - tüm ilişkili verileri temizle
async function deleteUserWithData(userId: string) {
return prisma.$transaction(async (tx) => {
// Önce yorumları sil
await tx.comment.deleteMany({
where: { authorId: userId }
})
// Postları sil
await tx.post.deleteMany({
where: { authorId: userId }
})
// Profili sil
await tx.profile.delete({
where: { userId }
}).catch(() => null) // Profil yoksa hata verme
// Son olarak kullanıcıyı sil
return tx.user.delete({
where: { id: userId }
})
})
}Sequential Transaction
// Bakiye transfer
async function transferBalance(fromId: string, toId: string, amount: number) {
return prisma.$transaction([
prisma.account.update({
where: { id: fromId },
data: { balance: { decrement: amount } }
}),
prisma.account.update({
where: { id: toId },
data: { balance: { increment: amount } }
}),
prisma.transaction.create({
data: {
fromAccountId: fromId,
toAccountId: toId,
amount,
type: 'TRANSFER'
}
})
])
}Batch Operations
// Bulk update
await prisma.post.updateMany({
where: {
authorId: userId,
published: false
},
data: {
published: true,
publishedAt: new Date()
}
})
// Bulk upsert
const tags = ['javascript', 'nextjs', 'prisma']
await Promise.all(
tags.map(tag =>
prisma.tag.upsert({
where: { slug: tag },
update: {},
create: { name: tag, slug: tag }
})
)
)Performans Optimizasyonu
Select ile Field Limiting
// ❌ Tüm alanları çeker
const users = await prisma.user.findMany()
// ✅ Sadece gerekli alanlar
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true
}
})Pagination
// Offset-based pagination
async function getPosts(page: number, pageSize: number = 10) {
const [posts, total] = await Promise.all([
prisma.post.findMany({
where: { published: true },
orderBy: { publishedAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize
}),
prisma.post.count({ where: { published: true } })
])
return {
posts,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize)
}
}
}
// Cursor-based pagination (daha performanslı)
async function getPostsCursor(cursor?: string, limit: number = 10) {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { id: 'desc' },
take: limit + 1,
...(cursor && {
cursor: { id: cursor },
skip: 1
})
})
const hasMore = posts.length > limit
const items = hasMore ? posts.slice(0, -1) : posts
return {
items,
nextCursor: hasMore ? items[items.length - 1].id : null
}
}N+1 Problem Çözümü
// ❌ N+1 problemi
const posts = await prisma.post.findMany()
for (const post of posts) {
const author = await prisma.user.findUnique({
where: { id: post.authorId }
})
}
// ✅ Include ile tek sorgu
const posts = await prisma.post.findMany({
include: {
author: {
select: { id: true, name: true }
}
}
})Query Optimization
// Aggregate functions
const stats = await prisma.post.aggregate({
where: { published: true },
_count: true,
_avg: { viewCount: true },
_sum: { viewCount: true }
})
// Group by
const postsByCategory = await prisma.post.groupBy({
by: ['categoryId'],
where: { published: true },
_count: true,
orderBy: {
_count: {
id: 'desc'
}
}
})Connection Pooling
Serverless için PgBouncer
# Direct connection (migrations için)
DIRECT_DATABASE_URL="postgresql://user:password@host:5432/db"
# Pooled connection (application için)
DATABASE_URL="postgresql://user:password@host:6543/db?pgbouncer=true"datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_DATABASE_URL")
}Neon Serverless
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
import { PrismaNeon } from '@prisma/adapter-neon'
import { Pool } from '@neondatabase/serverless'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const adapter = new PrismaNeon(pool)
export const prisma = new PrismaClient({ adapter })Error Handling Best Practices
Prisma Error Codes
import { Prisma } from '@prisma/client'
async function createUser(data: UserInput) {
try {
return await prisma.user.create({ data })
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
switch (error.code) {
case 'P2002':
// Unique constraint violation
const field = (error.meta?.target as string[])?.[0]
throw new Error(`${field} zaten kullanılıyor`)
case 'P2003':
// Foreign key constraint
throw new Error('İlişkili kayıt bulunamadı')
case 'P2025':
// Record not found
throw new Error('Kayıt bulunamadı')
default:
throw new Error('Veritabanı hatası')
}
}
if (error instanceof Prisma.PrismaClientValidationError) {
throw new Error('Geçersiz veri formatı')
}
throw error
}
}Wrapper Function
// lib/db-utils.ts
type DbResult<T> = { data: T; error: null } | { data: null; error: string }
export async function dbQuery<T>(
query: () => Promise<T>
): Promise<DbResult<T>> {
try {
const data = await query()
return { data, error: null }
} catch (error) {
console.error('Database error:', error)
if (error instanceof Prisma.PrismaClientKnownRequestError) {
return { data: null, error: getErrorMessage(error.code) }
}
return { data: null, error: 'Beklenmeyen bir hata oluştu' }
}
}
// Kullanım
const result = await dbQuery(() =>
prisma.user.create({ data: userData })
)
if (result.error) {
return { error: result.error }
}
return { user: result.data }Seeding ve Test Data
Seed Script
// prisma/seed.ts
import { PrismaClient } from '@prisma/client'
import { hash } from 'bcryptjs'
const prisma = new PrismaClient()
async function main() {
// Kategoriler
const categories = await Promise.all([
prisma.category.upsert({
where: { slug: 'technology' },
update: {},
create: { name: 'Technology', slug: 'technology' }
}),
prisma.category.upsert({
where: { slug: 'lifestyle' },
update: {},
create: { name: 'Lifestyle', slug: 'lifestyle' }
})
])
// Admin kullanıcı
const adminPassword = await hash('admin123', 12)
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
password: adminPassword,
role: 'ADMIN',
profile: {
create: {
bio: 'Site administrator'
}
}
}
})
// Örnek postlar
await prisma.post.createMany({
data: [
{
title: 'Getting Started with Next.js',
slug: 'getting-started-nextjs',
content: 'Lorem ipsum dolor sit amet...',
excerpt: 'Learn how to build modern web apps',
published: true,
publishedAt: new Date(),
authorId: admin.id,
categoryId: categories[0].id
},
{
title: 'Prisma Best Practices',
slug: 'prisma-best-practices',
content: 'Prisma is a modern ORM...',
excerpt: 'Master Prisma ORM',
published: true,
publishedAt: new Date(),
authorId: admin.id,
categoryId: categories[0].id
}
],
skipDuplicates: true
})
console.log('Seed completed!')
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect())// package.json
{
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}
}npx prisma db seedSık Sorulan Sorular
Prisma Edge Runtime'da çalışır mı?
Prisma Accelerate veya Neon serverless adapter ile edge'de çalışabilir. Standart Prisma Client Node.js gerektirir.
Migration'lar production'da nasıl çalışır?
prisma migrate deploy komutu CI/CD pipeline'da veya build sırasında çalıştırılmalı. Vercel'de postinstall script kullanabilirsiniz.
Soft delete nasıl yapılır?
deletedAt alanı ekleyip middleware ile tüm sorgulara where: { deletedAt: null } filtresi ekleyebilirsiniz.
Raw SQL gerektiğinde ne yapmalıyım?
prisma.$queryRaw ve prisma.$executeRaw ile raw SQL çalıştırabilirsiniz. Type safety için Prisma.sql template tag kullanın.
Connection limit aşılırsa ne olur?
Serverless ortamlarda connection pooling (PgBouncer, Neon, Supabase pooler) kullanın. Development'ta global prisma instance pattern uygulayın.
Multi-tenant yapı nasıl kurulur?
Schema-based (her tenant ayrı schema) veya row-based (tenantId ile filtreleme) yaklaşımlar kullanılabilir.
Sonuç
Prisma ve PostgreSQL kombinasyonu, Next.js ile full-stack development için güçlü bir temel oluşturuyor. Type-safe queries, otomatik migrations ve Server Components ile seamless entegrasyon - modern web development'ın tüm ihtiyaçlarını karşılıyor.
Bu rehberde schema tasarımından performans optimizasyonuna kadar geniş bir yelpazeyi kapsadık. Artık production-ready database katmanı oluşturabilecek bilgiye sahipsiniz.
Sonraki Adımlar:
- Prisma'yı projenize entegre edin
- İlişkisel bir schema tasarlayın
- Server Actions ile CRUD operations yazın
- Connection pooling ayarlayın
- Seeding scripti oluşturun
Serinin devamında NextAuth.js ile authentication konusunu işleyeceğiz. Kullanıcı oturumlarını güvenli şekilde yönetmeyi öğreneceksiniz.
Projenizi Hayata Geçirelim
Web sitesi, mobil uygulama veya yapay zeka çözümü mü arıyorsunuz? Fikirlerinizi birlikte değerlendirelim.
Ücretsiz Danışmanlık Alın