GraphQL Nedir: REST API ile Karşılaştırmalı Rehber
API geliştirme dünyasında bir şeyler değişiyor. Yıllarca REST API’ler altın standart olarak kabul gördü, ancak modern uygulamaların karmaşıklaşmasıyla birlikte geliştiriciler bazı sınırlamalarla karşılaşmaya başladı. Mobil uygulamalar farklı veri ihtiyaçları duyuyor, mikroservis mimarileri karmaşıklaşıyor ve frontend ekipleri backend’den bağımsız hareket etmek istiyor. İşte tam bu noktada GraphQL sahneye çıkıyor. Facebook’un 2012’de geliştirip 2015’te açık kaynak olarak yayınladığı bu sorgu dili, API tasarımına bambaşka bir perspektif getiriyor.
GraphQL Nedir?
GraphQL, bir API sorgu dili ve bu dil için bir çalışma zamanıdır. REST’in aksine, GraphQL’de tek bir endpoint bulunur ve istemci tam olarak hangi veriyi istediğini belirtir. Sunucu sadece istenen veriyi döner, fazlasını değil.
Bunu şöyle düşünebilirsiniz: REST API bir restoran menüsü gibidir, siz “Menü 2” dersiniz ve ne gelirse gelir. GraphQL ise açık büfe gibidir, tabağınıza tam olarak ne istediğinizi koyarsınız.
GraphQL’in üç temel operasyonu vardır:
- Query: Veri okuma işlemleri için kullanılır
- Mutation: Veri yazma, güncelleme ve silme işlemleri için kullanılır
- Subscription: Gerçek zamanlı veri akışı için kullanılır
REST API’nin Sorunları: Gerçek Dünya Senaryoları
Bir e-ticaret uygulaması düşünelim. Kullanıcı profil sayfasını açtığında şu bilgileri görmek istiyor: kullanıcı adı, son 3 sipariş ve her siparişin ürün adları. REST ile bu işlemi nasıl yaparsınız?
# Önce kullanıcı bilgilerini çek
GET /api/users/123
# Sonra siparişleri çek
GET /api/users/123/orders
# Her sipariş için ürün detaylarını çek
GET /api/orders/456/products
GET /api/orders/457/products
GET /api/orders/458/products
Bu 5 ayrı HTTP isteği demektir. Üstelik /api/users/123 endpoint’i size kullanıcının doğum tarihi, adresi, telefon numarası gibi onlarca gereksiz alanı da döner. İşte bu iki klasik REST problemi:
- Over-fetching: İhtiyacınızdan fazla veri çekmek
- Under-fetching: Tek istekle yeterli veri gelememesi, birden fazla istek yapmak zorunda kalmak
GraphQL ile Aynı Senaryoyu Çözmek
query GetUserProfile {
user(id: "123") {
username
orders(limit: 3) {
id
createdAt
products {
name
}
}
}
}
Tek istek, sadece istenen veri. Sunucunun döneceği yanıt tam olarak istediğiniz şekilde olur. Fazlası yok, eksiği yok.
GraphQL Şema Tasarımı
GraphQL’in kalbi Schema Definition Language (SDL) ile tanımlanan şemadır. Şema, API’nizin sözleşmesidir ve hangi veri tiplerine sahip olduğunuzu, bu tiplerin nasıl ilişkili olduğunu ve hangi sorguların yapılabileceğini tanımlar.
# Temel tip tanımları
type User {
id: ID!
username: String!
email: String!
createdAt: String!
orders: [Order!]!
}
type Order {
id: ID!
totalPrice: Float!
status: OrderStatus!
createdAt: String!
products: [Product!]!
}
type Product {
id: ID!
name: String!
price: Float!
stock: Int!
}
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}
# Root tipler
type Query {
user(id: ID!): User
users: [User!]!
order(id: ID!): Order
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateOrder(id: ID!, status: OrderStatus!): Order!
}
input CreateUserInput {
username: String!
email: String!
password: String!
}
Şemada ! işareti alanın null olamayacağını belirtir. [Order!]! ifadesi ise hem listenin hem de liste içindeki her elemanın null olamayacağı anlamına gelir.
Node.js ile Basit GraphQL Sunucusu Kurmak
Pratik bir örnek görelim. Apollo Server kullanarak basit bir GraphQL API kuralım:
# Proje dizini oluştur ve bağımlılıkları yükle
mkdir graphql-demo && cd graphql-demo
npm init -y
npm install apollo-server graphql
npm install --save-dev nodemon
// server.js
const { ApolloServer, gql } = require('apollo-server');
// Şema tanımı
const typeDefs = gql`
type Post {
id: ID!
title: String!
content: String!
author: User!
publishedAt: String
tags: [String!]!
}
type User {
id: ID!
username: String!
email: String!
posts: [Post!]!
}
type Query {
posts: [Post!]!
post(id: ID!): Post
user(id: ID!): User
}
type Mutation {
createPost(
title: String!
content: String!
authorId: ID!
tags: [String!]
): Post!
}
`;
// Mock veri
const users = [
{ id: '1', username: 'ahmet_dev', email: '[email protected]' },
{ id: '2', username: 'fatma_sys', email: '[email protected]' }
];
const posts = [
{
id: '1',
title: 'GraphQL ile API Tasarımı',
content: 'GraphQL modern API geliştirmenin...',
authorId: '1',
publishedAt: '2024-01-15',
tags: ['graphql', 'api', 'backend']
}
];
// Resolver'lar
const resolvers = {
Query: {
posts: () => posts,
post: (_, { id }) => posts.find(p => p.id === id),
user: (_, { id }) => users.find(u => u.id === id)
},
Post: {
author: (post) => users.find(u => u.id === post.authorId)
},
User: {
posts: (user) => posts.filter(p => p.authorId === user.id)
},
Mutation: {
createPost: (_, { title, content, authorId, tags }) => {
const newPost = {
id: String(posts.length + 1),
title,
content,
authorId,
publishedAt: new Date().toISOString(),
tags: tags || []
};
posts.push(newPost);
return newPost;
}
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen({ port: 4000 }).then(({ url }) => {
console.log(`GraphQL sunucusu calisiyor: ${url}`);
});
# Sunucuyu başlat
node server.js
# Test için curl ile sorgu gönder
curl -X POST http://localhost:4000/
-H "Content-Type: application/json"
-d '{
"query": "{ posts { id title author { username } tags } }"
}'
Resolver Zinciri ve N+1 Problemi
GraphQL’e yeni başlayanların sıkça düştüğü bir tuzak vardır: N+1 problemi. 10 post çektiğinizde, her postun yazarı için ayrı bir veritabanı sorgusu çalışır. Bu 1 + 10 = 11 sorgu demektir.
# N+1 problemi olmadan önce veritabanı loglarına bakın
# Her post için ayrı SELECT sorgusu göreceksiniz
# SELECT * FROM users WHERE id = '1'
# SELECT * FROM users WHERE id = '2'
# SELECT * FROM users WHERE id = '1' <- Tekrar!
Bunu çözmek için DataLoader kullanılır:
const DataLoader = require('dataloader');
// User DataLoader oluştur - aynı request içinde toplu sorgu yapar
const createUserLoader = () => new DataLoader(async (userIds) => {
// Tüm kullanıcıları tek sorguda getir
const users = await getUsersByIds(userIds);
// DataLoader sıralamayı korumak ister
return userIds.map(id => users.find(u => u.id === id));
});
// Her request için yeni loader oluştur
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => ({
userLoader: createUserLoader()
})
});
// Resolver'da kullan
const resolvers = {
Post: {
author: (post, _, { userLoader }) => {
return userLoader.load(post.authorId);
}
}
};
Artık 10 post için tek bir veritabanı sorgusu çalışır.
REST vs GraphQL: Temel Farklar
Her iki yaklaşımın güçlü ve zayıf yönlerini somut başlıklar altında inceleyelim.
Endpoint Yapısı
REST API’de her kaynak için ayrı endpoint tanımlanır:
- GET /users: Tüm kullanıcılar
- GET /users/:id: Tek kullanıcı
- POST /users: Kullanıcı oluştur
- PUT /users/:id: Kullanıcı güncelle
- DELETE /users/:id: Kullanıcı sil
GraphQL’de tek endpoint vardır:
- POST /graphql: Her şey buradan geçer
Versiyonlama
REST API’lerde versiyonlama büyük bir sorundur. /api/v1/users, /api/v2/users gibi endpoint’ler zamanla karmaşık bir hal alır.
GraphQL’de versiyonlama tipik olarak gerekli değildir. Şemaya yeni alanlar ekleyebilirsiniz, eski alanları @deprecated direktifi ile işaretleyebilirsiniz:
type User {
id: ID!
username: String!
# Eski alan, artık fullName kullanılsın
name: String @deprecated(reason: "fullName alanini kullanin")
fullName: String!
email: String!
}
Tip Güvenliği
GraphQL’in en güçlü özelliklerinden biri yerleşik tip sistemidir. Her alan, her argüman tiplenmiştir. Bu sayede:
- İstemci tarafında: Sorgu gönderilmeden önce hatalar yakalanır
- Geliştirme ortamında: IDE otomatik tamamlama sağlar
- Dokümantasyon: Şema otomatik olarak canlı dokümantasyon görevi görür
Gerçek Dünya Senaryosu: Mikroservis Mimarisi
Bir fintech uygulaması düşünelim. Kullanıcı, hesap, işlem ve bildirim olmak üzere 4 ayrı mikroservis var. REST ile bunu yönetmek frontend ekibi için kabus olabilir.
GraphQL Federation veya basit bir API Gateway yaklaşımıyla tüm servisleri tek bir şema altında birleştirebilirsiniz:
# Gateway şeması - tüm servisleri birleştirir
type Query {
# User Service'ten
me: User
# Account Service'ten
account(id: ID!): Account
# Transaction Service'ten
transactions(
accountId: ID!
startDate: String
endDate: String
limit: Int = 20
): [Transaction!]!
}
type User {
id: ID!
fullName: String!
email: String!
accounts: [Account!]!
notifications: [Notification!]!
}
type Account {
id: ID!
iban: String!
balance: Float!
currency: String!
transactions: [Transaction!]!
}
type Transaction {
id: ID!
amount: Float!
type: TransactionType!
description: String
createdAt: String!
}
enum TransactionType {
DEBIT
CREDIT
TRANSFER
}
Bu yapıyla frontend geliştiricisi tek bir sorguda ihtiyacı olan her şeyi alabilir:
query Dashboard {
me {
fullName
accounts {
iban
balance
currency
transactions(limit: 5) {
amount
type
description
createdAt
}
}
}
}
GraphQL Güvenlik Konuları
Güvenlik, GraphQL’de REST’ten daha karmaşık olabilir. Dikkat edilmesi gereken başlıca noktalar:
- Sorgu derinlik sınırı: Kötü niyetli sorgular çok derin iç içe geçmiş veri talep edebilir
- Sorgu karmaşıklık sınırı: Çok fazla alan talep eden sorgular sunucuyu yorabilir
- Rate limiting: Tek endpoint olduğu için standart rate limiting yeterli olmayabilir
- Introspection: Production ortamında şema bilgisini ifşa etmemek için introspection kapatılabilir
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
// Maksimum 5 seviye derinliğe izin ver
depthLimit(5),
// Maksimum 1000 karmaşıklık puanına izin ver
createComplexityLimitRule(1000)
],
introspection: process.env.NODE_ENV !== 'production'
});
Ne Zaman GraphQL, Ne Zaman REST?
Bu sorunun evrensel bir cevabı yoktur. Duruma göre değişir.
GraphQL’i tercih edin:
- Birden fazla istemci (web, mobil, TV uygulaması) farklı veri ihtiyaçları duyuyorsa
- Frontend ekibi backend’den bağımsız hızlı geliştirme yapmak istiyorsa
- Mikroservis mimarisini tek API ile sunmak istiyorsanız
- Veri gereksinimleri sık değişiyorsa ve versiyon yönetimi zorlaşıyorsa
- Hızlı prototip geliştirme ve ürün iterasyonları yapılıyorsa
REST’i tercih edin:
- Basit, iyi tanımlanmış kaynak tabanlı bir API yapınız varsa
- HTTP önbellekleme mekanizmalarından tam fayda sağlamak istiyorsanız
- Ekibiniz GraphQL öğrenme eğrisini kaldıramıyorsa
- Dosya yükleme veya indirme ağırlıklı bir API’niz varsa
- Public API yayınlıyorsanız ve geniş ekosistem desteği kritikse
- Küçük ve değişmeyecek bir proje yapıyorsanız
Unutmayın, bir projenin bazı kısımları için REST, diğer kısımları için GraphQL kullanmak da gayet geçerli bir seçimdir. Örneğin dosya yükleme için REST, veri sorgulama için GraphQL kullanabilirsiniz.
Subscription ile Gerçek Zamanlı Özellikler
GraphQL’in güçlü yanlarından biri de WebSocket tabanlı subscription desteğidir. Mesajlaşma uygulaması gibi gerçek zamanlı özelliklerde REST yerine GraphQL subscription çok daha temiz bir çözüm sunar:
type Subscription {
messageReceived(chatRoomId: ID!): Message!
orderStatusUpdated(orderId: ID!): Order!
stockPriceChanged(symbol: String!): StockPrice!
}
type Message {
id: ID!
content: String!
sender: User!
sentAt: String!
}
İstemci tarafında bu subscription’a şu şekilde bağlanılır:
subscription WatchOrder($orderId: ID!) {
orderStatusUpdated(orderId: $orderId) {
id
status
updatedAt
estimatedDelivery
}
}
Sonuç
GraphQL, REST API’nin yerini almak için değil, onu tamamlamak için tasarlanmış bir araçtır. Doğru kullanım senaryosunda GraphQL inanılmaz verimlilik sağlar: az ağ isteği, esnek veri sorguları, güçlü tip sistemi ve otomatik dokümantasyon.
Ancak her araç gibi GraphQL de karmaşıklık getirir. Öğrenme eğrisi, N+1 problemi gibi tuzaklar, önbelleklemenin zorlaşması ve güvenlik konfigürasyonu ciddi dikkat ister. Ekibinizin hazır olup olmadığını, projenizin karmaşıklık seviyesini ve gerçek ihtiyaçlarınızı değerlendirin.
Sysadmin perspektifinden bakınca şunu söyleyebilirim: GraphQL tabanlı bir uygulama deploy ederken tek endpoint trafiğini izlemeniz, sorgu bazlı log analizi yapabilmeniz ve APM araçlarınızı GraphQL’e uygun şekilde yapılandırmanız gerekir. Bu hazırlıkları yaparsanız, GraphQL production ortamında gayet başarılı çalışır.
Başlangıç için Apollo Studio’nun sandbox ortamını, Hasura’nın otomatik GraphQL oluşturma özelliğini veya mevcut REST API’nizi GraphQL’e saran basit bir gateway deneyin. En iyi öğrenme yöntemi her zaman uygulamalı denemedir.
