Node.js İle Graphql Şeması Tasarımı Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Projeye başlamadan önce bilgisayarınızda Node.js'in 20.x veya daha yeni bir LTS sürümünün yüklü olması gerekmektedir. Ayrıca paket yönetimi için npm veya pnpm kullanacağız. Çalışma ortamınızı hazırlamak için aşağıdaki adımları izleyin.

  1. Terminali açın ve proje klasörünüzü oluşturun: mkdir graphql-projesi && cd graphql-projesi
  2. Projenizi başlatın: npm init -y
  3. Gerekli kütüphaneleri yükleyin: npm install @apollo/server graphql

Bu kurulum, Apollo Server'ın en güncel sürümünü ve GraphQL motorunu projenize dahil edecektir. 2026 yılı standartlarına göre, modüler bir yapı kurmak için package.json dosyanıza "type": "module" eklemeyi unutmayın.

GraphQL Şema Tanımlama Dili (SDL) Nedir?

GraphQL Şema Tanımlama Dili (Schema Definition Language - SDL), verilerinizin yapısını insan tarafından okunabilir bir formatta tanımlamanıza olanak tanır. Tip sistemi, API'nizin sözleşmesidir; hangi alanların zorunlu, hangilerinin opsiyonel olduğunu belirtir.


const typeDefs = `#graphql
  type Kullanici {
    id: ID!
    isim: String!
    email: String!
    yas: Int
  }

  type Query {
    kullanicilar: [Kullanici]
    kullanici(id: ID!): Kullanici
  }
`;

Yukarıdaki örnekte ID! ifadesi, bu alanın zorunlu bir benzersiz kimlik olduğunu belirtir. ! işareti, GraphQL dünyasında "non-nullable" (boş geçilemez) anlamına gelir.

Resolver (Çözücü) Mantığı Nasıl Kurulur?

Şema, verinin yapısını belirtirken; resolver'lar bu verinin nereden ve nasıl getirileceğini tanımlayan fonksiyonlardır. Her bir alan için bir resolver yazılabilir, ancak genellikle kök (root) seviyesindeki Query ve Mutation işlemleri için tanımlanırlar.


const resolvers = {
  Query: {
    kullanicilar: () => veritabani.kullanicilar,
    kullanici: (_, { id }) => {
      return veritabani.kullanicilar.find(k => k.id === id);
    },
  },
};

Burada _ (alt tire) parametresi, parent objesini temsil eder; genellikle kök sorgularda kullanılmaz. İkinci parametre olan { id } ise istemciden gelen argümanları yakalar.

Şema Tasarımında Tip İlişkileri ve Bağlantılar

Gerçek dünya uygulamalarında veriler birbirine bağlıdır. Örneğin, bir "Kullanıcı"nın birden fazla "Sipariş"i olabilir. Bu ilişkileri şemada tanımlamak, GraphQL'in en büyük gücüdür.


type Siparis {
  id: ID!
  tutar: Float!
  kullaniciId: ID!
}

type Kullanici {
  id: ID!
  isim: String!
  siparisler: [Siparis]
}

Bu tasarım sayesinde istemci, bir kullanıcıyı sorgularken aynı zamanda onun siparişlerini de tek bir istekte getirebilir. İlişkisel veritabanı kullanıyorsanız, bu noktada DataLoader kütüphanesini kullanarak N+1 sorgu problemini çözmeniz önerilir.

Kritik Güvenlik Uyarısı: GraphQL şemalarınızda "Introspection" (şema keşfi) özelliğini üretim ortamında (production) kapatmayı unutmayın. Aksi takdirde, kötü niyetli kişiler API yapınızı tamamen haritalandırabilir.

GraphQL Şema Tasarımı Yöntemleri Karşılaştırması

Yöntem Avantaj Dezavantaj
Schema First Dokümantasyon odaklı, iş birliği kolay Resolver ile şema uyumunu korumak zor
Code First Tip güvenliği yüksek (TypeScript ile) Şema tasarımı kodun içinde kaybolabilir

