GraphQL ile İlk Sorgu: Temel Kullanım Rehberi
REST API’lere alışmış bir sysadmin olarak ilk kez GraphQL ile karşılaştığımda açıkçası biraz kaşlarımı çattım. “Bir API daha mı? Ne gerek var?” diye düşündüm. Ama birkaç projede kullandıktan sonra şunu söyleyebilirim: GraphQL, özellikle karmaşık veri ilişkilerini yönetirken ve fazladan endpoint yazmaktan kurtulmak istediğinde gerçekten hayat kurtarıyor. Bu yazıda sıfırdan başlayarak GraphQL’in temel kavramlarını, ilk sorgunuzu nasıl yazacağınızı ve gerçek dünya senaryolarında nasıl kullanacağınızı ele alacağız.
GraphQL Nedir ve Neden İhtiyacımız Var?
GraphQL, Facebook tarafından 2012’de geliştirilen ve 2015’te açık kaynak olarak yayınlanan bir sorgu dilidir. REST’ten temel farkı şu: REST’te her kaynak için ayrı bir endpoint tanımlarsınız (/users, /posts, /comments gibi), GraphQL’de ise tek bir endpoint üzerinden istediğiniz veriyi istediğiniz şekilde çekebilirsiniz.
Düşünün, bir dashboard sayfası yapıyorsunuz. REST ile bu işi yapabilmek için /users/1, /users/1/posts, /users/1/followers gibi üç ayrı istek atmak zorunda kalabilirsiniz. GraphQL’de bunu tek bir sorguda halledebilirsiniz. Hem bant genişliği kazanırsınız hem de kod karmaşıklığı azalır.
Temel avantajlar:
- Over-fetching yok: Sadece ihtiyacınız olan alanları çekersiniz, sunucu bütün kaydı döndürmez
- Under-fetching yok: İlişkili verileri tek sorguda alabilirsiniz
- Güçlü tip sistemi: Schema sayesinde API’niz otomatik olarak dokümante olur
- Gerçek zamanlı veri: Subscription mekanizması ile WebSocket desteği gelir
- Geliştirici deneyimi: GraphiQL ve Apollo Studio gibi araçlarla sorgularınızı test edebilirsiniz
Kurulum ve Ortam Hazırlığı
Pratik yapalım. Node.js üzerinde basit bir GraphQL sunucusu kuralım. Önce gerekli paketleri yükleyelim:
mkdir graphql-demo && cd graphql-demo
npm init -y
npm install graphql express express-graphql
npm install --save-dev nodemon
Şimdi basit bir sunucu dosyası oluşturalım:
cat > server.js << 'EOF'
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
// Schema tanımlama
const schema = buildSchema(`
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post]
}
type Post {
id: ID!
title: String!
content: String!
author: User
createdAt: String
}
type Query {
user(id: ID!): User
users: [User]
post(id: ID!): Post
posts: [Post]
}
`);
// Örnek veri
const usersData = [
{ id: '1', name: 'Ahmet Yilmaz', email: '[email protected]', age: 30 },
{ id: '2', name: 'Zeynep Kaya', email: '[email protected]', age: 25 },
];
const postsData = [
{ id: '1', title: 'Linux Sunucu Yönetimi', content: 'Detaylı içerik...', authorId: '1', createdAt: '2024-01-15' },
{ id: '2', title: 'Docker ile Container', content: 'Docker rehberi...', authorId: '2', createdAt: '2024-01-20' },
];
// Resolver'lar
const root = {
user: ({ id }) => {
const user = usersData.find(u => u.id === id);
if (user) {
user.posts = postsData.filter(p => p.authorId === id);
}
return user;
},
users: () => usersData,
post: ({ id }) => postsData.find(p => p.id === id),
posts: () => postsData,
};
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true, // Geliştirme ortamı için GUI
}));
app.listen(4000, () => {
console.log('GraphQL sunucusu http://localhost:4000/graphql adresinde çalışıyor');
});
EOF
Sunucuyu başlatalım:
node server.js
# veya geliştirme modunda
npx nodemon server.js
Tarayıcınızda http://localhost:4000/graphql adresine gittiğinizde GraphiQL arayüzünü göreceksiniz. Bu araç, sorgularınızı test etmek için son derece kullanışlı.
İlk GraphQL Sorgunuzu Yazmak
GraphQL’de üç temel operasyon tipi vardır:
- Query: Veri okuma (GET’in karşılığı)
- Mutation: Veri yazma/güncelleme/silme (POST/PUT/DELETE’nin karşılığı)
- Subscription: Gerçek zamanlı veri dinleme
İlk sorgumuzu GraphiQL üzerinden ya da curl ile atabiliriz. Basit bir kullanıcı sorgusundan başlayalım:
# curl ile GraphQL sorgusu atmak
curl -X POST
-H "Content-Type: application/json"
-d '{"query": "{ users { id name email } }"}'
http://localhost:4000/graphql
Bu sorgu bize tüm kullanıcıların id, name ve email alanlarını döndürür. Dikkat edin, age alanını istemedik, sunucu onu göndermeyecek. REST’te genelde sunucu bütün nesneyi döndürür, burada siz kontrol ediyorsunuz.
Şimdi daha karmaşık bir sorgu yazalım, kullanıcı ve ona ait postları tek seferde çekelim:
curl -X POST
-H "Content-Type: application/json"
-d '{
"query": "query GetUserWithPosts($userId: ID!) {
user(id: $userId) {
id
name
email
posts {
id
title
createdAt
}
}
}",
"variables": {"userId": "1"}
}'
http://localhost:4000/graphql
Burada birkaç önemli şey görüyorsunuz: sorguyu adlandırdık (GetUserWithPosts), değişken kullandık ($userId) ve değişkeni ayrı bir variables objesiyle gönderdik. Bu yaklaşım hem güvenli (SQL injection tarzı saldırılara karşı) hem de okunabilir.
Schema ve Tip Sistemi
GraphQL’in gücü büyük ölçüde tip sisteminden geliyor. Schema Language (SDL) ile API’nizin yapısını tanımlıyorsunuz ve bu hem dokümantasyon hem de validasyon görevi görüyor.
Daha kapsamlı bir schema örneği inceleyelim:
cat > schema.graphql << 'EOF'
# Temel skalar tipler: Int, Float, String, Boolean, ID
# ! işareti null olamaz anlamına gelir
enum UserRole {
ADMIN
EDITOR
VIEWER
}
type User {
id: ID!
name: String!
email: String!
role: UserRole!
age: Int
createdAt: String!
posts: [Post!]!
followers: [User!]!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
tags: [String!]!
author: User!
comments: [Comment!]!
likeCount: Int!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
}
# Giriş tipleri (mutation için)
input CreateUserInput {
name: String!
email: String!
role: UserRole!
age: Int
}
input UpdatePostInput {
title: String
content: String
published: Boolean
tags: [String!]
}
# Sorgu tipi
type Query {
me: User
user(id: ID!): User
users(role: UserRole, limit: Int, offset: Int): [User!]!
post(id: ID!): Post
posts(published: Boolean, tag: String): [Post!]!
searchPosts(keyword: String!): [Post!]!
}
# Mutation tipi
type Mutation {
createUser(input: CreateUserInput!): User!
updatePost(id: ID!, input: UpdatePostInput!): Post!
deletePost(id: ID!): Boolean!
publishPost(id: ID!): Post!
}
# Subscription tipi
type Subscription {
postAdded: Post!
commentAdded(postId: ID!): Comment!
}
EOF
echo "Schema dosyası oluşturuldu"
Mutation: Veri Yazma İşlemleri
Sorgu yazmayı öğrendik, şimdi veri oluşturma ve güncellemeye geçelim. Mutation sözdizimi Query’e çok benziyor:
# Yeni kullanıcı oluşturma
curl -X POST
-H "Content-Type: application/json"
-d '{
"query": "mutation CreateNewUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
role
createdAt
}
}",
"variables": {
"input": {
"name": "Mehmet Demir",
"email": "[email protected]",
"role": "EDITOR",
"age": 28
}
}
}'
http://localhost:4000/graphql
Mutation’ın güzel yanı, işlem sonucunda da istediğiniz alanları döndürebiliyorsunuz. Kullanıcı oluşturdunuz ve hemen sunucudan atanan id ve createdAt değerlerini alabiliyorsunuz, ekstra bir GET isteği atmadan.
Post güncelleme mutation’ı:
curl -X POST
-H "Content-Type: application/json"
-d '{
"query": "mutation PublishPost($postId: ID!) {
publishPost(id: $postId) {
id
title
published
}
}",
"variables": {"postId": "1"}
}'
http://localhost:4000/graphql
Fragment Kullanımı: Kod Tekrarını Önleme
Birden fazla sorguda aynı alanları tekrar tekrar yazmak sıkıcı ve hata yaratıcı. Fragment’lar burada devreye giriyor:
# Fragment ile sorgular
curl -X POST
-H "Content-Type: application/json"
-d '{
"query": "
fragment UserBasicInfo on User {
id
name
email
role
}
fragment PostSummary on Post {
id
title
published
likeCount
author {
...UserBasicInfo
}
}
query Dashboard {
me {
...UserBasicInfo
posts {
...PostSummary
}
}
posts(published: true) {
...PostSummary
}
}
"
}'
http://localhost:4000/graphql
Fragment’lar özellikle frontend’de component bazlı geliştirme yaparken çok işe yarıyor. Her component kendi fragment’ını tanımlıyor, ana sorgu bunları birleştiriyor.
Gerçek Dünya Senaryosu: CI/CD Dashboard API’si
Diyelim ki bir CI/CD pipeline monitoring dashboard’u yapıyorsunuz. Jenkins, GitHub Actions veya GitLab CI verilerini tek bir GraphQL API üzerinden sunmak istiyorsunuz. Bu senaryo aslında GraphQL’in en güçlü olduğu yer.
cat > pipeline-schema.graphql << 'EOF'
enum PipelineStatus {
RUNNING
SUCCESS
FAILED
CANCELLED
PENDING
}
enum TriggerType {
PUSH
PULL_REQUEST
SCHEDULE
MANUAL
}
type Repository {
id: ID!
name: String!
url: String!
defaultBranch: String!
pipelines(limit: Int, status: PipelineStatus): [Pipeline!]!
lastSuccessfulBuild: Pipeline
}
type Pipeline {
id: ID!
status: PipelineStatus!
branch: String!
commitSha: String!
commitMessage: String!
triggeredBy: TriggerType!
startedAt: String!
finishedAt: String
duration: Int # saniye cinsinden
jobs: [Job!]!
repository: Repository!
logs: String
}
type Job {
id: ID!
name: String!
status: PipelineStatus!
startedAt: String
duration: Int
artifacts: [Artifact!]!
}
type Artifact {
id: ID!
name: String!
size: Int!
downloadUrl: String!
}
type Query {
repository(id: ID!): Repository
repositories: [Repository!]!
pipeline(id: ID!): Pipeline
runningPipelines: [Pipeline!]!
failedPipelines(since: String): [Pipeline!]!
}
type Mutation {
triggerPipeline(repositoryId: ID!, branch: String!): Pipeline!
cancelPipeline(id: ID!): Pipeline!
retryPipeline(id: ID!): Pipeline!
}
type Subscription {
pipelineStatusChanged(pipelineId: ID!): Pipeline!
newPipelineTriggered(repositoryId: ID!): Pipeline!
}
EOF
echo "Pipeline schema hazır"
Bu schema ile hem frontend uygulamanız hem de monitoring araçlarınız aynı API’yi kullanabilir. Örneğin Grafana ile GraphQL datasource eklediğinizde, başarısız pipeline’ları tek bir sorguyla çekebilirsiniz:
# Başarısız pipeline'ları sorgulama
curl -X POST
-H "Content-Type: application/json"
-H "Authorization: Bearer YOUR_TOKEN"
-d '{
"query": "query FailedBuilds($since: String!) {
failedPipelines(since: $since) {
id
status
branch
commitMessage
startedAt
duration
repository {
name
url
}
jobs {
name
status
}
}
}",
"variables": {"since": "2024-01-01T00:00:00Z"}
}'
https://your-api.example.com/graphql
Hata Yönetimi ve Authentication
GraphQL’de hata yönetimi REST’ten biraz farklı çalışıyor. HTTP status kodu her zaman 200 olabilir (yani curl başarılı görünebilir), ama yanıt içinde errors alanı olabilir.
# Hata içeren yanıt örneği
# {
# "data": null,
# "errors": [
# {
# "message": "Kullanıcı bulunamadı",
# "locations": [{"line": 2, "column": 3}],
# "path": ["user"],
# "extensions": {
# "code": "USER_NOT_FOUND",
# "timestamp": "2024-01-15T10:30:00Z"
# }
# }
# ]
# }
# Hata kontrolü yapan bash script
check_graphql_errors() {
local response=$1
local errors=$(echo "$response" | python3 -c "
import json, sys
data = json.load(sys.stdin)
if 'errors' in data:
for err in data['errors']:
print(f'HATA: {err["message"]}')
sys.exit(1)
else:
print('Sorgu başarılı')
sys.exit(0)
")
echo "$errors"
return $?
}
# Kullanım
RESPONSE=$(curl -s -X POST
-H "Content-Type: application/json"
-H "Authorization: Bearer $GRAPHQL_TOKEN"
-d '{"query": "{ user(id: "999") { name } }"}'
https://api.example.com/graphql)
check_graphql_errors "$RESPONSE"
Authentication konusunda ise çoğu GraphQL API, HTTP header’larında Bearer token bekler. Bu REST ile aynı mantıkta çalışıyor. Bazı implementasyonlar X-API-Key de kullanıyor.
Apollo Client ile Frontend Entegrasyonu
Eğer Node.js veya React tarafında çalışıyorsanız Apollo Client kullanmak işleri ciddi ölçüde kolaylaştırıyor. Özellikle caching mekanizması müthiş:
npm install @apollo/client graphql
Basit bir Apollo Client kurulumu:
cat > apollo-client.js << 'EOF'
const { ApolloClient, InMemoryCache, gql, createHttpLink } = require('@apollo/client');
const { setContext } = require('@apollo/client/link/context');
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
});
// Auth header ekleme
const authLink = setContext((_, { headers }) => {
const token = process.env.API_TOKEN;
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'cache-first', // Önce cache'e bak
},
},
});
// Sorgu çalıştırma
async function getUsers() {
const { data } = await client.query({
query: gql`
query GetAllUsers {
users {
id
name
email
role
}
}
`,
});
return data.users;
}
getUsers()
.then(users => console.log('Kullanıcılar:', users))
.catch(err => console.error('Hata:', err));
EOF
Performans: N+1 Problemi ve DataLoader
GraphQL kullanırken dikkat etmeniz gereken önemli bir konu var: N+1 sorgu problemi. 10 kullanıcının postlarını çekmeye çalıştığınızda, her kullanıcı için ayrı bir veritabanı sorgusu çalışabilir. Bu 1 + 10 = 11 sorgu demek.
Çözüm DataLoader kullanmak:
npm install dataloader
cat > dataloader-example.js << 'EOF'
const DataLoader = require('dataloader');
// Batch fonksiyonu: birden fazla ID'yi toplu olarak çeker
const batchLoadUsers = async (userIds) => {
console.log(`Toplu kullanıcı yüklemesi: ${userIds.length} kayıt`);
// Normalde burada tek bir SQL IN sorgusu çalışır
// SELECT * FROM users WHERE id IN (1, 2, 3, ...)
const users = await db.query(
'SELECT * FROM users WHERE id = ANY($1)',
[userIds]
);
// DataLoader, sonuçları ID sırasına göre eşleştirmek ister
return userIds.map(id => users.find(u => u.id === id) || null);
};
const userLoader = new DataLoader(batchLoadUsers);
// Resolver'da kullanım
const resolvers = {
Post: {
author: (post) => {
// Her post için ayrı sorgu değil, toplu yükleme
return userLoader.load(post.authorId);
}
}
};
module.exports = { userLoader };
EOF
DataLoader ile 10 kullanıcı için 10 ayrı sorgu yerine tek bir toplu sorgu çalışır. Üretim ortamında bu farkı kesinlikle hissediyorsunuz.
Introspection: API’yi Keşfetmek
GraphQL’in harika özelliklerinden biri introspection. Yani API’ye “sen ne sunuyorsun?” diye sorabiliyorsunuz:
# Mevcut tipleri listele
curl -X POST
-H "Content-Type: application/json"
-d '{
"query": "query IntrospectionQuery {
__schema {
types {
name
kind
description
}
}
}"
}'
http://localhost:4000/graphql | python3 -m json.tool
# Belirli bir tipin alanlarını incele
curl -X POST
-H "Content-Type: application/json"
-d '{
"query": "query TypeDetails {
__type(name: "User") {
name
fields {
name
type {
name
kind
}
description
}
}
}"
}'
http://localhost:4000/graphql | python3 -m json.tool
Bu özellik sayesinde API’nin dokümantasyonunu otomatik olarak çıkartabilirsiniz. graphql-codegen gibi araçlarla schema’dan TypeScript tipleri de üretebiliyorsunuz, yani sıfırdan tip yazmanıza gerek kalmıyor.
Production’da GraphQL: Dikkat Edilmesi Gerekenler
Üretim ortamında birkaç kritik konuya dikkat etmeniz gerekiyor:
- Introspection’ı kapatın: Production’da
introspection: falseyapın, aksi halde saldırganlar API yapınızı keşfedebilir - Query depth limiti koyun: Sonsuz iç içe sorgular sunucunuzu çökertebilir,
graphql-depth-limitpaketi işinizi görür - Query complexity limiti: Çok karmaşık sorgulara karşı
graphql-query-complexitykullanın - Rate limiting: Standart HTTP rate limiting kuralları geçerli, nginx veya API gateway seviyesinde yapabilirsiniz
- Persisted queries: Apollo’nun persisted query özelliği ile sadece önceden kayıtlı sorgulara izin verebilirsiniz
- Loglama: Her GraphQL operasyonunu, süresini ve hataları loglamanız şart
- CORS: Tarayıcı tabanlı istemciler için CORS ayarlarını doğru yapılandırın
Sonuç
GraphQL ilk bakışta karmaşık görünebilir ama temel konseptleri yakaladıktan sonra REST’e göre çok daha esnek ve güçlü bir araç olduğunu anlıyorsunuz. Özellikle şu senaryolarda GraphQL’i tercih etmenizi öneririm: birden fazla frontend istemciniz varsa (mobil, web, masaüstü), veri ilişkileri karmaşıksa ve her endpoint için ayrı ayrı REST route’u yazmaktan bıktıysanız.
Sysadmin perspektifinden bakınca, GraphQL API’leri monitoring açısından biraz daha dikkat ister. Tüm istekler tek endpoint’e geldiğinden, hangi operasyonun ne kadar sürdüğünü takip etmek için operasyon bazlı logging şart. Bunu baştan kurun, sonradan eklemek zahmetli oluyor.
Bu yazıda anlattıklarım temel seviyeydi. Subscription ile gerçek zamanlı özellikler, schema stitching ve federation ile büyük ekiplerde mikro-servis mimarisi, directives kullanımı gibi ileri konular için serinin devamını takip edin. Sorularınız veya farklı senaryolarınız varsa yorum bölümünde buluşalım.
