Node.js Prisma Rehberi
STK Üye Yönetim ve Rapor Platformu Örneği
Bu rehber, Prisma ORM'yi PostgreSQL veritabanı ile kullanarak bir STK (Sivil Toplum Kuruluşu) üye yönetim ve rapor platformu geliştirme sürecini adım adım anlatmaktadır.
Prisma Nedir?
Prisma, modern veritabanı işlemleri için tasarlanmış bir ORM (Object-Relational Mapping) aracıdır. Veritabanı şemasını kod ile yönetmenizi, tip güvenli sorgular yazmanızı ve veritabanı migrasyonlarını kolayca yapmanızı sağlar.
Temel Bileşenler
- Prisma Schema: Veritabanı yapısını tanımlayan dosya (
schema.prisma) - Prisma Client: Otomatik üretilen veritabanı istemcisi
- Prisma Migrate: Veritabanı şema değişikliklerini yöneten araç
- Prisma Studio: Veritabanı verilerini görsel olarak yöneten araç
Kurulum ve Başlangıç
1. Proje Kurulumu
npm init -y
npm install prisma @prisma/client
npm install -D prisma2. Prisma Başlatma
npx prisma initBu komut şunları oluşturur:
prisma/schema.prismadosyası.envdosyası
3. Veritabanı Bağlantısı (.env)
DATABASE_URL="postgresql://kullanici:sifre@localhost:5432/stk_platform"Schema Tanımlama
Temel Schema Dosyası (schema.prisma)
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// STK Kuruluşu
model Organization {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.Text
email String @unique @db.VarChar(255)
phone String? @db.VarChar(20)
address String? @db.Text
website String? @db.VarChar(255)
foundedAt DateTime @map("founded_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// İlişkiler
members Member[]
projects Project[]
donations Donation[]
reports Report[]
@@map("organizations")
}
// Üyeler
model Member {
id Int @id @default(autoincrement())
firstName String @map("first_name") @db.VarChar(100)
lastName String @map("last_name") @db.VarChar(100)
email String @unique @db.VarChar(255)
phone String? @db.VarChar(20)
birthDate DateTime? @map("birth_date")
address String? @db.Text
membershipType MembershipType @default(REGULAR) @map("membership_type")
joinDate DateTime @default(now()) @map("join_date")
isActive Boolean @default(true) @map("is_active")
organizationId Int @map("organization_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// İlişkiler
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
donations Donation[]
projectAssignments ProjectAssignment[]
activities ActivityLog[]
@@index([organizationId])
@@index([email])
@@map("members")
}
// Üyelik Tipleri
enum MembershipType {
REGULAR // Düzenli Üye
VOLUNTEER // Gönüllü
BOARD // Yönetim Kurulu
HONORARY // Onursal Üye
@@map("membership_type")
}
// Projeler
model Project {
id Int @id @default(autoincrement())
title String @db.VarChar(255)
description String @db.Text
startDate DateTime @map("start_date")
endDate DateTime? @map("end_date")
budget Decimal @db.Decimal(15, 2)
status ProjectStatus @default(PLANNING)
organizationId Int @map("organization_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// İlişkiler
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
assignments ProjectAssignment[]
donations Donation[]
reports Report[]
@@index([organizationId])
@@index([status])
@@map("projects")
}
// Proje Durumları
enum ProjectStatus {
PLANNING // Planlama
ACTIVE // Aktif
COMPLETED // Tamamlandı
CANCELLED // İptal Edildi
ON_HOLD // Beklemede
@@map("project_status")
}
// Proje Atamaları
model ProjectAssignment {
id Int @id @default(autoincrement())
memberId Int @map("member_id")
projectId Int @map("project_id")
role ProjectAssignmentRole
joinDate DateTime @default(now()) @map("join_date")
leaveDate DateTime? @map("leave_date")
isActive Boolean @default(true) @map("is_active")
// İlişkiler
member Member @relation(fields: [memberId], references: [id], onDelete: Cascade)
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@unique([memberId, projectId])
@@index([projectId])
@@map("project_assignments")
}
// Proje Görev Rolleri
enum ProjectAssignmentRole {
LEADER // Proje Lideri
COORDINATOR // Koordinatör
VOLUNTEER // Gönüllü
CONSULTANT // Danışman
@@map("project_assignment_role")
}
// Bağışlar
model Donation {
id Int @id @default(autoincrement())
amount Decimal @db.Decimal(15, 2)
currency String @default("TRY") @db.VarChar(3)
donationType DonationType @map("donation_type")
description String? @db.Text
donationDate DateTime @default(now()) @map("donation_date")
isRecurring Boolean @default(false) @map("is_recurring")
memberId Int? @map("member_id")
organizationId Int @map("organization_id")
projectId Int? @map("project_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// İlişkiler
member Member? @relation(fields: [memberId], references: [id], onDelete: SetNull)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
@@index([organizationId])
@@index([donationDate])
@@index([memberId])
@@map("donations")
}
// Bağış Tipleri
enum DonationType {
CASH // Nakit
BANK_TRANSFER // Banka Havalesi
CREDIT_CARD // Kredi Kartı
IN_KIND // Ayni Bağış
VOLUNTEER_TIME // Gönüllü Zamanı
@@map("donation_type")
}
// Raporlar
model Report {
id Int @id @default(autoincrement())
title String @db.VarChar(255)
content String @db.Text
reportType ReportType @map("report_type")
reportPeriod String @map("report_period") @db.VarChar(50) // "2024-Q1", "2024-01", vb.
organizationId Int @map("organization_id")
projectId Int? @map("project_id")
createdBy String @map("created_by") @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// İlişkiler
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
@@index([organizationId])
@@index([reportType])
@@index([reportPeriod])
@@map("reports")
}
// Rapor Tipleri
enum ReportType {
MONTHLY // Aylık Rapor
QUARTERLY // Çeyreklik Rapor
ANNUAL // Yıllık Rapor
PROJECT // Proje Raporu
FINANCIAL // Mali Rapor
ACTIVITY // Faaliyet Raporu
@@map("report_type")
}
// Aktivite Logları
model ActivityLog {
id Int @id @default(autoincrement())
memberId Int @map("member_id")
activityType String @map("activity_type") @db.VarChar(100)
description String @db.Text
createdAt DateTime @default(now()) @map("created_at")
// İlişkiler
member Member @relation(fields: [memberId], references: [id], onDelete: Cascade)
@@index([memberId])
@@index([activityType])
@@index([createdAt])
@@map("activity_logs")
}Veritabanı İşlemleri
1. Migration Oluşturma
npx prisma migrate dev --name initBu komut:
- SQL migration dosyası oluşturur
- Veritabanında tabloları oluşturur
- Prisma Client'ı günceller
2. Oluşturulan SQL Yapısı
Migration sonrasında PostgreSQL'de oluşan tablolar:
-- Organizations Tablosu
CREATE TABLE organizations (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
email VARCHAR(255) UNIQUE NOT NULL,
phone VARCHAR(20),
address TEXT,
website VARCHAR(255),
founded_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Members Tablosu
CREATE TABLE members (
id SERIAL PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
phone VARCHAR(20),
birth_date DATE,
address TEXT,
membership_type membership_type DEFAULT 'REGULAR',
join_date TIMESTAMP DEFAULT NOW(),
is_active BOOLEAN DEFAULT true,
organization_id INTEGER REFERENCES organizations(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_members_organization_id ON members(organization_id);
CREATE INDEX idx_members_email ON members(email);3. Prisma Studio ile Veritabanı Yönetimi
npx prisma studioBu komut web tabanlı veritabanı yönetim arayüzünü açar.
SQL Sorguları ve Prisma Karşılıkları
Temel CRUD İşlemleri
1. Kayıt Ekleme
SQL:
INSERT INTO members (first_name, last_name, email, organization_id)
VALUES ('Ahmet', 'Yılmaz', 'ahmet@example.com', 1);Prisma:
const newMember = await prisma.member.create({
data: {
firstName: 'Ahmet',
lastName: 'Yılmaz',
email: 'ahmet@example.com',
organizationId: 1
}
});2. Kayıt Sorgulama
SQL:
SELECT m.*, o.name as organization_name
FROM members m
JOIN organizations o ON m.organization_id = o.id
WHERE m.is_active = true
ORDER BY m.created_at DESC
LIMIT 10;Prisma:
const activeMembers = await prisma.member.findMany({
where: { isActive: true },
include: { organization: true },
orderBy: { createdAt: 'desc' },
take: 10
});3. Kayıt Güncelleme
SQL:
UPDATE members
SET membership_type = 'VOLUNTEER', updated_at = NOW()
WHERE id = 1;Prisma:
const updatedMember = await prisma.member.update({
where: { id: 1 },
data: { membershipType: 'VOLUNTEER' }
});Kompleks Sorgular
1. Grup Bazında Özet Raporu
SQL:
SELECT
o.name as organization_name,
COUNT(m.id) as total_members,
COUNT(CASE WHEN m.is_active = true THEN 1 END) as active_members,
SUM(d.amount) as total_donations
FROM organizations o
LEFT JOIN members m ON o.id = m.organization_id
LEFT JOIN donations d ON o.id = d.organization_id
GROUP BY o.id, o.name
ORDER BY total_donations DESC;Prisma:
const organizationStats = await prisma.organization.findMany({
select: {
name: true,
_count: {
members: true
},
members: {
where: { isActive: true },
select: { id: true }
},
donations: {
select: {
amount: true
}
}
}
});
// Sonuçları işleme
const processedStats = organizationStats.map(org => ({
organizationName: org.name,
totalMembers: org._count.members,
activeMembers: org.members.length,
totalDonations: org.donations.reduce((sum, d) => sum + d.amount, 0)
}));2. Proje Bazında Detaylı Rapor
SQL:
SELECT
p.title,
p.status,
COUNT(DISTINCT pa.member_id) as team_size,
SUM(d.amount) as project_donations,
AVG(EXTRACT(DAYS FROM (COALESCE(p.end_date, NOW()) - p.start_date))) as duration_days
FROM projects p
LEFT JOIN project_assignments pa ON p.id = pa.project_id AND pa.is_active = true
LEFT JOIN donations d ON p.id = d.project_id
WHERE p.organization_id = 1
GROUP BY p.id, p.title, p.status
ORDER BY project_donations DESC;Prisma:
const projectReport = await prisma.project.findMany({
where: { organizationId: 1 },
select: {
title: true,
status: true,
startDate: true,
endDate: true,
assignments: {
where: { isActive: true },
select: { memberId: true }
},
donations: {
select: { amount: true }
}
}
});İlişki (Relation) Yönetimi
1. One-to-Many İlişkiler
// Bir kuruluşun birden fazla üyesi olabilir
model Organization {
id Int @id @default(autoincrement())
name String
members Member[] // One-to-many
}
model Member {
id Int @id @default(autoincrement())
name String
organizationId Int
organization Organization @relation(fields: [organizationId], references: [id])
}2. Many-to-Many İlişkiler
// Üyeler birden fazla projeye atanabilir, projeler birden fazla üyeye sahip olabilir
model Member {
id Int @id @default(autoincrement())
assignments ProjectAssignment[]
}
model Project {
id Int @id @default(autoincrement())
assignments ProjectAssignment[]
}
model ProjectAssignment {
memberId Int
projectId Int
role String
member Member @relation(fields: [memberId], references: [id])
project Project @relation(fields: [projectId], references: [id])
@@id([memberId, projectId])
}3. Self İlişkiler
// Üyeler arasında mentor-mentee ilişkisi
model Member {
id Int @id @default(autoincrement())
mentorId Int?
mentor Member? @relation("MentorMentee", fields: [mentorId], references: [id])
mentees Member[] @relation("MentorMentee")
}DBML Dönüştürme
DBML Nedir?
DBML (Database Markup Language), veritabanı şemalarını görsel olarak temsil etmek için kullanılan bir işaretleme dilidir.
Prisma'dan DBML'ye Dönüştürme
1. Gerekli Paketi Yükleme
npm install -D prisma-dbml-generator2. Schema'ya Generator Ekleme
generator dbml {
provider = "prisma-dbml-generator"
output = "./dbml"
}3. DBML Oluşturma
npx prisma generate4. Oluşturulan DBML Dosyası Örneği
Table organizations {
id int [pk, increment]
name varchar(255) [not null]
description text
email varchar(255) [unique, not null]
phone varchar(20)
address text
website varchar(255)
founded_at timestamp [not null]
created_at timestamp [default: `now()`]
updated_at timestamp [default: `now()`]
}
Table members {
id int [pk, increment]
first_name varchar(100) [not null]
last_name varchar(100) [not null]
email varchar(255) [unique, not null]
phone varchar(20)
birth_date date
address text
membership_type membership_type [default: 'REGULAR']
join_date timestamp [default: `now()`]
is_active boolean [default: true]
organization_id int [ref: > organizations.id]
created_at timestamp [default: `now()`]
updated_at timestamp [default: `now()`]
}
Table projects {
id int [pk, increment]
title varchar(255) [not null]
description text [not null]
start_date timestamp [not null]
end_date timestamp
budget decimal(15,2) [not null]
status project_status [default: 'PLANNING']
organization_id int [ref: > organizations.id]
created_at timestamp [default: `now()`]
updated_at timestamp [default: `now()`]
}
Table project_assignments {
id int [pk, increment]
member_id int [ref: > members.id]
project_id int [ref: > projects.id]
role project_assignment_role [not null]
join_date timestamp [default: `now()`]
leave_date timestamp
is_active boolean [default: true]
}
Table donations {
id int [pk, increment]
amount decimal(15,2) [not null]
currency varchar(3) [default: 'TRY']
donation_type donation_type [not null]
description text
donation_date timestamp [default: `now()`]
is_recurring boolean [default: false]
member_id int [ref: > members.id]
organization_id int [ref: > organizations.id]
project_id int [ref: > projects.id]
created_at timestamp [default: `now()`]
updated_at timestamp [default: `now()`]
}
Enum membership_type {
REGULAR
VOLUNTEER
BOARD
HONORARY
}
Enum project_status {
PLANNING
ACTIVE
COMPLETED
CANCELLED
ON_HOLD
}DBML'den Diagram Oluşturma
1. dbdiagram.io Kullanımı
- DBML dosyasının içeriğini kopyalayın
- https://dbdiagram.io sitesine gidin
- Kodu yapıştırın
- Otomatik olarak ER diagram oluşur
2. CLI ile PNG/SVG Export
npm install -g @dbml/cli
dbml2sql schema.dbml --postgresPratik Örnekler ve İpuçları
1. Veritabanı Seed (Tohum) Verisi
prisma/seed.js dosyası:
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function main() {
// Kuruluş oluştur
const org = await prisma.organization.create({
data: {
name: 'Çevre Koruma Derneği',
email: 'info@cevredernek.org',
description: 'Çevre koruma alanında faaliyet gösteren STK',
foundedAt: new Date('2020-01-01'),
website: 'https://cevredernek.org'
}
});
// Üyeler oluştur
const members = await prisma.member.createMany({
data: [
{
firstName: 'Ayşe',
lastName: 'Kaya',
email: 'ayse@example.com',
membershipType: 'BOARD',
organizationId: org.id
},
{
firstName: 'Mehmet',
lastName: 'Demir',
email: 'mehmet@example.com',
membershipType: 'VOLUNTEER',
organizationId: org.id
}
]
});
console.log('Tohum veri oluşturuldu');
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());2. Faydalı Prisma Komutları
# Veritabanını sıfırla
npx prisma db push --force-reset
# Schema'dan SQL oluştur
npx prisma db pull
# Mevcut veritabanından schema oluştur
npx prisma db pull
# Client'ı yeniden oluştur
npx prisma generate
# Formatla
npx prisma format3. Raw SQL Sorguları
Karmaşık sorgular için raw SQL kullanabilirsiniz:
// Ham SQL sorgusu
const result = await prisma.$queryRaw`
SELECT
DATE_TRUNC('month', donation_date) as month,
SUM(amount) as monthly_total
FROM donations
WHERE organization_id = ${orgId}
GROUP BY DATE_TRUNC('month', donation_date)
ORDER BY month;
`;
// Parametreli sorgu
const memberStats = await prisma.$queryRaw`
SELECT
membership_type,
COUNT(*) as member_count,
AVG(EXTRACT(YEAR FROM AGE(birth_date))) as avg_age
FROM members
WHERE organization_id = ${organizationId}
GROUP BY membership_type;
`;4. Transaction İşlemleri
// İşlem (Transaction) örneği
const transferDonation = await prisma.$transaction(async (tx) => {
// Bağışı oluştur
const donation = await tx.donation.create({
data: {
amount: 1000,
donationType: 'BANK_TRANSFER',
organizationId: orgId,
memberId: memberId
}
});
// Aktivite logu ekle
await tx.activityLog.create({
data: {
memberId: memberId,
activityType: 'DONATION',
description: `${donation.amount} TL bağış yapıldı`
}
});
return donation;
});Sonuç
Bu rehber, Prisma ORM'nin temel özelliklerini STK üye yönetim platformu örneği üzerinden göstermiştir. Prisma'nın güçlü yanları:
- Tip güvenliği: Otomatik tip üretimi
- Kolay migration: Veritabanı değişikliklerini takip etme
- İlişki yönetimi: Karmaşık ilişkileri basit syntax ile tanımlama
- Raw SQL desteği: Karmaşık sorgular için ham SQL kullanabilme
- Görsel araçlar: Prisma Studio ve DBML dönüştürme
Bu temel bilgiler ile PostgreSQL veritabanı kullanan Node.js projelerinizde Prisma'yı etkili şekilde kullanabilirsiniz.