Mutation ile Veri Değişikliği Tasarımı

Veri okuma işlemleri Query ile yapılırken, veri ekleme, güncelleme veya silme işlemleri Mutation ile yapılır. Mutation tasarlarken, işlemin sonucunda genellikle etkilenen objeyi döndürmek en iyi pratiktir.


type Mutation {
  kullaniciEkle(isim: String!, email: String!): Kullanici
}

// Resolver tarafı
const resolvers = {
  Mutation: {
    kullaniciEkle: (_, { isim, email }) => {
      const yeniKullanici = { id: '3', isim, email };
      veritabani.kullanicilar.push(yeniKullanici);
      return yeniKullanici;
    }
  }
};

Bu örnekte, basit bir diziye ekleme yapıyoruz. Gerçek bir uygulamada, burada veritabanı işlemlerini (örneğin Prisma veya Mongoose kullanarak) gerçekleştirmelisiniz.

Hata Yönetimi ve Validasyon

GraphQL şemanızda hata yönetimi, kullanıcı deneyimini doğrudan etkiler. Hataları GraphQLError sınıfını kullanarak özelleştirebilirsiniz. Örneğin, bulunamayan bir kullanıcı için 404 benzeri bir hata döndürmek yerine, GraphQL'in hata formatını kullanmak daha doğrudur.


import { GraphQLError } from 'graphql';

kullanici: (_, { id }) => {
  const user = veritabani.find(u => u.id === id);
  if (!user) {
    throw new GraphQLError('Kullanıcı bulunamadı', {
      extensions: { code: 'NOT_FOUND' },
    });
  }
  return user;
}
Kod Güvenliği Sorumluluk Reddi: Bu makaledeki kod örnekleri eğitim amaçlıdır. Üretim ortamında kullanıcı girdilerini her zaman doğrulayın (validation) ve SQL Injection veya XSS saldırılarına karşı gerekli kütüphaneleri (Joi, Zod vb.) kullanın.

Sıkça Sorulan Sorular

GraphQL şeması neden REST'ten daha iyidir?

GraphQL, istemcinin tam olarak ihtiyaç duyduğu veriyi almasını sağlar (over-fetching ve under-fetching sorunlarını çözer). Ayrıca güçlü tip sistemi sayesinde hata payını azaltır.

Şema tasarımı yaparken hangi kütüphaneyi kullanmalıyım?

2026 yılı itibarıyla Apollo Server, Node.js ekosistemindeki en standart ve geniş topluluk desteğine sahip araçtır. Başlangıç için en güvenli tercihtir.

N+1 sorgu problemi nedir?

Bir kullanıcıyı çektikten sonra, her bir siparişi için ayrı ayrı veritabanı sorgusu atılmasıdır. Bunu çözmek için DataLoader kütüphanesi ile sorguları gruplayarak tek bir veritabanı isteğine indirgemelisiniz.

Şemayı nasıl versiyonlayabilirim?

GraphQL'de versiyonlama yerine "alanları kullanımdan kaldırma" (deprecation) yöntemi tercih edilir. @deprecated direktifini kullanarak eski alanları işaretleyebilir ve yeni alanlar ekleyebilirsiniz.

TypeScript kullanmak zorunlu mu?

Zorunlu değil ancak şiddetle önerilir. Şema tipleriniz ile TypeScript arayüzlerinizin (interface) uyumlu olması, büyük projelerde geliştirme hızını ve hata yönetimini ciddi oranda artırır.

GraphQL Şemasında Performans Optimizasyonu: DataLoader Kullanımı

GraphQL'in en büyük avantajı olan esneklik, yanlış kurgulandığında veritabanı üzerinde ciddi bir yük oluşturabilir. Özellikle iç içe geçmiş (nested) sorgularda, her bir alt alan için veritabanına ayrı bir sorgu atılması "N+1" problemini doğurur. Bu durumu engellemek için DataLoader kütüphanesi standart bir çözüm haline gelmiştir.

