REST API’den GraphQL’e Geçiş: Ne Zaman Geçmeli?
Yıllarca REST API’lerle çalışıp her şeyin yolunda gittiğini düşünürken bir gün frontend ekibinden şu mesajı alırsınız: “Kardeşim bu endpoint’ten 47 alan geliyor ama biz sadece 3’ünü kullanıyoruz, bir de şu ilişkili datayı almak için 4 ayrı istek daha atıyoruz.” İşte tam o an, GraphQL’in neden var olduğunu anlamaya başlarsınız.
Bu yazıda size hem teorik hem de sahadan gelen gerçek deneyimlerle REST’ten GraphQL’e geçişin ne zaman mantıklı olduğunu, ne zaman gereksiz bir karmaşıklık olduğunu ve geçişi nasıl sağlıklı yapacağınızı anlatacağım.
REST API’nin Getirdiği Acılar
REST API’lerin dünyayı değiştirdiği bir gerçek. Basit, anlaşılır, HTTP’nin doğal yapısına uygun. Ama ölçek büyüdükçe bazı sorunlar kaçınılmaz hale geliyor.
Over-fetching ve Under-fetching
Bu iki kavram, REST’in en bilinen zafiyetleri. Over-fetching, ihtiyaç duyduğunuzdan fazla veri çekmek demek. Bir kullanıcının sadece adını ve emailini göstereceksiniz ama endpoint size profil fotoğrafı, adres, fatura bilgileri, tercihler dahil 30 alan gönderiyor.
Under-fetching ise tam tersi: Tek bir ekranı doldurmak için birden fazla istek atmak zorunda kalmak. Kullanıcı sayfasını açtığınızda önce kullanıcıyı çekiyorsunuz, sonra onun siparişlerini, sonra siparişteki ürünleri. Bu N+1 problemi production’da gerçek bir kabus olabiliyor.
# Klasik REST N+1 problemi örneği
# 1. Kullanıcıyı çek
curl https://api.example.com/users/123
# 2. Kullanıcının siparişlerini çek
curl https://api.example.com/users/123/orders
# 3. Her sipariş için ürün detaylarını çek (10 sipariş = 10 istek)
curl https://api.example.com/orders/456/products
curl https://api.example.com/orders/457/products
# ...ve devam ediyor
Versiyonlama Cehennemi
REST’te API versiyonlamak zamanla büyük bir yük haline gelir. /v1/users, /v2/users, /v3/users… Eski versiyonları ne zaman kapatacaksınız? Hangi istemciler hala v1 kullanıyor? Kimse bilmiyor.
# Farklı versiyonlar için ayrı endpoint'ler
curl https://api.example.com/v1/products/789
curl https://api.example.com/v2/products/789
curl https://api.example.com/v3/products/789
# Her birinin response formatı farklı, dökümantasyon ayrı, bakım maliyeti katlanıyor
GraphQL Nedir ve Ne Vaat Eder?
GraphQL, Facebook’un 2015’te açık kaynak olarak yayınladığı bir sorgu dili ve runtime’ı. Temel fikir şu: İstemci tam olarak neye ihtiyacı varsa onu ister, fazlasını değil.
Tek bir endpoint üzerinden çalışır, genellikle /graphql. İstemci hangi alanları istediğini, hangi ilişkileri dahil etmek istediğini schema üzerinde tanımlanmış tipler çerçevesinde belirtir.
# GraphQL ile tek istekte ihtiyacınız olan her şey
curl -X POST https://api.example.com/graphql
-H "Content-Type: application/json"
-d '{
"query": "{
user(id: "123") {
name
email
orders(last: 5) {
id
total
products {
name
price
}
}
}
}"
}'
Tek istek, tam ihtiyacınız olan veri. N+1 sorunu yok, fazladan veri yok.
Ne Zaman Geçmeli? Sinyaller ve Senaryolar
Şimdi asıl soruya gelelim. GraphQL her projeye uygun değil. Geçiş kararını doğru verebilmek için bazı kritik sinyallere dikkat etmek lazım.
Sinyal 1: Birden Fazla İstemci Tipi Var
Eğer aynı API’yi web uygulaması, iOS uygulaması, Android uygulaması ve belki de bir TV uygulaması tüketiyorsa, GraphQL’in parlaması için ideal zemin var demektir.
Her istemcinin veri ihtiyacı farklıdır. Mobil uygulama bant genişliği konusunda hassastır, sadece ekranda görünecek alanları ister. Web uygulaması belki daha fazla detay kullanabilir. REST’te ya herkese minimum veriyi verirsiniz ya da herkese maksimum veriyi, ikisi de optimal değil.
# Mobile için gereken minimal veri
curl -X POST https://api.example.com/graphql
-H "Content-Type: application/json"
-d '{
"query": "{
products {
id
name
thumbnailUrl
price
}
}"
}'
# Web için daha zengin veri - aynı endpoint, farklı query
curl -X POST https://api.example.com/graphql
-H "Content-Type: application/json"
-d '{
"query": "{
products {
id
name
fullDescription
images
price
stock
reviews { rating author }
relatedProducts { id name }
}
}"
}'
Sinyal 2: Frontend Ekibi Bağımsızlık İstiyor
Backend API değişikliği beklemeden frontend’in kendi ihtiyacına göre veri çekebilmesi, geliştirme hızını ciddi oranda artırır. GraphQL schema’sı bir kontrat görevi görür ve bu kontrat değişmediği sürece her iki taraf bağımsız çalışabilir.
Sinyal 3: Gerçek Zamanlı Veri İhtiyacı Var
GraphQL’in Subscription özelliği, WebSocket tabanlı gerçek zamanlı veri akışını birinci sınıf vatandaş olarak destekler. Bildirimler, canlı feed’ler, dashboard güncellemeleri için REST’te polling veya ayrı WebSocket altyapısı kurmak yerine GraphQL Subscription’ları kullanabilirsiniz.
// GraphQL Subscription örneği
const PRICE_SUBSCRIPTION = `
subscription OnPriceUpdate($productId: ID!) {
priceUpdated(productId: $productId) {
productId
oldPrice
newPrice
updatedAt
}
}
`;
Sinyal 4: API Dökümantasyonu Sürekli Güncellenmiyorsa
GraphQL’in introspection özelliği sayesinde schema kendi kendini belgeler. GraphiQL veya Apollo Studio gibi araçlarla interaktif dökümantasyon otomatik olarak hazır olur. “Bu endpoint ne döndürüyor?” sorusunu artık kimse sormaz.
# GraphQL introspection ile schema'yı sorgula
curl -X POST https://api.example.com/graphql
-H "Content-Type: application/json"
-d '{
"query": "{
__schema {
types {
name
kind
description
fields {
name
type { name }
}
}
}
}"
}'
Ne Zaman Geçmemeli?
Dürüst olmak gerekirse, GraphQL her durumda doğru cevap değil.
Basit CRUD Uygulamaları
Eğer uygulamanız temelden CRUD operasyonları yapıyorsa ve veri modeli karmaşık ilişkiler içermiyorsa, GraphQL’in getirdiği öğrenme eğrisi ve altyapı maliyeti karşılık vermeyebilir. Küçük bir admin paneli, basit bir blog API’si, tek istemcili bir iç araç için REST yeterlidir ve daha az karmaşıktır.
Dosya Transferi Ağırlıklı Sistemler
GraphQL metin tabanlı JSON veri alışverişi için tasarlanmış. Büyük dosya yüklemeleri ve indirmeleri için REST’in multipart form data desteği çok daha doğal bir çözüm sunar.
Yüksek Cache Gereksinimleri
REST’in HTTP cache mekanizmaları inanılmaz güçlü. CDN’ler, browser cache’leri, reverse proxy’ler URL bazlı cache’leme konusunda onlarca yıllık optimize altyapıya sahip. GraphQL POST istekleri varsayılan olarak cache’lenmez ve bu durum yüksek trafik altında önemli bir dezavantaj olabilir. Persisted queries gibi çözümler var ama ek karmaşıklık getiriyor.
Geçiş Stratejisi: Büyük Patlama Yerine Kademeli Yaklaşım
Mevcut bir REST API’yi bir gecede GraphQL’e dönüştürmeye çalışmak felakete davetiye çıkarmaktır. Doğru yaklaşım kademeli geçiş.
Yaklaşım 1: GraphQL as a Gateway
En pragmatik yaklaşım, mevcut REST servislerinizin önüne bir GraphQL gateway koymak. Backend’i değiştirmiyorsunuz, sadece GraphQL bir çeviri katmanı görevi görüyor.
# Apollo Server ile basit bir gateway kurulumu
npm install @apollo/server graphql node-fetch
# Server dosyasını oluştur
cat > server.js << 'EOF'
const { ApolloServer, gql } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
const fetch = require('node-fetch');
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
orders: [Order]
}
type Order {
id: ID!
total: Float!
status: String!
}
type Query {
user(id: ID!): User
users: [User]
}
`;
const resolvers = {
Query: {
user: async (_, { id }) => {
const response = await fetch(`https://legacy-api.example.com/users/${id}`);
return response.json();
},
},
User: {
orders: async (user) => {
const response = await fetch(`https://legacy-api.example.com/users/${user.id}/orders`);
return response.json();
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`GraphQL server running at ${url}`);
EOF
Yaklaşım 2: Strangler Fig Pattern
Martin Fowler’ın önerdiği bu pattern ile yeni özellikleri doğrudan GraphQL’de geliştirirken eski REST endpoint’lerini olduğu gibi bırakırsınız. Zamanla tüketim REST’ten uzaklaştıkça eski endpoint’leri devre dışı bırakırsınız.
# Nginx ile ikili yönlendirme - eski REST ve yeni GraphQL yan yana
cat > /etc/nginx/sites-available/api.conf << 'EOF'
upstream rest_backend {
server 127.0.0.1:3000;
}
upstream graphql_backend {
server 127.0.0.1:4000;
}
server {
listen 443 ssl;
server_name api.example.com;
# Eski REST endpoint'leri - legacy istemciler için
location /v1/ {
proxy_pass http://rest_backend;
proxy_set_header X-Forwarded-For $remote_addr;
}
location /v2/ {
proxy_pass http://rest_backend;
proxy_set_header X-Forwarded-For $remote_addr;
}
# Yeni GraphQL endpoint
location /graphql {
proxy_pass http://graphql_backend;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
EOF
nginx -t && systemctl reload nginx
Performans İzleme ve Karşılaştırma
Geçiş sürecinde performansı sürekli izlemek kritik. Özellikle resolver’larınızın veritabanına nasıl yüklendiğini takip etmeniz gerekiyor.
# Apollo Studio için temel performans metrikleri toplanması
# .env dosyasına Apollo key ekle
echo "APOLLO_KEY=service:myapp:xxxxxxxx" >> .env
# DataLoader ile N+1 problemini çöz
cat > dataloader-example.js << 'EOF'
const DataLoader = require('dataloader');
// Batch function - tek DB sorgusu ile birden fazla kullanıcı
const userLoader = new DataLoader(async (userIds) => {
const users = await db.query(
'SELECT * FROM users WHERE id = ANY($1)',
[userIds]
);
// ID sırasına göre map
return userIds.map(id => users.find(u => u.id === id));
});
// Resolver'da kullan
const resolvers = {
Order: {
user: (order) => userLoader.load(order.userId)
}
};
EOF
Schema Tasarımı: REST Endpoint’lerini Körce Kopyalamayın
En yaygın hata, REST resource’larını bire bir GraphQL type’larına çevirmek. GraphQL schema’sı kullanım senaryosu etrafında tasarlanmalı.
# Kötü yaklaşım - REST endpoint mantığını GraphQL'e taşımak
# GET /users/:id -> query { user(id) }
# GET /users/:id/orders -> query { userOrders(userId) }
# GET /orders/:id/products -> query { orderProducts(orderId) }
# Bunlar birbirinden kopuk, N+1 problemi devam ediyor
# İyi yaklaşım - Use case etrafında tasarım
cat > schema-good.graphql << 'EOF'
type Query {
# Kullanıcı profil sayfası için tek sorgu
userProfile(id: ID!): UserProfile
# Sipariş listesi sayfası için optimize edilmiş sorgu
orderDashboard(userId: ID!, status: OrderStatus): OrderDashboard
}
type UserProfile {
user: User!
recentOrders: [Order!]!
totalSpent: Float!
membershipLevel: MembershipLevel!
}
type OrderDashboard {
orders: [Order!]!
totalCount: Int!
pendingCount: Int!
summary: OrderSummary!
}
EOF
Güvenlik Konuları
GraphQL’in esnekliği bazı güvenlik açıklarına da kapı açabiliyor. Geçiş yaparken bunları göz ardı etmeyin.
# Sorgu derinliği sınırlama - sonsuz nested query saldırılarını engelle
npm install graphql-depth-limit
cat > security-config.js << 'EOF'
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(7), // Maksimum 7 seviye derinlik
createComplexityLimitRule(1000) // Maksimum query karmaşıklığı
],
// Introspection'ı production'da kapat
introspection: process.env.NODE_ENV !== 'production',
});
EOF
# Rate limiting için Nginx konfigürasyonu
cat >> /etc/nginx/sites-available/api.conf << 'EOF'
limit_req_zone $binary_remote_addr zone=graphql:10m rate=30r/m;
location /graphql {
limit_req zone=graphql burst=10 nodelay;
proxy_pass http://graphql_backend;
}
EOF
Gerçek Dünya Senaryosu: E-ticaret Geçişi
Bir e-ticaret platformunda çalıştığınızı düşünün. Ürün listeleme sayfası için şu durumda olduğunuzu hayal edin:
Mevcut REST akışı:
/productsendpoint’i 200 ürün döndürüyor, her biri 35 alan içeriyor- Her ürün için ayrı
/products/:id/reviews/summaryçağrısı - Her ürün için
/inventory/:skuçağrısı - Toplam 401 HTTP isteği, sayfa yüklenme süresi 4.2 saniye
GraphQL sonrası:
- Tek sorgu, sadece gerekli 8 alan, iç içe reviews summary ve inventory
- Toplam 1 HTTP isteği, sayfa yüklenme süresi 0.8 saniye
- Mobil dönüşüm oranında belirgin artış
Bu rakamlar gerçekçi ve şaşırtıcı değil. Asıl şaşırtıcı olan, geçişin ne kadar kademeli ve az riskli yapılabildiği.
Araçlar ve Ekosistem
Geçiş kararı verdikten sonra doğru araçları seçmek önemli.
Apollo GraphQL: En olgun ekosistem. Apollo Server, Apollo Client, Apollo Studio birlikte güçlü bir stack oluşturuyor. Enterprise özellikleri var ama bazıları ücretli.
GraphQL Yoga: Daha hafif, framework agnostik. Hızlı başlamak için ideal.
Hasura: Eğer PostgreSQL kullanıyorsanız, veritabanınızın üzerine otomatik GraphQL API oluşturuyor. Gerçek zamanlı subscription’lar dahil. Bazı özel iş mantığı senaryolarında kısıtlayıcı olabiliyor.
Strawberry veya Graphene: Python ekosistemi için. FastAPI ile güzel çalışıyor.
# Hasura ile hızlı prototip
docker run -d -p 8080:8080
-e HASURA_GRAPHQL_DATABASE_URL=postgres://user:pass@host/dbname
-e HASURA_GRAPHQL_ENABLE_CONSOLE=true
-e HASURA_GRAPHQL_DEV_MODE=true
hasura/graphql-engine:latest
# Artık http://localhost:8080/console adresinden
# otomatik oluşturulan GraphQL API'nizi yönetebilirsiniz
Takım ve Süreç Değişiklikleri
Teknik geçişin yanı sıra takım süreçleri de değişmeli. Frontend ve backend ekipleri arasındaki koordinasyon şekli değişiyor.
Schema önce tasarım yaklaşımı öneriyorum: Backend resolver’ları yazmadan önce schema üzerinde anlaşın. Frontend mock data ile geliştirmeye başlayabilir, backend gerçek implementasyonu tamamlarken.
Code review süreçlerinize schema değişikliklerini ekleyin. Schema breaking change’ler production’ı bozabilir. @deprecated directive’ini aktif kullanın, eski field’ları hemen silmeyin.
# Schema validation CI/CD pipeline'a ekle
npm install -g @graphql-inspector/cli
# Breaking change kontrolü
graphql-inspector diff
schema-v1.graphql
schema-v2.graphql
# Çıktı örneği:
# ✖ Field 'User.phone' was removed (BREAKING)
# ✔ Field 'User.phoneNumber' was added
# ℹ Field 'User.email' description changed
Sonuç
REST’ten GraphQL’e geçiş kararı, sadece teknik bir tercih değil, organizasyonel bir karar. Doğru zamanda yapıldığında geliştirme hızını artırır, ağ maliyetlerini düşürür, frontend ekiplerine özerklik kazandırır ve API’nizin evrimini çok daha yönetilebilir kılar.
Yanlış zamanda, yanlış motivasyonla yapıldığında ise gereksiz karmaşıklık, performans sorunları ve hayal kırıklığıyla sonuçlanır.
Geçiş için doğru sinyaller şunlar: Birden fazla istemci tipi, N+1 sorunu yaratan karmaşık veri ilişkileri, frontend ekibinin API bağımlılıklarından kurtulmak istemesi ve gerçek zamanlı veri ihtiyacı. Bu sinyaller güçlüyse ve takımınız öğrenme sürecini kaldırabilecek kapasitedeyse, kademeli bir geçiş stratejisiyle ilerlemek mantıklı.
Büyük patlama yaklaşımından kaçının. Gateway pattern ile başlayın, yeni özellikleri GraphQL’de geliştirin, eski endpoint’leri zamanla emekli edin. DataLoader ile N+1 problemini çözün, sorgu derinliği ve karmaşıklık limitleriyle güvenliği ihmal etmeyin.
En önemlisi: GraphQL bir hedef değil, araç. Kullanıcıya değer katacak ürünü daha hızlı çıkarmak için kullanılıyor. Bunu aklınızdan çıkarmayın.
