İçeriğe geç
Educora
İleri24 dk24 / 27

fetch ve REST API'ler

Sunucularla konuş: HTTP nasıl çalışır, REST kaynakları ve metotları, JSON ile `fetch`, durum kodları, hata yönetimi, `AbortController` ile zaman aşımı ve paralel istekler.

Kendini test et
Bu derste öğreneceklerin
  • Bir HTTP isteğini ve yanıtını açıklamak: metot, URL, başlıklar, gövde, durum kodu
  • fetch ile GET ve POST istekleri göndermek, JSON okumak, HTTP ve ağ hatalarını doğru işlemek
  • Sağlam asenkron kalıplar kurmak: zaman aşımı, yeniden deneme, paralel istekler ve URLSearchParams

Bir uygulamadaki hava tahmini, döviz kurları, sosyal ağdaki akış; hepsi sunuculardan bir API aracılığıyla gelir. Kodun bir HTTP isteği gönderir, sunucu da verilerle, genellikle JSON ile yanıt verir. fetch, tarayıcılarda ve Node.js'te (18. sürümden beri) bunun için hazır bulunan modern araçtır.

Bir dakikada HTTP

Bir istek; metot, URL, başlıklar (headers) ve bazen bir gövdeden (body) oluşur. Yanıtta bir durum kodu, başlıklar ve gövde bulunur. Durum kodunun ilk rakamı anlamı verir: 2xx başarı, 3xx yönlendirme, 4xx istemci hatası (hatalı istek), 5xx sunucu hatası.

Text
POST /api/orders HTTP/1.1
Host: shop.example.com
Content-Type: application/json
Authorization: Bearer <token>

{"productId": 7, "quantity": 2}

HTTP/1.1 201 Created
Content-Type: application/json

{"id": 1043, "productId": 7, "quantity": 2, "status": "new"}
Üstte istek, altta sunucunun yanıtı var. Başlıkları gövdeden boş bir satır ayırır.
Tanım
REST

Bir API kurma üslubu: veriler kendi URL'leri olan kaynaklardır (/users, /users/42), işlemler ise HTTP metotlarıyla ifade edilir. Her istek bağımsızdır (sunucu bir öncekini “hatırlamaz”) ve veriler genellikle JSON olarak taşınır.

MetotAnlamıÖrnek ve başarılı yanıt
GETokumakGET /users/42 → 200 OK
POSToluşturmakPOST /orders → 201 Created
PUT / PATCHtamamen / kısmen güncellemekPATCH /users/42 → 200 OK
DELETEsilmekDELETE /orders/1043 → 204 No Content
Sık göreceğin hata kodları: 400 (hatalı veri), 401 (giriş gerekli), 403 (izin yok), 404 (bulunamadı), 429 (çok fazla istek), 500 ve 503 (sunucu sorunu).

fetch ile GET

fetch(url), bir Response nesnesiyle tamamlanan bir Promise döndürür. İş iki adımdır: önce yanıtı beklersin (başlıklar gelir), sonra gövdeyi response.json() ile okursun; bu da bir Promise'tir. Önemli nokta: fetch yalnızca bir ağ hatasında (internet yok, bilinmeyen alan adı, CORS) reddedilir. 404 ve 500 yanıtları da “başarıyla gelir”; bu yüzden her zaman response.ok'u (durum 200–299) kontrol et.

JavaScript
async function getUser(id) {
  const response = await fetch(`https://api.example.com/users/${id}`);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return response.json();
}

try {
  const user = await getUser(42);
  console.log(user.name, user.city);
  await getUser(999999);
} catch (error) {
  console.log('Request failed:', error.message);
}
Beklenen çıktı
Aysel Baku
Request failed: HTTP 404
api.example.com temsilî bir adrestir: gerçek bir API'de alan adları farklı olur. Bu sayfadaki çalıştırıcı gerçek istek göndermez; bu yüzden aşağıda sunucuyu taklit edeceğiz.

Bir Response nesnesi doğrudan JavaScript'te oluşturulabilir. Bu, sunucuyu taklit edip internetsiz alıştırma yapmanı sağlar: fakeFetch tıpkı gerçek fetch gibi bir Response döndürür ve kodun farkı anlamaz. Testlerde de tam olarak böyle yapılır.

JavaScript
const db = { 42: { id: 42, name: 'Aysel', city: 'Baku' } };

async function fakeFetch(url) {
  await new Promise((resolve) => setTimeout(resolve, 50));
  const id = Number(url.split('/').pop());
  const user = db[id];
  return new Response(JSON.stringify(user ?? { error: 'Not found' }), {
    status: user ? 200 : 404,
    headers: { 'Content-Type': 'application/json' },
  });
}

for (const id of [42, 7]) {
  const response = await fakeFetch(`/api/users/${id}`);
  console.log(response.status, response.ok, response.headers.get('content-type'));
  console.log(await response.json());
}
▸ Beklenen çıktı
200 true application/json
{ id: 42, name: 'Aysel', city: 'Baku' }
404 false application/json
{ error: 'Not found' }

POST, başlıklar ve hatalar

Veri göndermek için fetch'e ikinci argüman olarak bir seçenekler nesnesi ver: method, headers ve body. Gövde metin olmalıdır; bu yüzden nesne JSON.stringify ile string'e çevrilir, Content-Type: application/json başlığı ise sunucuya biçimi bildirir. Giriş gerektiren API'ler genellikle bir Authorization: Bearer <token> başlığı bekler.

