GraphQL, REST'in yerini almak için doğmadı — onu tamamlayıcı olarak başladı. Ancak doğru kullanıldığında API deneyimini kökten değiştirebiliyor. Son 2 yılda production ortamında edindiğim deneyimleri paylaşıyorum.
1. Schema Design: İlk Önce Düşün, Sonra Yaz
Schema, GraphQL API'nizin sözleşmesidir. Bir kez publish ettikten sonra değiştirmek zordur. İyi bir schema tasarımı için:
- Noun-based types:
User,Post,Commentgibi isimler kullanın - Connection pattern: Pagination için Relay spec'ini takip edin
- Input types: Mutation argümanları için dedicated input types oluşturun
# Kötü type Query { getUser(id: ID!): User getUserByEmail(email: String!): User } # İyi type Query { user(id: ID!): User } type User { id: ID! email: String! posts(first: Int, after: String): PostConnection! } type PostConnection { edges: [PostEdge!]! pageInfo: PageInfo! } type PageInfo { hasNextPage: Boolean! endCursor: String }
2. N+1 Problemini Çözün
GraphQL'de en yaygın performans sorunu. Her resolver bağımsız çalıştığı için, 100 post için 100 ayrı author query çalışabilir.
DataLoader Kullanın
import { DataLoader } from 'dataloader'; // Batch function const batchUsers = async (userIds: readonly string[]) => { const users = await prisma.user.findMany({ where: { id: { in: userIds as string[] } } }); // Map results to maintain order const userMap = new Map(users.map(u => [u.id, u])); return userIds.map(id => userMap.get(id)); }; // Resolver'da kullanım export const resolvers = { Post: { author: (parent, _args, { loaders }) => { return loaders.user.load(parent.authorId); } } };
Bu tek değişiklik, 100 post içeren bir sayfada 101 database query'yi 2'ye düşürdü (1 posts + 1 batched users).
3. Caching Stratejisi
GraphQL caching, REST'ten farklı çünkü her response unique. Apollo Client'ın normalized cache'i bu sorunu çözüyor, ama server-side caching de önemli.
Apollo Response Cache
import { responseCachePlugin } from '@apollo/server-plugin-response-cache'; const server = new ApolloServer({ schema, plugins: [ responseCachePlugin({ sessionId: (context) => context.userId ?? null, ttl: 300, // 5 dakika shouldReadFromCache: (context) => !context.userId, // Sadece anonymous users için }) ] });
4. Error Handling
GraphQL hataları errors array'inde döner, ama bu yeterli değil. User-friendly mesajlar için:
class AppError extends Error { constructor( message: string, public code: string, public httpStatus: number = 400 ) { super(message); } } // Usage in resolver if (!user) { throw new AppError('Kullanıcı bulunamadı', 'USER_NOT_FOUND', 404); } // Frontend'te handling try { const { data } = await client.query({ query: GET_USER }); } catch (error) { if (error.networkError) { showToast('Bağlantı hatası'); } else if (error.graphQLErrors?.[0]?.extensions?.code === 'USER_NOT_FOUND') { showToast('Kullanıcı bulunamadı'); } }
5. Federation ile Microservisler
Büyük projelerde tek GraphQL server yeterli olmaz. Apollo Federation ile farklı servisleri birleştirin:
# Users Service type User @key(fields: "id") { id: ID! email: String! name: String! } # Posts Service type User @key(fields: "id") @extends { id: ID! @external posts: [Post!]! } type Post { id: ID! title: String! author: User! }
6. Monitoring & Observability
Production'da ne olduğunu görmek için:
- Apollo Studio: Query tracing, schema registry
- Prometheus + Grafana: Custom metrics
- OpenTelemetry: Distributed tracing
Sonuç
GraphQL, doğru kullanıldığında muazzam bir araç. Schema design'a zaman ayırın, N+1'i çözün, caching stratejinizi belirleyin ve monitoring kurun. Bu 6 madde, production GraphQL API'nizin temelini oluşturacak.