İçeriğe atla

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):

LibraryAvg LatencyP99 LatencyThroughputMemory
Undici23ms89ms4,235 rps124MB
Native Fetch31ms156ms3,122 rps156MB
Axios42ms234ms2,234 rps289MB
Got38ms189ms2,567 rps234MB

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 TercihKaçının
High-throughput microservice’lerUndiciGotNative Fetch
Kompleks enterprise API’larAxiosEffectKy
Functional programming takımıEffect-SuperAgent
Basit script’ler/CLI’larNative FetchKyEffect
Browser + Node.jsAxiosKyUndici
Edge computing (Cloudflare)Native FetchHonoNode-specific
Legacy sistem entegrasyonuAxiosSuperAgentEffect

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

  1. Connection pooling opsiyonel değil - Production’da file descriptor’ları yaktık. Her zaman connection limit’leri konfigüre edin.

  2. Memory leak’ler gerçek - O Axios 502 bug’ı bize haftalarca debugging’e mal oldu. Her zaman error senaryolarıyla load test yapın.

  3. Circuit breaker’lar geliri kurtarır - Her external API fail edecek. Bunun için plan yapın.

  4. Timeout’ların katmanları olmalı - Connection timeout, request timeout, total timeout. Hepsini ayarlayın.

  5. 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 Production'da: Çalışan Patternler ve İşe Yaramayan Anti-Patternler

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.

langchainllmproduction+5
pnpm Catalog ile Dependency Drift Sorununu Çözmek: Production'da Kanıtlanmış Monorepo Stratejisi

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

pnpmnodejsdependency-management+2
Node.js Geliştiricileri için Go: Serverless Migration Deneyimleri

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.

golangnodejsserverless+5
Node.js ile Zaman Yönetimi: Moment.js Olmadan Zamana Hükmetmek

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.

nodejsjavascripttime-management+7
AWS CDK Link Kısaltıcı Bölüm 2: Temel Fonksiyonlar & API Geliştirme

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.

aws-cdklambdaapi-gateway+6