2025-09-04
Server-Side HTTP Client'lar: Native Fetch'ten Effect'e, Production Deneyimleri
Node.js HTTP client'larının kapsamlı karşılaştırması: performans testleri, circuit breaker pattern'ler ve gerçek production deneyimleri
HTTP Client Zorluğu
Microservice mimarileri genellikle basit başlar - servisler edge case’ler üzerinde fazla düşünmeden HTTP üzerinden haberleşir. Sonra yüksek trafik olayları sınırlamaları ortaya çıkarır. Payment servisleri yük altında timeout’a düşmeye başlayabilir, request başına 30 saniye takılabilir.
Yaygın bir sorun: proper timeout handling olmadan native fetch kullanmak. Bu takılan connection’lar Lambda concurrent execution’ları tüketebilir ve infrastructure maliyetlerini etkiler.
Bu deneyim, HTTP client seçiminin sadece feature’larla ilgili olmadığını gösteriyor—production yükü altında neyin bozulduğunu anlamakla ilgili. Doğru client seçimi latency, memory kullanımı ve operasyonel maliyetleri doğrudan etkiler. Bir payment servisi timeout’larında dakikalarca asılı kalan connection’lar tüm Lambda concurrency’yi bloke edebilir; bu da diğer isteklerin de başarısız olmasına yol açar.
Server-Side HTTP Client’lar Neden Düşündüğünüzden Daha Önemli
Browser’da HTTP client’lar basit. Request yaparsın, response’u handle edersin, bitti. Server-side? İşte orada işler ilginçleşiyor:
- Connection pooling saniyede binlerce request yaparken kritik hale geliyor
- Memory leak’ler Node.js process’inizi günler içinde yavaşça öldürebilir
- Circuit breaker’lar graceful degradation ile cascading failure arasındaki fark
- Retry stratejileri network probleminin outage’a dönüşüp dönüşmeyeceğini belirler
Gelin her büyük oyuncuya bakalım ve production gerçekliğiyle nasıl başa çıktıklarını görelim.
Native Fetch: Her Zaman Yeterli Olmayan Varsayılan
Node.js 18’den beri native fetch var. Her yerde kullanmak cazip - zero dependency, standard API, sevmemek için ne var?
// Yeterince basit görünüyor
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' })
});
Native Fetch’in Parladığı Yerler
- Zero dependency: Docker image’larınız yalın kalıyor
- Standard API: Aynı kod browser, Node.js, Deno, Bun’da çalışıyor
- Modern: Arka planda undici kullanıyor (Node.js 18’den beri)
Yetersiz Kaldığı Yerler
Production’da bizi yakayan şey:
// Timeout tuzağı - bu düşündüğünüzü yapmıyor
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://slow-api.com', {
signal: controller.signal
});
} catch (error) {
// Bu abort'u yakalar, ama TCP connection hala açık olabilir!
}
AbortController sadece JavaScript tarafını iptal eder. Alttaki TCP connection? O yavaşça connection pool’unuzu yiyerek kalabilir. undici tabanlı fetch’te bu daha iyi yönetiliyor olsa da, yüksek concurrency ortamlarında explicit timeout ve connection limit stratejileri şart. Aksi halde “neden process memory sürekli artıyor?” sorusuyla karşılaşırsın.
Production Kararı
Native fetch’i şunlar için kullanın:
- Basit script’ler ve CLI tool’ları
- Prototype’lar ve POC’ler
- Hem client hem server’ı kontrol ettiğinizde
Şunlardan kaçının:
- Retry, circuit breaker veya connection pooling gerektiğinde
- Saniyede binlerce request yaparken
- Güvenilmez third-party API’larla entegre olurken
Axios: İsviçre Çakısı
Axios haftada 45 milyon download ile en popüler seçim olmaya devam ediyor. Her yerde olmasının bir nedeni var.
import axios from 'axios';
import axiosRetry from 'axios-retry';
// Production-ready konfigürasyon
const client = axios.create({
timeout: 10000,
maxRedirects: 5,
validateStatus: (status) => status < 500
});
// Retry logic ekle
axiosRetry(client, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
error.response?.status === 429; // Rate limited
}
});
// Logging için request/response interceptor'lar
client.interceptors.request.use((config) => {
config.headers['X-Request-ID'] = generateRequestId();
logger.info('Giden request', {
method: config.method,
url: config.url
});
return config;
});
Bulduğumuz Memory Leak
Bir projede Axios’un 502 error’ları handle ederken memory leak yaptığını keşfettik. Sorun follow-redirects dependency’sindeydi. Nasıl bulduğumuza bakalım:
// Memory leak reprodüksiyon
async function leakTest() {
const promises = [];
for (let i = 0; i < 10000; i++) {
promises.push(
axios.get('https://api.returns-502.com')
.catch(() => {}) // Error object'ler memory'de kalıyordu!
);
}
await Promise.all(promises);
// Heap snapshot'ı burada kontrol et - HTML error response'ları hala memory'de
}
Connection Pooling Düzeltmesi
Düz Axios her request için yeni connection açar. Scale’de bu server’ınızı öldürür:
import Agent from 'agentkeepalive';
const keepAliveAgent = new Agent({
maxSockets: 100,
maxFreeSockets: 10,
timeout: 60000,
freeSocketTimeout: 30000
});
const client = axios.create({
httpAgent: keepAliveAgent,
httpsAgent: new Agent.HttpsAgent(keepAliveAgent.options)
});
Production Kararı
Axios hala şunlar için sağlam:
- Kompleks request/response transformation’lar
- Kapsamlı middleware ihtiyacı olduğunda
- Zaten aşina olan takımlar
Ama şunlara dikkat:
- Bundle size (1.84MB unpacked, production bundle için ~13KB gzipped)
- Error response’larla memory leak’ler
- Connection pooling ekstra setup gerektiriyor. Kurumsal ortamlarda proxy ve sertifika yapılandırması da ekstra kod gerektirir.
Undici: Performans Şampiyonu
Undici, Node.js fetch’i içerde güçlendiren şey. Ama direkt kullanmak size süper güçler veriyor.
import { request, Agent } from 'undici';
const agent = new Agent({
connections: 100,
pipelining: 10, // HTTP/1.1 pipelining
keepAliveTimeout: 60 * 1000,
keepAliveMaxTimeout: 600 * 1000
});
// High-throughput senaryolar için axios'tan 3x daha hızlı
const { statusCode, body } = await request('https://api.example.com', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ data: 'value' }),
dispatcher: agent
});
Performans Rakamları
Payment servisimizde benchmark yaptık (1000 concurrent request):
| Library | Avg Latency | P99 Latency | Throughput | Memory |
|---|---|---|---|---|
| Undici | 23ms | 89ms | 4,235 rps | 124MB |
| Native Fetch | 31ms | 156ms | 3,122 rps | 156MB |
| Axios | 42ms | 234ms | 2,234 rps | 289MB |
| Got | 38ms | 189ms | 2,567 rps | 234MB |
HTTP/2 Desteği
Undici HTTP/2 destekliyor, ama açıkça aktive edilmesi gerekiyor:
import { Agent, request } from 'undici';
// HTTP/2 aktif agent oluştur
const h2Agent = new Agent({
allowH2: true, // HTTP/2'yi aktive et
connections: 50,
pipelining: 0 // HTTP/2 için pipelining'i deaktive et
});
// Belirli HTTP/2 endpoint'leriyle kullan
const response = await request('https://http2.example.com/api', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ data: 'value' }),
dispatcher: h2Agent
});
// Veya global dispatcher ile
import { setGlobalDispatcher } from 'undici';
setGlobalDispatcher(h2Agent);
// Artık tüm fetch çağrıları mevcut olduğunda HTTP/2 kullanıyor
const h2Response = await fetch('https://http2.example.com/data');
HTTP/2 birden çok paralel request için önemli performance faydası sağlıyor:
// Benchmark: 50 concurrent request ile HTTP/1.1 vs HTTP/2
const h1Agent = new Agent({ allowH2: false });
const h2Agent = new Agent({ allowH2: true });
// HTTP/1.1: ~200ms ortalama (connection overhead)
// HTTP/2: ~80ms ortalama (multiplexing avantajı)
Gelişmiş Konfigürasyon: Proxy ve Sertifikalar
Undici production ortamları için kapsamlı proxy ve sertifika yönetimi sunar. ProxyAgent, client sertifikaları, certificate pinning, NTLM proxy desteği.
Production Kararı
Undici şunlarda mükemmel:
- High-throughput microservice’ler
- Her milisaniye önemli olduğunda
- Memory kısıtlı ortamlar
Şunlarda pas geçin:
- Native HTTP/2 gerekiyorsa
- Takımınız higher-level abstraction’ları tercih ediyorsa
- Axios’tan migrate ediyorsanız (çok farklı)
Effect: Fonksiyonel Güç Merkezi
Effect tamamen farklı bir yaklaşım benimsiyor. Promise’ler yerine, built-in error handling’li composable effect’ler alıyorsunuz.
import { Effect, Schedule, Duration } from 'effect';
import { HttpClient, HttpClientError } from '@effect/platform';
// Otomatik retry'lı API client tanımla
const apiClient = HttpClient.HttpClient.pipe(
HttpClient.retry(
Schedule.exponential(Duration.seconds(1), 2).pipe(
Schedule.jittered,
Schedule.either(Schedule.recurs(3))
)
),
HttpClient.filterStatusOk
);
// Type-safe error handling
const fetchUser = (id: string) =>
Effect.gen(function* (_) {
const response = yield* _(
apiClient.get(`/users/${id}`),
Effect.catchTag('HttpClientError', (error) => {
if (error.response?.status === 404) {
return Effect.succeed({ found: false });
}
return Effect.fail(error);
})
);
return yield* _(response.json);
});
Öğrenme Eğrisi Hikayesi
Effect’i bir takıma tanıttık. 1. Hafta: kafa karışıklığı. 2. Hafta: frustrasyon. 4. Hafta: “Asla geri dönmeyeceğiz.” Type-safe error handling bütün bir bug sınıfını ortadan kaldırdı.
// Effect öncesi: Runtime sürprizleri
async function riskyOperation() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id); // Fail edebilir
return processOrders(orders); // Bu da fail edebilir
} catch (error) {
// Network mi? Auth mi? Business logic mi? Kim bilir!
logger.error('Bir şeyler fail etti', error);
}
}
// Effect ile: Error'lar type'ın parçası
const safeOperation = Effect.gen(function* (_) {
const user = yield* _(fetchUser);
const orders = yield* _(fetchOrders(user.id));
return yield* _(processOrders(orders));
}).pipe(
Effect.catchTags({
NetworkError: (e) => logAndRetry(e),
AuthError: (e) => refreshTokenAndRetry(e),
ValidationError: (e) => Effect.fail(new BadRequest(e))
})
);
Production Kararı
Effect şunlar için mükemmel:
- Birden çok failure mode’u olan kompleks business logic
- Functional programming’e rahat takımlar
- Type safety kritik olduğunda
İki kere düşünün eğer:
- Takımınız FP konseptlerine yeni
- Junior’ları hızlıca onboard etmeniz gerekiyor
- Basit bir CRUD servisi
Diğerleri: Hızlı Turlar
Got: Node.js Uzmanı
import got from 'got';
const client = got.extend({
timeout: { request: 10000 },
retry: {
limit: 3,
methods: ['GET', 'PUT', 'DELETE'],
statusCodes: [408, 429, 500, 502, 503, 504],
errorCodes: ['ETIMEDOUT', 'ECONNRESET'],
calculateDelay: ({ attemptCount }) => attemptCount * 1000
},
hooks: {
beforeRetry: [(error, retryCount) => {
logger.warn(`Retry denemesi ${retryCount}`, error.message);
}]
}
});
Sadece Node.js projeleri için harika. Built-in pagination desteği güzel.
Ky: Hafif Fetch Wrapper
import ky from 'ky';
const api = ky.create({
prefixUrl: 'https://api.example.com',
timeout: 10000,
retry: {
limit: 2,
methods: ['get', 'put', 'delete'],
statusCodes: [408, 429, 500, 502, 503, 504]
}
});
Minimal overhead ile pilli fetch istediğinizde mükemmel.
SuperAgent: Hala Hayatta
import superagent from 'superagent';
superagent
.post('/api/users')
.send({ name: 'John' })
.retry(3, (err, res) => {
if (err) return true;
return res.status >= 500;
})
.end((err, res) => {
// Callback style hala çalışıyor
});
Plugin sistemi güçlü, ama Axios popülerlik yarışını kazandı.
Hono: Edge Runtime Şampiyonu
Hono’nun fetch-uyumlu yaklaşımı Cloudflare Workers, Deno ve Bun’da çalışır. Minimal overhead, edge-optimize. Edge-first deployment’lar için ilk tercih.
Enterprise Ortamı: Proxy, Sertifikalar ve Kurumsal Ağlar
Kurumsal proxy’ler, self-signed sertifikalar ve dahili DNS client konfigürasyonu gerektirir. Undici proxy, Axios proxy/httpsAgent, NODE_EXTRA_CA_CERTS. Konfigürasyon olmadan kurumsal ağlardaki harici API çağrıları başarısız olur.
Kurumsal Proxy Debugging
curl -v ile proxy üzerinden test; SSL sertifika zincirini doğrula. Sık sorunlar: proxy auth, sertifika pinning, DNS çözümlemesi. Doğru client konfigürasyonuyla çoğu hata çözülür.
Circuit Breaker’lar: Production Kurtarıcınız
Hangi HTTP client’ı seçerseniz seçin, circuit breaker ekleyin. İşte Cockatiel ile production setup’ımız:
import { circuitBreaker, retry, wrap, ExponentialBackoff } from 'cockatiel';
// 5 ardışık failure'dan sonra açılan circuit breaker
const breaker = circuitBreaker({
halfOpenAfter: 10000,
breaker: new ConsecutiveBreaker(5)
});
// Exponential backoff'lu retry policy
const retryPolicy = retry({
maxAttempts: 3,
backoff: new ExponentialBackoff()
});
// Birleştir
const resilientFetch = wrap(
retryPolicy,
breaker,
async (url: string) => {
const response = await undici.request(url);
if (response.statusCode >= 500) {
throw new Error(`Server error: ${response.statusCode}`);
}
return response;
}
);
// Kullanım
try {
const data = await resilientFetch('https://flaky-api.com/data');
} catch (error) {
if (breaker.state === 'open') {
// Circuit açık, fallback kullan
return getCachedData();
}
throw error;
}
Circuit Breaker Production Benefits
Payment provider’da aralıklı timeout’lar olabilir. Circuit breaker olmadan: tüm checkout flow’u bloklanır. Circuit breaker ile: failure threshold’dan sonra anında backup provider’a failover yapılır, gelir kaybı önlenir.
Production Monitoring Setup
Hangi client’ı seçerseniz seçin, instrument edin:
import { metrics } from '@opentelemetry/api-metrics';
const meter = metrics.getMeter('http-client');
const requestDuration = meter.createHistogram('http.request.duration');
const requestCount = meter.createCounter('http.request.count');
// HTTP client'ınızı wrap edin
async function instrumentedRequest(url: string, options: any) {
const start = Date.now();
const labels = { method: options.method, url: new URL(url).hostname };
try {
const response = await yourHttpClient(url, options);
labels.status = response.status;
labels.success = 'true';
return response;
} catch (error) {
labels.status = error.response?.status || 0;
labels.success = 'false';
throw error;
} finally {
requestDuration.record(Date.now() - start, labels);
requestCount.add(1, labels);
}
}
Karar Matrisi
Yıllarca production deneyiminden sonra, tavsiye matrisim:
| Use Case | İlk Tercih | İkinci Tercih | Kaçının |
|---|---|---|---|
| High-throughput microservice’ler | Undici | Got | Native Fetch |
| Kompleks enterprise API’lar | Axios | Effect | Ky |
| Functional programming takımı | Effect | - | SuperAgent |
| Basit script’ler/CLI’lar | Native Fetch | Ky | Effect |
| Browser + Node.js | Axios | Ky | Undici |
| Edge computing (Cloudflare) | Native Fetch | Hono | Node-specific |
| Legacy sistem entegrasyonu | Axios | SuperAgent | Effect |
Production Debugging: Deneyimden Dersler
Phantom Memory Leak Debugging
Axios 502 bug’ı: response olmadan hata fırlattığında handle’lar açık kalıyordu. Heap dump’lar, error senaryolarıyla test. HTTP client’larda memory leak’ler gerçek.
Connection Pool Exhaustion
Yük altında file descriptor’lar tükendi. Connection limit’lerini konfigüre edin. Production için connection pooling opsiyonel değil.
Production’da Yavaş İstekleri Debug Etme
Response time percentile’ları (ortalamalar değil), p95/p99 metrikleri. Log’lar tek başına yeterli değil. Timeout katmanları: connection, request, total.
Önemli Dersler
-
Connection pooling opsiyonel değil - Production’da file descriptor’ları yaktık. Her zaman connection limit’leri konfigüre edin.
-
Memory leak’ler gerçek - O Axios 502 bug’ı bize haftalarca debugging’e mal oldu. Her zaman error senaryolarıyla load test yapın.
-
Circuit breaker’lar geliri kurtarır - Her external API fail edecek. Bunun için plan yapın.
-
Timeout’ların katmanları olmalı - Connection timeout, request timeout, total timeout. Hepsini ayarlayın.
-
Log’lar yeterli değil - Metrik’lere ihtiyacınız var. Ortalamalar değil, p95/p99 gibi response time percentile’ları önemli. Tracing ile yavaş request’lerin hangi kod path’inden geldiğini bulmak debug süresini ciddi kısaltır.
Sırada Ne Var?
HTTP client manzarası evrim geçirmeye devam ediyor. Native fetch iyileşiyor, undici HTTP/2 ekliyor ve Effect popülerlik kazanıyor. Tavsiyem? Hype’a değil, takımınıza ve use case’inize göre seçin.
Basit başlayın (native fetch), her şeyi ölçün ve gerçek sınırlamalara çarptığınızda upgrade edin. Ve ne seçerseniz seçin, ihtiyacınız olmadan önce circuit breaker ekleyin. Bu konuda bana güvenin.
Takımınıza ve use case’inize en uygun HTTP client’ı seçin, doğru error handling uygulayın ve her şeyi izleyin. Ne seçerseniz seçin, ihtiyacınız olmadan önce circuit breaker ekleyin.
İlgili yazılar
LangChain uygulamalarını production'a taşırken öğrendiklerim. Başarısızlığa yol açan anti-patternler, başarıyı sağlayan patternler, çalışan kod örnekleri ve maliyet optimizasyon stratejileri.
pnpm'in catalog özelliği JavaScript monorepo'larındaki dependency drift sorununu temelden nasıl çözüyor - pratik uygulama pattern'leri ve kanıtlanmış stratejilerle
Serverless ortamlarda Node.js'den Go'ya geçiş sürecinden gerçek deneyimler: performans kazanımları, takım zorlukları ve pratik karar çerçeveleri.
Production'da yaşanan zaman yönetimi problemleri, Moment.js'den modern alternatiflere geçiş stratejileri ve UTC handling best practice'leri. Timezone savaşlarından çıkışın yolu.
Yönlendirme motoru, analytics toplama ve API Gateway konfigürasyonu. Günlük milyonlarca yönlendirmeyi işlemenin gerçek performans optimizasyonları ve debugging stratejileri.