Axios vs Fetch vs Undici: Node.js HTTP Client Karşılaştırması
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
Node.js sunucudan sunucuya çağrılar için HTTP client seçimi nadiren tek seferlik bir karar olur: native fetch, Axios, undici ve Effect, bağlantı baskısı, retry fırtınaları ve kısmi başarısızlıklar karşısında birbirinden farklı davranır. Yanlış varsayılan (örneğin timeout’suz fetch) Lambda concurrency’yi tüketebilir ve altyapı genelinde bir kesintiye yol açabilir. Bu yazı, söz konusu dört client’ı connection pooling, timeout semantiği, circuit-breaker desteği ve pratik production değiş tokuşları açısından karşılaştırıyor.
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 Client’ların Gizli Bedeli
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' })
});
Güçlü Yönler
- 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)
Sınırlamalar
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;
});
Bilinen Memory Leak
Axios, 502 error’larını handle ederken belirli koşullarda memory leak yapabilir. Sorun follow-redirects dependency’sindedir. Nasıl tespit edileceği:
// 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ı
1000 concurrent request ile bir payment servisi üzerindeki benchmark sonuçları:
| 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 |
Kullandığımız benchmark script’i:
import { performance } from 'perf_hooks';
import { Agent } from 'undici';
import axios from 'axios';
import got from 'got';
const testUrl = 'https://httpbin.org/json';
const concurrency = 100;
const totalRequests = 10000;
// Undici setup
const undiciAgent = new Agent({
connections: 50,
pipelining: 10
});
// Axios setup
const axiosClient = axios.create({
timeout: 5000
});
// Benchmark fonksiyonu
async function benchmark(name: string, clientFn: () => Promise<any>) {
const start = performance.now();
const promises = [];
let completed = 0;
for (let i = 0; i < totalRequests; i++) {
promises.push(
clientFn().then(() => completed++)
);
// Concurrency'yi kontrol et
if (promises.length >= concurrency) {
await Promise.race(promises);
promises.splice(promises.findIndex(p => p.isFulfilled), 1);
}
}
await Promise.allSettled(promises);
const duration = performance.now() - start;
console.log(`${name}: ${Math.round(totalRequests / (duration / 1000))} req/s`);
console.log(` Completed: ${completed}/${totalRequests}`);
console.log(` Duration: ${Math.round(duration)}ms`);
}
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:
import { ProxyAgent, Agent } from 'undici';
import { readFileSync } from 'fs';
// Authentication'lı proxy konfigürasyonu
const proxyAgent = new ProxyAgent({
uri: 'http://proxy.corporate.com:8080',
auth: Buffer.from('username:password').toString('base64'),
requestTls: {
ca: readFileSync('./ca.pem'),
cert: readFileSync('./client-cert.pem'),
key: readFileSync('./client-key.pem'),
rejectUnauthorized: true
}
});
// Self-signed veya dahili CA'lar için özel sertifika yönetimi
const secureAgent = new Agent({
connect: {
ca: [
readFileSync('./root-ca.pem'),
readFileSync('./intermediate-ca.pem')
],
cert: readFileSync('./client-cert.pem'),
key: readFileSync('./client-key.pem'),
// Certificate pinning
checkServerIdentity: (hostname, cert) => {
const expectedFingerprint = 'AA:BB:CC:DD:EE:FF...';
const actualFingerprint = cert.fingerprint256;
if (actualFingerprint !== expectedFingerprint) {
throw new Error(`Certificate fingerprint mismatch for ${hostname}`);
}
},
servername: 'api.internal.company.com', // SNI
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3'
}
});
// NTLM authentication'lı kurumsal proxy (Windows ortamları)
const ntlmProxyAgent = new ProxyAgent({
uri: process.env.HTTPS_PROXY,
token: `NTLM ${Buffer.from(ntlmToken).toString('base64')}`
});
// Sertifika hatalarında retry ile kullanım
async function secureRequest(url: string, options = {}) {
try {
return await request(url, {
...options,
dispatcher: secureAgent
});
} catch (error) {
if (error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
console.error('Certificate verification failed:', error);
// Fallback mantığı veya alert
}
throw error;
}
}
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
Effect benimsenmesi tahmin edilebilir bir eğri izler: 1. haftada kafa karışıklığı, 2. haftada direnç, 4. haftada netlik; type-safe error handling bütün bir runtime bug sınıfını ortadan kaldırır.
// 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
import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
const app = new Hono();
// Cloudflare Workers gibi edge ortamları için yapıldı
app.post('/proxy', async (c) => {
const { url, method = 'GET', headers, body } = await c.req.json();
try {
// Arka planda web standard fetch kullanır
const response = await fetch(url, {
method,
headers: {
...headers,
'User-Agent': 'Hono-Proxy/1.0'
},
body: method !== 'GET' ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(10000) // 10s timeout
});
// Verimlilik için response'u stream et
return new Response(response.body, {
status: response.status,
headers: response.headers
});
} catch (error) {
throw new HTTPException(502, {
message: `Upstream error: ${error.message}`
});
}
});
// Performans: 402,820 ops/sec vs itty-router'ın 212,598 ops/sec
export default app;
Bundle size ve cold start süresinin en önemli olduğu Cloudflare Workers, Vercel Edge Functions ve diğer edge runtime’lar için mükemmel.
Enterprise Ortamı: Proxy, Sertifikalar ve Kurumsal Ağlar
Kurumsal ortamda mı çalışıyorsunuz? İşte gerçekten bilmeniz gerekenler:
// Yaygın kurumsal gereksinimler
interface EnterpriseHttpClient {
proxySupport: boolean;
ntlmAuth: boolean;
certificatePinning: boolean;
mutualTLS: boolean;
customDNS: boolean;
socks5Support: boolean;
}
// Gerçek dünya kurumsal proxy setup'ı
import { SocksProxyAgent } from 'socks-proxy-agent';
class EnterpriseHttpClient {
private agent: any;
constructor() {
// Birden çok proxy environment variable'ını kontrol et
const proxyUrl = process.env.HTTPS_PROXY ||
process.env.https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy;
if (proxyUrl) {
// Farklı proxy tiplerini ele al
if (proxyUrl.startsWith('socks://')) {
this.agent = new SocksProxyAgent(proxyUrl);
} else {
this.agent = new HttpsProxyAgent(proxyUrl);
}
// Windows ortamları için NTLM desteği ekle
if (process.env.NTLM_DOMAIN) {
this.agent = new NtlmProxyAgent({
proxy: proxyUrl,
domain: process.env.NTLM_DOMAIN,
username: process.env.NTLM_USER,
password: process.env.NTLM_PASS
});
}
}
}
async request(url: string, options = {}) {
// Dahili ve harici URL'leri otomatik algıla ve ele al
const isInternal = url.includes('.internal.') ||
url.includes('.corp.') ||
url.startsWith('https://10.') ||
url.startsWith('https://192.168.');
if (isInternal) {
// Dahili URL'ler için proxy'yi atla
return await fetch(url, {
...options,
agent: undefined
});
}
// Harici URL'ler için proxy kullan
return await fetch(url, {
...options,
agent: this.agent
});
}
}
// Kısıtlı ortamlarda sertifika doğrulaması
async function validateCorporateCertificate(cert: any) {
// Kurumsal sertifika store'una göre kontrol et
const trustedFingerprints = await fetchFromCorporateVault('/api/trusted-certs');
if (!trustedFingerprints.includes(cert.fingerprint256)) {
// Güvenlik ekibini uyar
await notifySecurityTeam({
event: 'untrusted_certificate',
fingerprint: cert.fingerprint256,
hostname: cert.subject.CN
});
throw new Error('Certificate not in corporate trust store');
}
}
Kurumsal Proxy Debugging
Kurumsal ortamlardaki yaygın “connection refused” hataları genellikle şunlardan kaynaklanır:
- Kurumsal proxy’nin NTLM authentication gerektirmesi
- Proxy konfigürasyonunun ortamlar arasında değişmesi
- Dahili API’ların yanlışlıkla proxy üzerinden yönlendirilmesi
- Proxy’nin belirli header’ları çıkarması
Çözüm: Dahili ve harici URL’leri otomatik algılayan akıllı bir client:
const NO_PROXY_PATTERNS = [
'*.internal.company.com',
'10.*',
'192.168.*',
'localhost'
];
function shouldUseProxy(url: string): boolean {
const hostname = new URL(url).hostname;
return !NO_PROXY_PATTERNS.some(pattern => {
const regex = new RegExp(pattern.replace('*', '.*'));
return regex.test(hostname);
});
}
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
Kullanım senaryosuna ve ekip bağlamına göre karar matrisi:
| 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: Pratik Çözümler
Phantom Memory Leak Debugging
Servisler günler boyunca heap dump’larda belirgin bir iz bırakmadan yavaşça memory tüketebilir. Yaygın bir neden error handling’deki ince bug’lardır:
// Memory leak - görebilir misiniz?
const pendingRequests = new Map();
async function makeRequest(id: string, url: string) {
const controller = new AbortController();
pendingRequests.set(id, controller);
try {
const response = await fetch(url, {
signal: controller.signal
});
return response;
} catch (error) {
// BUG: Başarılı veya abort edilen request'leri asla temizlemiyoruz!
if (error.name === 'AbortError') {
throw error;
}
throw error;
} finally {
// Bu en baştan burada olmalıydı
pendingRequests.delete(id);
}
}
Ders: Request takibini her zaman temizleyin, error path’lerinde bile.
Connection Pool Exhaustion
Yüksek trafik olayları, servisler 502 dönmeye başladığında connection pool sınırlamalarını ortaya çıkarabilir. Sorun genellikle varsayılan connection limit’lerine dayanır:
// Önce: Binlerce connection ile ölüm
const badClient = axios.create(); // Limitsiz varsayılan agent kullanır
// Sonra: Kontrollü connection kullanımı
const goodClient = axios.create({
httpAgent: new require('http').Agent({
keepAlive: true,
maxSockets: 20, // Host başına
maxTotalSockets: 100, // Toplam
timeout: 60000,
keepAliveTimeout: 30000
}),
timeout: 10000
});
// Connection monitoring ekle
goodClient.interceptors.response.use(
(response) => {
console.log(`Active connections: ${process.env._http_agent?.sockets || 'unknown'}`);
return response;
}
);
Yavaş İstek Analizi
Aşağıdaki gibi bir request analyzer, yavaş istek hatalarının büyük bölümünü ortadan kaldırır:
class RequestAnalyzer {
private static slowRequests = new Map();
static trackRequest(url: string, options: RequestInit) {
const requestId = Math.random().toString(36);
const start = Date.now();
// Yavaş request'ler için request stack trace'ini takip et
const stack = new Error().stack;
this.slowRequests.set(requestId, {
url,
method: options.method || 'GET',
start,
stack: stack?.split('\n').slice(2, 8).join('\n') // Caller context'ini al
});
// 30 saniye sonra otomatik temizlik
setTimeout(() => {
const req = this.slowRequests.get(requestId);
if (req) {
const duration = Date.now() - req.start;
if (duration > 5000) {
console.warn(`Slow request detected after cleanup:`, {
...req,
duration,
possibleHang: duration > 30000
});
}
this.slowRequests.delete(requestId);
}
}, 30000);
return requestId;
}
static completeRequest(requestId: string, response?: Response, error?: Error) {
const req = this.slowRequests.get(requestId);
if (!req) return;
const duration = Date.now() - req.start;
if (duration > 1000) { // 1s üzeri request'leri logla
console.warn(`Slow request completed:`, {
...req,
duration,
status: response?.status,
error: error?.message,
// Bu, yavaş request'i kodunuzun hangi kısmının yaptığını belirlemeye yardım eder
callerStack: req.stack
});
}
this.slowRequests.delete(requestId);
}
}
// Herhangi bir HTTP client ile kullanım
async function trackedFetch(url: string, options: RequestInit = {}) {
const requestId = RequestAnalyzer.trackRequest(url, options);
try {
const response = await fetch(url, options);
RequestAnalyzer.completeRequest(requestId, response);
return response;
} catch (error) {
RequestAnalyzer.completeRequest(requestId, undefined, error as Error);
throw error;
}
}
Çıkarımlar
-
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.
Değerlendirme
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. Takımınıza ve kullanım senaryonuza en uygun HTTP client’ı seçin, doğru error handling uygulayın ve her şeyi izleyin. Ne seçerseniz seçin, ihtiyaç duyulmadan önce circuit breaker ekleyin.
Kaynaklar
- Node.js HTTP Modülü Belgeleri - İstemci istek API’si dahil yerleşik http modülü için resmi Node.js belgeleri
- undici - GitHub - Node.js yerel fetch API’sinin temeli olan, sıfırdan yazılmış resmi Node.js HTTP/1.1 istemcisi
- axios Belgeleri - Önleyiciler, örnek yapılandırması ve hata yönetimini kapsayan resmi axios belgeleri
- got - GitHub - Yeniden deneme, akış ve kancalar için destek içeren Node.js’e özel kullanıcı dostu HTTP istemcisi
- node-fetch - GitHub - Tarayıcı ve sunucu HTTP kalıplarını birleştirerek Node.js’e Fetch API’sini getiren hafif modül
- Node.js ile Fetch (undici) - Modern Node.js’te undici destekli yerel fetch API’sini kullanmak için resmi kılavuz
İlgili yazılar
LangChain'i production'da çalıştırmadan dersler: başarısızlığa yol açan anti-patternler, işe yarayan patternler, kod örnekleri ve maliyet optimizasyonu.
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 zaman hatalarının kökleri, Moment.js'den Day.js ve date-fns'e geçiş ve her yerde UTC kullanıp dönüşümü görüntüleme sınırına bırakma.
Yönlendirme motoru, analytics toplama ve API Gateway yapılandırması: günlük milyonlarca yönlendirme için performans optimizasyonları ve debugging stratejileri.