KARŞILAŞTIRMA11m READ4 Haziran 2026

REST vs GraphQL: Doğru API Paradigmasını Seçmek

N+1, cache ve ne zaman hangi paradigma.

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ı

// PLAINTEXT //
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
// TYPESCRIPT //
// 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);
});

REST Sorunları:

// PLAINTEXT //
// 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

GraphQL Yaklaşımı

// GRAPHQL //
# 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!
}
// JAVASCRIPT //
// 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 }
      }
    }
  }
`;

Resolver Yazımı

// TYPESCRIPT //
// 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 },
      });
    },
  },
};

Karşılaştırma Tablosu

ÖzellikRESTGraphQL
Öğrenme eğrisiDüşükOrta-Yüksek
Over/Under-fetchingSorunÇözülmüş
CacheHTTP cache doğal çalışırKarmaşık, CDN desteği zayıf
Dosya yüklemeBasit multipartEk konfigürasyon
Real-timePolling/SSESubscriptions built-in
Tip güvenliğiOpenAPI/ZodSchema native
DebuggingBasit (URL + method)Playground gerekli
MobileEksik veri veya aşırı veriİdeal
MicroservicesBasitSchema 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:

// PLAINTEXT //
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

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.