Node.js'te HTTP isteği atmanın en kısa yolu global fetch() fonksiyonudur. Tarayıcıdaki Fetch API ile uyumludur, hiçbir paket kurmadan kullanılır ve bir Response nesnesine çözülen Promise döndürür: const yanit = await fetch(url), ardından await yanit.json(). Belgelere göre fetch v18.0.0 ile deneysel bayrak gerektirmez oldu, v21.0.0 ile de deneysel olmaktan çıktı. Node'daki uygulama, Node için sıfırdan yazılmış HTTP/1.1 istemcisi undici üzerine kuruludur ve yerleşik node:http istemcisini kullanmaz.
Bu yazıda GET ve POST istekleri, res.ok ile durum kontrolü, başlıklar, JSON/form/metin gövdeleri, AbortSignal.timeout ile zaman aşımı, ağ hatalarının yapısı, yanıtı stream olarak okumak ve gövdenin tek kullanımlık olması konularını Node.js v26.13.0 ile çalıştırılmış örneklerle anlatıyoruz. Örneklerin dış servislere bağımlı olmaması için aynı betikte açılan küçük bir yerel sunucu kullanıyoruz. Sunucu tarafını öğrenmek için node:http ile sunucu yazısına, serinin tamamı için Node.js öğrenme rehberi sayfasına bakabilirsiniz.
Örneklerde kullanılan test sunucusu
Aşağıdaki sunucu.mjs dosyası rastgele bir portta açılan ve birkaç uç nokta sunan bir yardımcıdır: /urun JSON döndürür, /yanki gelen gövdeyi ve içerik türünü geri yollar, /yavas iki saniye bekler, /akis gövdeyi üç parça halinde gönderir, diğer yollar 404 verir. Sonraki örnekler bu dosyayı içe aktarır ve işi bitince sunucuyu kapatır. Sunucu kodunun nasıl çalıştığı HTTP sunucusu yazısında ayrıntılı anlatılıyor.
// sunucu.mjs: örnekler için küçük bir test sunucusu
import http from 'node:http';
import { text } from 'node:stream/consumers';
export function testSunucusu() {
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, 'http://localhost');
if (url.pathname === '/urun') {
res.writeHead(200, { 'Content-Type': 'application/json', 'X-Istek-Id': 'abc123' });
res.end(JSON.stringify({ id: 7, ad: 'Klavye', fiyat: 950 }));
} else if (url.pathname === '/yanki') {
const govde = await text(req);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ method: req.method, tur: req.headers['content-type'], govde }));
} else if (url.pathname === '/yavas') {
setTimeout(() => res.end('geç geldi'), 2000);
} else if (url.pathname === '/akis') {
let i = 0;
const t = setInterval(() => {
res.write(`parça ${++i}\n`);
if (i === 3) { clearInterval(t); res.end(); }
}, 30);
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ hata: 'yok' }));
}
});
return new Promise((resolve) => {
server.listen(0, () => resolve({ server, base: `http://localhost:${server.address().port}` }));
});
}GET isteği ve yanıtı okumak
fetch(url) varsayılan olarak GET isteği atar. Dönen Response nesnesinde status (sayısal durum), statusText, ok (durum 200-299 aralığındaysa true) ve headers bulunur. Gövdeyi okumak için json(), text(), arrayBuffer(), blob() gibi Promise döndüren yöntemler kullanılır:
import { testSunucusu } from './sunucu.mjs';
const { server, base } = await testSunucusu();
const yanit = await fetch(`${base}/urun`);
console.log(yanit.ok, yanit.status, yanit.statusText); // çıktı: true 200 OK
console.log(yanit.headers.get('content-type'), yanit.headers.get('x-istek-id')); // çıktı: application/json abc123
console.log(await yanit.json()); // çıktı: { id: 7, ad: 'Klavye', fiyat: 950 }
const yok = await fetch(`${base}/olmayan`);
console.log(yok.ok, yok.status); // çıktı: false 404
server.close();Bu örneğin en önemli satırı sonuncusudur: 404 yanıtı Promise'i reddetmedi. fetch yalnızca isteği hiç tamamlayamadığında (ağ hatası, iptal, geçersiz URL) reddeder; 4xx ve 5xx durumları başarılı bir HTTP alışverişidir ve ok alanı false olarak gelir. Node.js Learn'deki Axios'tan fetch'e geçiş rehberi bu farkı özellikle vurgular: Axios hata durumlarında reddettiği için, ona göre yazılmış hata yönetimi fetch'e geçince elle gözden geçirilmelidir. Başlık adları headers.get()'te büyük/küçük harfe duyarsızdır; sunucu X-Istek-Id gönderdi, biz x-istek-id ile okuduk.
POST isteği: JSON, form ve metin gövdeleri