JavaScript
async function createOrder(order, token) {
  const response = await fetch('https://api.example.com/orders', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify(order),
  });

  if (response.status === 401) throw new Error('Please log in again');
  if (!response.ok) throw new Error(`Server error ${response.status}`);
  return response.json();
}

const saved = await createOrder({ productId: 7, quantity: 2 }, token);
console.log('Created order', saved.id, saved.status);
Beklenen çıktı
Created order 1043 new

Sağlam asenkron kalıplar

Gerçek ağ yavaş ve güvenilmezdir. Dört alışkanlık yardımcı olur: sorgu parametrelerini URL ve URLSearchParams ile kur (özel karakterler otomatik kodlanır); bağımsız istekleri Promise.all ile paralel gönder; bir süre sınırı koy — fetch(url, { signal: AbortSignal.timeout(8000) }), 8 saniye sonra isteği TimeoutError ile iptal eder; geçici hatalarda (5xx, 429) beklemeyi artırarak yeniden dene.

JavaScript
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', 'plov & dolma');
url.searchParams.set('city', 'Baku');
url.searchParams.set('page', 2);

console.log(url.toString());
console.log(url.searchParams.get('q'));
console.log(Object.fromEntries(url.searchParams));
▸ Beklenen çıktı
https://api.example.com/search?q=plov+%26+dolma&city=Baku&page=2
plov & dolma
{ q: 'plov & dolma', city: 'Baku', page: '2' }
Elle birleştirilmiş ?q=plov & dolma isteği bozardı: & yeni bir parametre sanılırdı. URLSearchParams onu %26 olarak kodlar.
JavaScript
function slowServer(ms, signal) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => resolve('data'), ms);
    signal.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new DOMException('Request took too long', 'TimeoutError'));
    });
  });
}

async function withTimeout(ms, limit) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), limit);
  try {
    return await slowServer(ms, controller.signal);
  } catch (error) {
    return `failed: ${error.name}`;
  } finally {
    clearTimeout(timer);
  }
}

console.log(await withTimeout(50, 200));
console.log(await withTimeout(500, 200));
▸ Beklenen çıktı
data
failed: TimeoutError
AbortController bir iptal sinyali oluşturur; gerçek fetch bu signal'ı kabul eder. AbortSignal.timeout(ms) aynı işi tek satırda yapar.
Alıştırma

getJSON(url) yardımcısını yaz: fakeFetch(url)'i çağırsın, yanıt başarılı değilse HTTP <status> mesajlı bir hata fırlatsın, başarılıysa ayrıştırılmış JSON'u döndürsün.

Alıştırma · JavaScript
const products = { 1: { id: 1, title: 'Notebook', price: 5 } };

async function fakeFetch(url) {
  const id = Number(url.split('/').pop());
  const product = products[id];
  return new Response(JSON.stringify(product ?? { error: 'Not found' }), {
    status: product ? 200 : 404,
    headers: { 'Content-Type': 'application/json' },
  });
}

async function getJSON(url) {
  // call fakeFetch, check response.ok, return the JSON
}

for (const url of ['/api/products/1', '/api/products/99']) {
  try {
    console.log(await getJSON(url));
  } catch (error) {
    console.log('Error:', error.message);
  }
}
▸ Beklenen çıktı
{ id: 1, title: 'Notebook', price: 5 }
Error: HTTP 404
Alıştırma

flakyFetch ilk iki seferde 503 döndürür. fetchWithRetry(url, retries) fonksiyonunu yaz: her başarısız denemeden sonra Attempt N: 503 yazdırsın; 5xx hatasında 100, sonra 200 ms (her seferinde iki katı) bekleyip yeniden denesin; retries hakkı bitince ya da 4xx gelince HTTP <status> hatası fırlatsın.

Alıştırma · JavaScript
let calls = 0;
async function flakyFetch(url) {
  calls++;
  const ok = calls >= 3;
  return new Response(JSON.stringify(ok ? { rates: { USD: 1.7 } } : {}), { status: ok ? 200 : 503 });
}

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function fetchWithRetry(url, retries) {
  // only one attempt for now: add retries with a growing delay
  const response = await flakyFetch(url);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

const data = await fetchWithRetry('/api/rates', 3);
console.log('Rates:', data.rates);
▸ Beklenen çıktı
Attempt 1: 503
Attempt 2: 503
Rates: { USD: 1.7 }

Önemli noktalar

  • HTTP: istek = metot + URL + başlıklar + gövde; yanıt = durum + başlıklar + gövde. 2xx başarı, 4xx istemci hatası, 5xx sunucu hatası.
  • REST: kaynaklar URL'lerde bulunur, işlemler metotlarla yapılır: GET okur, POST oluşturur, PUT/PATCH günceller, DELETE siler.
  • fetch yalnızca ağ hatalarında reddedilir; her zaman response.ok'u kontrol et ve gövdeyi await response.json() ile bir kez oku.
  • POST için method, bir Content-Type: application/json başlığı ve body: JSON.stringify(data) gönder.
  • Sağlamlık: süre sınırı (AbortSignal.timeout), geçici 5xx hatalarında yeniden deneme, paralel istekler için Promise.all ve parametreler için URLSearchParams.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
404 döndüren bir adrese fetch gönderiliyor. Ne olur?