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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir