Yanıtın şeklini istemci yazar
Hangi alanların döneceğine sorgu karar verir; dönen JSON sorgunun yapısını birebir yansıtır. Fazlası da eksiği de gelmez.
Klasik bir REST API'de her uç nokta ne döneceğine sunucu karar verir; istemci çoğu zaman ihtiyacından fazlasını (over-fetching) ya da azını (under-fetching) alır ve eksiği kapatmak için birkaç istek daha atar. GraphQL bu dengeyi tersine çevirir: tek bir endpoint vardır, istemci tam olarak hangi alanları (field) istediğini bir sorgu (query) ile yazar, sunucu da yalnızca istenen şekli döner. Bunu mümkün kılan şey, API'nin sunduğu her tipi ve alanı önceden tanımlayan bir schema'dır — hem sözleşme hem de canlı dokümantasyon. Bu sayfada GraphQL'in REST'ten farkını, schema ve tip sistemini, sorguların resolver'larla nasıl çözüldüğünü, çözdüğü over-fetching sorununu ve beraberinde getirdiği N+1 ve query complexity gibi yeni başlıkları birlikte ele alıyoruz. Genel API kavramları için API Tasarımı & Entegrasyon rehberimizi öneririz.
GraphQL bir veritabanı ya da ürün değil, bir API sorgu dili ve onu çalıştıran bir sunucu ortamıdır. REST'te her kaynak için ayrı bir URL vardır ve her URL sabit bir yanıt döner; GraphQL'de ise tek bir endpoint'e (genellikle /graphql) bir sorgu gönderirsiniz ve yanıtın şeklini sorgu belirler. İstediğiniz alanları yazarsınız, iç içe ilişkileri tek istekte çekersiniz, dönen JSON tam da sorgunuzun aynası olur. Bunun bedeli, sunucunun bu esnekliği güvenli biçimde karşılayacak şekilde tasarlanmasıdır: istemciye "ne istersen sor" demek, aynı zamanda "her sorunun maliyetini kontrol etmem gerek" demektir.
# REST: sabit yanıt + birkaç istek
GET /users/12 → { ...tüm alanlar }
GET /users/12/orders → [ ...tüm alanlar ]
# GraphQL: tek istek, istenen alanlar
POST /graphql
{ user(id: 12) {
ad
orders { tutar } } }
→ { "user": { "ad": "...", "orders": [...] } }
Hangi alanların döneceğine sorgu karar verir; dönen JSON sorgunun yapısını birebir yansıtır. Fazlası da eksiği de gelmez.
Birbirine bağlı kaynakları (kullanıcı → siparişleri → ürünleri) ayrı isteklerle değil, tek sorguda iç içe çekersiniz.
İstemciye açık uçlu sorgu hakkı vermek, her sorgunun maliyetini ve derinliğini sunucuda sınırlamayı zorunlu kılar.
GraphQL'in kalbi schema'dır: API'nin sunduğu her tipi (type), her alanı ve her alanın türünü önceden tanımlayan güçlü tipli bir sözleşme. Genellikle SDL (Schema Definition Language) ile yazılır. Bir alan zorunlu mu, liste mi, hangi tipe bağlı — hepsi burada bellidir; bu yüzden sunucu gelen sorguyu çalıştırmadan önce doğrulayabilir, geçersiz alanları reddeder. Schema aynı zamanda introspection sayesinde kendi kendini anlatır: araçlar API'yi sorgulayarak otomatik dokümantasyon ve otomatik tamamlama üretir. Her sorgunun bir giriş noktası vardır — okuma için Query, yazma için Mutation, canlı akış için Subscription.
type User {
id: ID! # ! = zorunlu
ad: String!
orders: [Order!] # liste
}
type Query {
user(id: ID!): User
}
Sunucu, sorguyu çalıştırmadan schema'ya göre doğrular. Var olmayan bir alan ya da yanlış tip, veri katmanına inmeden reddedilir.
Introspection sayesinde schema sorgulanabilir; dokümantasyon ve editör otomatik tamamlaması elle güncellenmeden hep güncel kalır.
Query okur, Mutation değiştirir, Subscription canlı akış verir. Bu ayrım, niyeti API seviyesinde net kılar.
Bir sorgudaki her alanın arkasında bir resolver vardır: o alanın değerini nasıl getireceğini bilen küçük bir fonksiyon. Sunucu sorguyu bir ağaç gibi gezer; her alan için ilgili resolver'ı çağırır ve sonuçları sorgunun şekline göre birleştirir. Alanlar argument alabilir (user(id: 12)), tekrar eden alan kümelerini fragment ile paylaşabilir, sorguyu variable'larla parametreleyebilirsiniz. Kritik nokta şudur: iç içe her alan ayrı bir resolver tetikleyebilir; sorgu masumca derinleştikçe arkada çalışan iş katlanarak artabilir. Resolver'ların ne yaptığını görünür kılmak için her alanı izlemek erken uyarı sağlar.
# istemcinin sorgusu
query {
user(id: 12) { → Query.user resolver
ad → User.ad resolver
orders { → User.orders resolver
tutar → Order.tutar resolver
}
}
}
# yanıt: sorgunun aynası
{ "user": { "ad": "Ada",
"orders": [ { "tutar": 250 } ] } }
user(id: 12), orders(first: 5)). Filtreleme, sayfalama ve arama bunun üzerinden yürür.GraphQL'in en somut vaadi over-fetching (gereğinden çok veri) ve under-fetching'i (eksik veri, ardından ek istekler) ortadan kaldırmasıdır: istemci tam olarak ihtiyacını ister, ne fazlası ne azı. Ama esnek iç içe sorgular yeni bir tuzak açar — N+1 problemi. Bir listedeki her öğe için ilişkili veriyi ayrı ayrı çeken naif bir resolver, tek sorguyu yüzlerce veritabanı çağrısına dönüştürür. Çözüm resolver'ları toplu çalıştırmaktır: DataLoader deseni, aynı tik içinde biriken istekleri tek bir toplu sorguda birleştirir (batching) ve tekrarları önbelleğe alır. Altta yatan veri katmanının nasıl sorgulandığı burada belirleyicidir; veritabanı ve önbellekleme tarafı GraphQL performansının gerçek zeminidir.
İstemci alan listesini kendi yazdığı için ne fazla veri taşınır ne de eksik kalıp ek istek gerekir. Mobilde bant genişliği kazancı belirgindir.
Listedeki her öğe için ayrı sorgu atan resolver, tek GraphQL isteğini yüzlerce veritabanı çağrısına çevirir. Sessizce performansı çökertir.
Aynı tikte biriken istekleri tek toplu sorguda birleştirir ve tekrarları önbelleğe alır. N+1'i tek sorguya indirir.
Query yalnızca okur; veriyi değiştirmek için mutation kullanılır — kayıt oluşturma, güncelleme, silme. Mutation da bir sorgu gibi yazılır ama niyeti nettir ve yan etkilidir; iyi bir pratik, mutation'ın değişen nesneyi geri döndürmesidir, böylece istemci ek istek atmadan güncel hâli alır. Gerçek zamanlı senaryolar için subscription vardır: istemci bir olaya abone olur (yeni mesaj, fiyat değişimi) ve sunucu genellikle bir WebSocket üzerinden güncellemeleri iter. Subscription güçlüdür ama en maliyetli parçadır: açık bağlantı yönetimi, ölçekleme ve durum takibi gerektirir; çoğu uygulama için birkaç kritik akışla sınırlı tutmak doğru olur.
mutation {
siparisOlustur(urun: "A12", adet: 2) {
id # değişen nesneyi
tutar # geri döndür →
durum # ek istek gerekmez
}
}
# subscription: sunucu iter
subscription { yeniMesaj { metin } }
Yazma sonrası güncel hâli yanıtta vermek, istemcinin ayrıca bir query atmasını gereksiz kılar ve arayüzü tek tur içinde tutarlı tutar.
Kalıcı bağlantılar ölçekleme ve durum yükü getirir. Gerçekten canlı olması gereken birkaç akışa saklayın; gerisi query/polling ile daha ucuzdur.
Her mutation gerçek bir eylemdir; kimin neyi değiştirebileceği kimlik & erişim katmanında netçe sınırlanmalıdır.
İstemciye "ne istersen sor" demek, kötü niyetli ya da dikkatsiz bir sorgunun sunucuyu zorlayabileceği anlamına gelir: derin iç içe geçmiş bir sorgu tek istekte devasa bir iş yaratabilir. Bu yüzden GraphQL güvenliği REST'ten farklı önlemler ister. Temel çerçeve nettir: query complexity ve depth limiting ile sorgunun maliyetini ve derinliğini sınırlayın, rate limiting'i istek sayısına değil sorgu maliyetine göre uygulayın, üretimde persisted queries ile yalnızca önceden onaylı sorgulara izin verin ve gereksizse introspection'ı kapatın. Bir de GraphQL'in doğal zayıflığı vardır: her yanıt aynı endpoint'ten ve çoğunlukla POST ile geldiği için HTTP önbelleklemesi REST kadar kolay değildir; önbellek çoğunlukla istemci ve alan seviyesine taşınır.
Her sorguya bir maliyet puanı ve derinlik sınırı koyun; belirlenen eşiği aşan sorgu çalıştırılmadan reddedilsin. Sonsuz iç içe geçmeyi baştan keser.
İstek sayısını değil, sorgunun hesaplanan maliyetini kısıtlayın. Tek "istek" çok pahalı olabildiği için REST tarzı sayaç GraphQL'de yanıltır.
Üretimde yalnızca önceden kaydedilip onaylanmış sorgulara izin verin. Keyfi sorgu yüzeyini kapatır, ağ yükünü de düşürür.
Schema'yı dışarıya açık tutmak saldırgana harita verir. Herkese açık olması gerekmeyen API'lerde üretimde introspection'ı kapatmayı değerlendirin.
REST kaynak başına ayrı URL sunar ve yanıtın şeklini sunucu belirler; basit, iyi önbelleklenir, olgundur. GraphQL tek endpoint'ten esnek, istemcinin şekillendirdiği sorgular sunar; over-fetching'i keser ama complexity ve önbellek yönetimini size bırakır. Biri diğerinin yerini almaz; ihtiyaca göre seçilir, hatta bir arada kullanılır.
gRPC yüksek performanslı, sözleşmeli (Protobuf) bir uzak çağrı (RPC) çerçevesidir; genellikle servisler arası iç iletişimde parlar. GraphQL ise istemcinin veriyi esnek biçimde şekillendirdiği, çoğunlukla dış/istemci-yönlü bir sorgu dilidir. Kabaca: gRPC servisler arası hız, GraphQL istemci-odaklı esneklik için.
Query yalnızca okur ve yan etkisizdir; paralel çalıştırılabilir. Mutation veriyi değiştirir, yan etkilidir ve sırayla çalışır. İkisi de aynı sözdizimiyle yazılır; fark niyet ve garantidedir. Okuma yaptığınız bir işi mutation'a koymak, GraphQL'in verdiği güvenceleri boşa çıkarır.
Schema API'nin ne sunduğunu tarif eder: tipler, alanlar, türleri. Resolver ise her alanın değerinin nasıl getirileceğini yürütür. Schema sözleşme, resolver uygulamadır; ikisi eşleşmezse API ya doğrulamada ya çalışma anında kırılır.
İkisi de veri getirme verimliliğiyle ilgilidir ama zıt uçlarda. Over-fetching ağ üzerinde çok fazla veri taşımaktır; GraphQL bunu istemciye alan seçtirerek çözer. N+1 ise sunucuda çok fazla ayrı sorgu çalıştırmaktır; GraphQL bunu getirir ve DataLoader/batching ile çözersiniz. Biri bant genişliği, diğeri sunucu iş yükü sorunudur.
GraphQL'in resmi spesifikasyonu, öğrenme rehberi ve REST'in temelini tanımlayan çalışma.
GraphQL mı REST mi, schema tasarımından N+1 önlemeye ve query complexity sınırlarına kadar; projenize uygun API mimarisini kurmak için bize yazabilirsiniz. Genel API kavramları için API Tasarımı & Entegrasyon rehberimize de göz atabilirsiniz.