Gövde göndermek için method ve body seçeneklerini verirsiniz. body string, URLSearchParams, FormData, Blob, ArrayBuffer/TypedArray ya da bir stream olabilir. JSON gönderirken nesneyi JSON.stringify ile string'e çevirmek ve Content-Type başlığını kendiniz eklemek zorundasınız; aksi halde istek düz metin olarak etiketlenir:
import { testSunucusu } from './sunucu.mjs';
const { server, base } = await testSunucusu();
const r1 = await fetch(`${base}/yanki`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ urun: 7, adet: 2 }),
});
console.log(await r1.json());
// çıktı: {
// çıktı: method: 'POST',
// çıktı: tur: 'application/json',
// çıktı: govde: '{"urun":7,"adet":2}'
// çıktı: }
const r2 = await fetch(`${base}/yanki`, {
method: 'POST',
body: new URLSearchParams({ ad: 'Ali', sehir: 'İzmir' }),
});
console.log(await r2.json());
// çıktı: {
// çıktı: method: 'POST',
// çıktı: tur: 'application/x-www-form-urlencoded;charset=UTF-8',
// çıktı: govde: 'ad=Ali&sehir=%C4%B0zmir'
// çıktı: }
const r3 = await fetch(`${base}/yanki`, { method: 'POST', body: 'düz metin' });
console.log(await r3.json()); // çıktı: { method: 'POST', tur: 'text/plain;charset=UTF-8', govde: 'düz metin' }
server.close();Çıktıdaki tur alanı, sunucunun gördüğü Content-Type başlığıdır. URLSearchParams gövdesi otomatik olarak application/x-www-form-urlencoded;charset=UTF-8 türüyle ve yüzde kodlanmış halde ("İ" harfi %C4%B0) gitti; Axios rehberi de postForm çağrılarının bu biçime çevrildiğini gösterir. String gövde ise text/plain;charset=UTF-8 olarak etiketlendi. İlk örnekte başlığı vermeseydik JSON gövde de düz metin olarak etiketlenecek ve birçok sunucu onu ayrıştırmayacaktı. JSON.stringify'ın tarih, undefined ve döngüsel nesnelerle ilgili kuralları için JSON.parse ve stringify yazısına bakabilirsiniz.
| body türü | Otomatik Content-Type | Tipik kullanım |
|---|---|---|
| string | text/plain;charset=UTF-8 | JSON için başlığı elle ekleyin |
| URLSearchParams | application/x-www-form-urlencoded;charset=UTF-8 | Klasik form gönderimi |
| FormData | multipart/form-data (sınır değeriyle) | Dosya yükleme |
| Blob | Blob'un type değeri | Hazır ikili içerik |
| ArrayBuffer, TypedArray | Yok | Ham bayt gönderimi |
Zaman aşımı ve hataların yapısı

