REST ve GraphQL, modern API tasarımının iki ana paradigmasıdır. Her birinin güçlü ve zayıf yönleri vardır; doğru seçim projenizin gereksinimlerine bağlıdır. Bu makalede her iki yaklaşımı aynı problem üzerinde karşılaştırıyoruz.
Problem: Blog API
Bir blog platformu için hem REST hem de GraphQL API tasarlıyoruz. İhtiyaçlar:
▸ Makale listesi (sayfalama, filtreleme)
▸ Makale detayı (yazar bilgisi, yorumlar, etiketler)
▸ Yorum ekleme
▸ Kullanıcı profili
REST Yaklaşımı
GET /api/makaleler?sayfa=1&boyut=10&kategori=backend
GET /api/makaleler/:slug
GET /api/makaleler/:slug/yorumlar
POST /api/makaleler/:slug/yorumlar
GET /api/kullanicilar/:id
GET /api/kullanicilar/:id/makaleler KOPYALA
// Express — REST endpoint'leri
app . get ( '/api/makaleler' , async ( req , res ) => {
const { sayfa = 1 , boyut = 10 , kategori } = req . query ;
const makaleler = await db . makale . findMany ({
where : kategori ? { kategori : String ( kategori ) } : {},
skip : ( Number ( sayfa ) - 1 ) * Number ( boyut ),
take : Number ( boyut ),
select : {
id : true , baslik : true , slug : true , excerpt : true ,
olusturuldu : true ,
yazar : { select : { ad : true , avatar : true } },
etiketler : { select : { isim : true } },
},
orderBy : { olusturuldu : 'desc' },
});
res . json ( makaleler );
});
app . get ( '/api/makaleler/:slug' , async ( req , res ) => {
const makale = await db . makale . findUnique ({
where : { slug : req . params . slug },
include : {
yazar : true ,
etiketler : true ,
yorumlar : {
include : { yazar : { select : { ad : true , avatar : true } } },
orderBy : { olusturuldu : 'asc' },
},
},
});
if ( ! makale ) return res . status ( 404 ). json ({ hata : 'Makale bulunamadı.' });
res . json ( makale );
}); KOPYALA
REST Sorunları:
// Senaryo: Mobil uygulama anasayfa
// Gerçekte ihtiyaç: sadece başlık + slug + yazar adı
GET /api/makaleler
Response: {
id, baslik, slug, excerpt, icerik, gorunumler, olusturuldu,
guncellendi, yazar: { id, ad, email, bio, avatar, olusturuldu, ... },
etiketler: [{ id, isim, slug, renk, ... }],
yorumlar: [...] // 50 yorum, hiç kullanılmayacak
}
// Over-fetching: çok fazla veri geliyor KOPYALA
GraphQL Yaklaşımı
# schema.graphql
type Query {
makaleler ( sayfa : Int, boyut : Int, kategori : String): MakaleSonucu!
makale ( slug : String!): Makale
kullanici ( id : ID!): Kullanici
}
type Mutation {
yorumEkle ( makaleId : ID!, icerik : String!): Yorum!
makaleOlustur ( girdi : MakaleGirdi!): Makale!
}
type Makale {
id : ID!
baslik : String!
slug : String!
excerpt : String!
icerik : String!
gorunumler : Int!
olusturuldu : String!
yazar : Kullanici!
etiketler : [Etiket!]!
yorumlar ( limit : Int): [Yorum!]!
}
type Kullanici {
id : ID!
ad : String!
email : String!
avatar : String
makaleler : [Makale!]!
}
type MakaleSonucu {
veri : [Makale!]!
toplam : Int!
sonSayfa : Int!
} KOPYALA
// Client — sadece ihtiyaç duyulan alanlar
const MAKALE_LISTESI = gql `
query MakalelerGetir($sayfa: Int, $kategori: String) {
makaleler(sayfa: $sayfa, boyut: 10, kategori: $kategori) {
veri {
slug
baslik
excerpt
olusturuldu
yazar {
ad
avatar
}
etiketler {
isim
}
}
toplam
sonSayfa
}
}
` ;
// Makale detay — farklı alanlar
const MAKALE_DETAY = gql `
query MakaleGetir($slug: String!) {
makale(slug: $slug) {
baslik
icerik
olusturuldu
yazar {
ad
avatar
email
makaleler(limit: 3) {
baslik
slug
}
}
yorumlar(limit: 20) {
icerik
olusturuldu
yazar { ad avatar }
}
}
}
` ; KOPYALA
Resolver Yazımı
// resolvers.ts
export const resolvers = {
Query : {
makaleler : async ( _ : unknown , { sayfa = 1 , boyut = 10 , kategori }: {
sayfa ? : number ; boyut ? : number ; kategori ? : string ;
}) => {
const atla = ( sayfa - 1 ) * boyut ;
const where = kategori ? { kategori } : {};
const [ veri , toplam ] = await Promise . all ([
db . makale . findMany ({ where , skip : atla , take : boyut }),
db . makale . count ({ where }),
]);
return { veri , toplam , sonSayfa : Math . ceil ( toplam / boyut ) };
},
makale : ( _ : unknown , { slug }: { slug : string }) =>
db . makale . findUnique ({ where : { slug } }),
},
Makale : {
// Field resolver — sadece istenirse çalışır
yazar : ( makale : { yazarId : string }) =>
db . kullanici . findUnique ({ where : { id : makale . yazarId } }),
etiketler : ( makale : { id : string }) =>
db . etiket . findMany ({ where : { makaleler : { some : { id : makale . id } } } }),
yorumlar : ( makale : { id : string }, { limit = 50 }: { limit ? : number }) =>
db . yorum . findMany ({
where : { makaleId : makale . id },
take : limit ,
orderBy : { olusturuldu : 'asc' },
}),
},
Mutation : {
yorumEkle : async (
_ : unknown ,
{ makaleId , icerik }: { makaleId : string ; icerik : string },
context : { kullanici ? : { id : string } }
) => {
if ( ! context . kullanici ) throw new Error ( 'Giriş yapmanız gerekiyor.' );
return db . yorum . create ({
data : { makaleId , icerik , yazarId : context . kullanici . id },
});
},
},
}; KOPYALA
Karşılaştırma Tablosu
Özellik REST GraphQL Öğrenme eğrisi Düşük Orta-Yüksek Over/Under-fetching Sorun Çözülmüş Cache HTTP cache doğal çalışır Karmaşık, CDN desteği zayıf Dosya yükleme Basit multipart Ek konfigürasyon Real-time Polling/SSE Subscriptions built-in Tip güvenliği OpenAPI/Zod Schema native Debugging Basit (URL + method) Playground gerekli Mobile Eksik veri veya aşırı veri İdeal Microservices Basit Schema stitching karmaşık
Ne Zaman REST, Ne Zaman GraphQL?
REST tercih edin:
▸ Basit CRUD operasyonları
▸ Public API (üçüncü taraf entegrasyon)
▸ Dosya yükleme yoğun işlemler
▸ CDN cache kritikse
▸ Küçük ekip / hızlı prototip
GraphQL tercih edin:
▸ Mobil + web + farklı client'lar aynı API'yi kullanıyorsa
▸ İç API (frontend team kontrolünde)
▸ Çok sayıda ilişkili entity
▸ Real-time (subscriptions)
▸ Farklı ekranlar farklı veri şekilleri istiyorsa
Hibrit Yaklaşım
Büyük platformlar genellikle ikisini birlikte kullanır:
Public API → REST (üçüncü taraf, basit entegrasyon)
Internal API → GraphQL (mobil + web aynı backend)
Admin panel → REST (CRUD işlemleri)
Real-time → WebSocket + GraphQL subscriptions KOPYALA
Sonuç
REST 2000'lerin standardıdır ve basitliğiyle hâlâ geçerliliğini korur. GraphQL ise farklı client'ların farklı veri ihtiyaçlarına tek bir endpoint'ten cevap verme sorununu çözer. Projenizin büyüklüğü, ekip deneyimi ve client çeşitliliği kararın belirleyicisi olmalıdır. İkisi de doğru kullanıldığında harika API'ler üretir.