DataLoader, aynı istek (request) döngüsü içerisinde yapılan veri taleplerini toplar (batching) ve sonuçları önbelleğe (caching) alır. Aşağıdaki örnekte, bir kullanıcıya ait gönderileri çekerken nasıl optimize edildiğini görebilirsiniz:

const DataLoader = require('dataloader');

// Batch fonksiyonu: ID listesini alır ve veritabanından tek seferde çeker
const batchUsers = async (ids) => {
  const users = await db.users.find({ id: { $in: ids } });
  return ids.map(id => users.find(user => user.id === id));
};

const userLoader = new DataLoader(batchUsers);

// Resolver içerisinde kullanımı
const resolvers = {
  Post: {
    author: (parent) => {
      return userLoader.load(parent.authorId);
    }
  }
};

GraphQL Şeması İçin Birim Testleri (Unit Testing)

Şema tasarımınızın doğruluğunu ve resolver'ların beklenen çıktıyı verip vermediğini garanti altına almak için test süreçlerini otomatize etmelisiniz. apollo-server-testing veya graphql-tools gibi araçlarla, şemanızı ayağa kaldırmadan resolver fonksiyonlarınızı izole bir şekilde test edebilirsiniz.

Test yazarken izlemeniz gereken temel adımlar şunlardır:

  • Mocking: Veritabanı veya dış servis çağrılarını mock verilerle değiştirin.
  • Query/Mutation Testi: Şemanın beklenen sorgulara doğru yanıt verip vermediğini kontrol edin.
  • Hata Senaryoları: Geçersiz parametre gönderildiğinde API'nin doğru hata kodlarını döndürdüğünü doğrulayın.
const { mockServer } = require('@graphql-tools/mock');
const { schema } = require('./schema');

const server = mockServer(schema);

test('Kullanıcı sorgusu geçerli bir isim döndürmeli', async () => {
  const query = '{ user(id: "1") { name } }';
  const response = await server.query(query);
  expect(response.data.user).toHaveProperty('name');
});

İleri Seviye Şema Tasarımı İpuçları

Profesyonel bir GraphQL API'si tasarlarken dikkat etmeniz gereken bazı "best practice" kuralları şunlardır:

  1. Pagination (Sayfalama): Büyük veri setlerini döndürürken mutlaka limit ve offset veya cursor-based sayfalama kullanın.
  2. Input Tipleri: Mutation'larda karmaşık argümanlar için input tipini kullanın. Bu, şemanın okunabilirliğini artırır.
  3. Scalar Tipleri: Standart tipler (String, Int, Boolean) yetmediğinde Date veya Email gibi özel scalar tipleri tanımlayarak veri validasyonunu şema katmanına taşıyın.
# Örnek Input Tipi Kullanımı
input CreateUserInput {
  username: String!
  email: String!
  age: Int
}

type Mutation {
  createUser(input: CreateUserInput!): User
}

Sonuç

Node.js ile GraphQL şeması tasarımı, başlangıçta karmaşık görünse de prensipleri anladığınızda uygulamanızın veri katmanını çok daha yönetilebilir kılar. SDL ile şemayı tanımlamak, resolver'lar ile mantığı kurmak ve tip güvenliğini sağlamak, profesyonel bir API'nin temelidir. Bir sonraki adım olarak, veritabanı entegrasyonu (PostgreSQL veya MongoDB) ve kimlik doğrulama (JWT) mekanizmalarını şemanıza nasıl ekleyeceğinizi araştırmanızı öneririm.

Bu yazıya tepkinizi paylaşın:
Selin Yılmaz

Yaratıcı problem çözme teknikleri üzerine odaklanan bir içerik editörüyüm. Hayatı pratikleştiren tüyolarla okuyucuya zaman kazandırmayı hedefleyen yazılar kurguluyorum.

Yorumlar (0)

Yorum Yaz