fetch'in kendine ait bir timeout seçeneği yoktur. Belirli bir sürede tamamlanmayan isteği kesmek için signal seçeneğine AbortSignal.timeout(ms) verirsiniz; globals belgesine göre bu yöntem belirtilen milisaniye sonra iptal edilen yeni bir sinyal döndürür. Birden fazla iptal kaynağını (kullanıcı iptali ve zaman aşımı) birleştirmek için AbortSignal.any([...]) kullanılabilir. Aşağıdaki örnek hem zaman aşımını hem de kapalı bir porta bağlanmaya çalışınca oluşan ağ hatasını gösterir:
import { testSunucusu } from './sunucu.mjs';
const { server, base } = await testSunucusu();
try {
await fetch(`${base}/yavas`, { signal: AbortSignal.timeout(500) });
} catch (err) {
console.log(err.name, err.message); // çıktı: TimeoutError The operation was aborted due to timeout
}
server.closeAllConnections();
server.close();
await new Promise((r) => server.once('close', r));
// Sunucu artık kapalı: aynı porta bağlanılamaz
try {
await fetch(`${base}/urun`);
} catch (err) {
console.log(err.name, err.message); // çıktı: TypeError fetch failed
console.log(err.cause.code); // çıktı: ECONNREFUSED
}İki hata türü farklıdır ve ayırt etmek önemlidir. Zaman aşımında err.name TimeoutError olur; elle controller.abort() çağırdığınızda ise AbortError görürsünüz. Ağ düzeyindeki hatalarda Node'un fetch'i TypeError: fetch failed fırlatır ve asıl nedeni err.cause içinde taşır; bu örnekte cause.code ECONNREFUSED, yani o portta dinleyen kimse yok. Günlük kaydına yalnızca err.message yazmak "fetch failed" dışında bilgi vermez; err.cause'u da kaydedin. Hata kodlarının anlamı için ECONNREFUSED ve diğer sistem hataları yazısına, iptal mekanizmasının genel kullanımı için AbortController yazısına bakabilirsiniz.
Kullanıcının bir işlemi iptal edebildiği ve aynı zamanda bir üst süre sınırı istediğiniz durumlarda iki sinyali birleştirirsiniz. Globals belgesine göre AbortSignal.any([s1, s2]), verilen sinyallerden herhangi biri iptal edildiğinde iptal olan yeni bir sinyal döndürür ve reason alanına iptale yol açan sinyalin nedenini koyar. Örneğin AbortSignal.any([kullaniciIptali.signal, AbortSignal.timeout(5000)]) ile yapılan bir istek, ya kullanıcı vazgeçtiğinde ya da beş saniye dolduğunda kesilir; yakalanan hatanın name değerine (AbortError veya TimeoutError) bakarak hangisinin olduğunu anlarsınız. Belgeler ayrıca 'abort' olayına eklenen dinleyicilerin bellek sızıntısını önlemek için { once: true } ile eklenmesini önerir. Bir sinyal iptal edildikten sonra o durumda kalır; süresi dolmuş bir zaman aşımı sinyalini başka bir istekte tekrar kullanırsanız o istek de iptal edilmiş sayılır. Bu yüzden her istek için yeni bir AbortSignal.timeout() oluşturun.
Yanıt gövdesini stream olarak okumak
response.body bir Web ReadableStream'dir. Node'da bu stream'i for await...of ile parça parça okuyabilirsiniz; parçalar Uint8Array olarak gelir. Büyük indirmelerde ya da sunucunun yanıtı yavaş yavaş ürettiği durumlarda (log akışı, uzun süren üretim) tüm gövdeyi beklemek yerine geldikçe işlemenizi sağlar. Çok baytlı karakterlerin parça sınırında bölünmemesi için TextDecoder'ı { stream: true } ile kullanın; Learn'deki undici rehberi de aynı yolu izler. Aynı örnek gövdenin tek kullanımlık olduğunu ve clone() ile nasıl kopyalandığını da gösterir:
import { testSunucusu } from './sunucu.mjs';
const { server, base } = await testSunucusu();
const yanit = await fetch(`${base}/akis`);
const cozucu = new TextDecoder();
for await (const parca of yanit.body) {
process.stdout.write('geldi: ' + cozucu.decode(parca, { stream: true }));
}
// çıktı: geldi: parça 1
// çıktı: geldi: parça 2
// çıktı: geldi: parça 3
const ikinci = await fetch(`${base}/urun`);
const kopya = ikinci.clone();
console.log(await ikinci.text()); // çıktı: {"id":7,"ad":"Klavye","fiyat":950}
console.log((await kopya.json()).ad); // çıktı: Klavye
try {
await ikinci.json();
} catch (err) {
console.log(err.name, err.message); // çıktı: TypeError Body is unusable: Body has already been read
}
server.close();Gövde bir stream olduğu için yalnızca bir kez okunabilir: text()'ten sonra json() çağırmak TypeError verdi. Hem ham metni kaydetmek hem de JSON olarak işlemek istiyorsanız ya metni bir kez alıp JSON.parse ile kendiniz çevirin ya da okumadan önce clone() ile kopya alın. Web stream'ini Node stream bekleyen bir API'ye (ör. fs.createWriteStream ile pipeline) vermek için Readable.fromWeb(yanit.body) kullanılır; bu dönüşüm stream yazısında gösteriliyor.
Yeniden kullanılabilir bir getJSON yardımcısı
Gerçek projelerde her çağrıda aynı kontrolleri yazmak yerine küçük bir yardımcı fonksiyon tanımlamak işe yarar. Aşağıdaki getJSON, varsayılan bir zaman aşımı ekler, ok değilse durum kodu ve sunucunun hata gövdesini içeren bir Error fırlatır, başarılıysa JSON'u döndürür. Error'ın cause seçeneği dil düzeyinde bir özelliktir ve JavaScript hata yönetimi yazısında anlatılır. Örnek ayrıca Request nesnesiyle isteği önceden tanımlamayı ve undici sürümünü veren process.versions.undici alanını gösterir:
import { testSunucusu } from './sunucu.mjs';
const { server, base } = await testSunucusu();
async function getJSON(url, { zamanAsimi = 3000, ...secenekler } = {}) {
const yanit = await fetch(url, { ...secenekler, signal: AbortSignal.timeout(zamanAsimi) });
if (!yanit.ok) {
throw new Error(`HTTP ${yanit.status} ${yanit.statusText}`, { cause: await yanit.text() });
}
return yanit.json();
}
console.log(await getJSON(`${base}/urun`)); // çıktı: { id: 7, ad: 'Klavye', fiyat: 950 }
try {
await getJSON(`${base}/olmayan`);
} catch (err) {
console.log(err.message, err.cause); // çıktı: HTTP 404 Not Found {"hata":"yok"}
}
const istek = new Request(`${base}/urun`, { headers: { Accept: 'application/json' } });
console.log(istek.method, istek.headers.get('accept')); // çıktı: GET application/json
console.log(typeof process.versions.undici); // çıktı: string
server.close();Hata dalında da gövdeyi text() ile okuduk; hem sunucunun hata mesajını kaybetmemiş olduk hem de gövdeyi tüketmiş olduk. Globals belgesine göre FormData, Headers, Request ve Response sınıfları da fetch ile birlikte global olarak kullanılabilir. Bağlı undici sürümünü merak ediyorsanız process.versions.undici değerine bakın; değer Node sürümüne göre değiştiği için burada yalnızca türünü yazdırdık.
Vekil sunucu, sertifika ve özel dispatcher
Kurumsal ağlarda istekler bir vekil (proxy) sunucudan geçmek zorunda olabilir. Node.js Learn'deki "Enterprise Network Configuration" rehberine göre HTTP_PROXY, HTTPS_PROXY ve NO_PROXY ortam değişkenleri, NODE_USE_ENV_PROXY=1 ortam değişkeni ya da --use-env-proxy bayrağı etkinleştirildiğinde dikkate alınır; bu destek fetch() için v22.21.0 veya v24.0.0 ve sonrasında vardır. Aynı rehber, kurum içi sertifika otoriteleri için NODE_EXTRA_CA_CERTS ve sistem sertifika deposunu kullanan --use-system-ca seçeneklerini anlatır, ayrıca http.globalAgent ayarlarının fetch()'i etkilemediğini not eder. Daha ince bağlantı yönetimi (bağlantı havuzu, eşzamanlı bağlantı sayısı) için globals belgesi, undici'nin Dispatcher sınıfıyla uyumlu bir nesneyi dispatcher seçeneğiyle vermeyi gösterir; bu, undici paketini ayrıca kurmayı gerektirir.
NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://vekil.ornek:8080 node app.mjs
node --use-env-proxy app.mjsUygulamalı mini örnek: dış API'den veri çeken bir betik
Her gece bir dış servisten döviz ya da stok verisi çeken bir betik düşünün. Sağlam bir sürüm şu adımları izler: URL'yi new URL() ve searchParams.set() ile kurmak (elle string birleştirip kodlamayı unutmamak için); isteği AbortSignal.timeout() ile sınırlamak; res.ok değilse durum kodu ve gövdeyle anlamlı bir hata üretmek; TypeError geldiğinde err.cause.code'a bakarak ağ hatasını ayırt etmek; başarılı yanıtı json() ile alıp beklenen alanları kontrol etmek; ve hata durumunda process.exitCode'u sıfırdan farklı yapmak. Çıkış kodlarının zamanlayıcılar ve izleme araçları için anlamı process.argv ve çıkış kodları yazısında anlatılıyor. Aynı uca çok sayıda istek atacaksanız hepsini aynı anda başlatmak yerine sınırlı sayıda paralel istekle ilerlemek hem karşı servisi hem de sizin sürecinizi korur; birden fazla Promise'i birlikte beklemenin yolları Promise.all ve allSettled yazısında.
Sık yapılan hatalar

- 404 ve 500'ün reddedileceğini sanmak: fetch yalnızca ağ hatasında reddeder; her çağrıda
res.okya dares.statuskontrol edin. - JSON gönderirken Content-Type eklememek: String gövde
text/plainolarak gider. - Nesneyi doğrudan body'ye vermek:
JSON.stringifyile çevirmeden verilen düz nesne JSON olarak gönderilmez. - Gövdeyi iki kez okumak:
Body is unusablehatası alırsınız;clone()kullanın. - Zaman aşımı koymamak: Yanıt vermeyen bir uç betiği uzun süre bekletebilir;
AbortSignal.timeout()ekleyin. - Hata kaydında err.cause'u atlamak: "fetch failed" tek başına sorunun nedenini söylemez.
Sık Sorulan Sorular
Node'daki fetch ile tarayıcıdaki fetch aynı mı?
Belgeler onu tarayıcı uyumlu bir uygulama olarak tanımlar; temel API (Request, Response, Headers, FormData) aynıdır. Altta undici çalıştığı için bağlantı havuzu, vekil ve sertifika ayarları Node'a özgüdür; çerezler ya da CORS gibi tarayıcı kavramlarını ise kendiniz yönetirsiniz.
Axios yerine fetch kullanmak mantıklı mı?
Node.js Learn'de Axios çağrılarını fetch'e çeviren resmî bir codemod rehberi bulunur ve bağımlılık azaltmayı amaç olarak gösterir. Rehber, timeout, maxRedirects, interceptor gibi Axios özelliklerinin doğrudan karşılığı olmadığı için bu kullanımların otomatik çevrilmediğini de belirtir.
fetch ile dosya indirip diske nasıl yazarım?
pipeline(Readable.fromWeb(yanit.body), createWriteStream(dosya)) deseniyle gövdeyi belleğe almadan diske akıtabilirsiniz; pipeline'ın hata ve backpressure davranışı pipeline yazısında anlatılıyor.
WebSocket için de fetch mi kullanılır?
Hayır. Node'da ayrı bir global WebSocket istemcisi vardır. WebSocket ile Server-Sent Events arasındaki farklar için WebSocket ve SSE yazısına bakabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Node.js öğrenme rehberi · node:http ile sunucu · AbortController ve zaman aşımı · URL ve URLSearchParams · JavaScript async/await · Node.js REST API geliştirme