Node.js Fetch API: Yang Sering Dilewatkan Setiap Tutorial (Panduan 2026)

Terakhir diperbarui pada May 14, 2026
Node.js Fetch API: Yang Sering Dilewatkan Setiap Tutorial (Panduan 2026)
Ringkasan AI
Berhenti mengandalkan panggilan fetch dasar di produksi. Pelajari cara menangani timeout, error HTTP, logika retry, dan connection pooling di Node.js untuk 2026.

Setiap tutorial fetch Node.js mengajarkan Anda await fetch(url) lalu selesai di situ. Lalu aplikasi produksi Anda diam-diam menelan error 500, sebuah request menggantung selama 90 detik tanpa timeout, dan Anda menghabiskan Jumat malam untuk men-debug sesuatu yang seharusnya jelas sejak awal.

Saya sudah cukup lama membangun internal tools dan pipeline data di Thunderbit, dan saya bisa bilang: jarak antara "fetch bekerja di tutorial saya" dan "fetch bekerja di produksi" adalah tempat sebagian besar masalah terjadi. Seorang developer di Reddit merangkumnya dengan pas: "when you go into production, you realise you need something more resilient than the native fetch."

Yang lain mengaku, "Worked for 3 years as a web developer, TIL the fetch API's catch block is NOT for HTTP errors." Panduan ini membahas lima hal yang paling sering dilewatkan tutorial — jebakan error, timeout AbortController, logika retry, reuse koneksi, dan kapan harus melangkah lebih jauh dari fetch untuk ekstraksi data terstruktur. Kalau Anda pernah punya call fetch yang gagal diam-diam di produksi, artikel ini untuk Anda.

nodejs-fetch-error-handling-flowchart.png

Apa Itu Node.js Fetch API?

Node.js Fetch API adalah cara bawaan yang kompatibel dengan browser untuk membuat permintaan HTTP (GET, POST, PUT, DELETE, dll.) dari Node.js — tanpa perlu memasang Axios, node-fetch, atau paket lain. Kalau Anda sudah pernah memakai fetch() di browser, Anda sudah tahu sintaksnya. Sekarang API yang sama bekerja di server.

Berikut ringkasan sejarah versinya:

TonggakVersi NodeApa yang terjadi
Flag fetch eksperimentalv17.5.0 / v16.15.0fetch ditambahkan di balik --experimental-fetch
Default global fetchv18.0.0Fetch eksperimental tersedia secara global, didukung oleh Undici
Fetch stabilv21.0.0Tidak lagi eksperimental
Baseline produksi 2026v22 LTS / v24 LTSDirekomendasikan untuk produksi; v20 sekarang EOL

Di balik layar, fetch milik Node didukung oleh Undici — HTTP client berperforma tinggi yang dibangun khusus untuk Node.js. Ini tidak bergantung pada modul http bawaan yang lebih lama. Manfaat praktisnya: Anda mendapatkan API HTTP modern berbasis Promise yang bekerja sama persis di kode browser, backend Express, function serverless, dan skrip CLI.

Mengapa Node.js Fetch API Penting untuk Proyek Anda

Sebelum Node 18, setiap proyek baru dimulai dengan ritual yang sama: npm install axios atau npm install node-fetch. Di 2026, jika proyek Anda berjalan di Node LTS yang masih didukung, permintaan HTTP dasar tidak membutuhkan dependensi sama sekali. Itu keuntungan nyata untuk ukuran bundle, keamanan supply chain, dan onboarding (developer front-end dan back-end akhirnya berbagi API yang sama).

Ini area di mana native fetch benar-benar unggul:

SkenarioMengapa native fetch cocokCatatan produksi
Backend Express/Fastify memanggil REST APIasync/await yang familiar, tanpa dependensiTambahkan timeout dan pengecekan response.ok
Function serverless (Lambda, Vercel, dll.)Surface cold-start kecil, tanpa instal paketJaga timeout di bawah batas maksimum platform
Skrip CLI dan otomasiGET/POST sederhana tanpa setup proyekTambahkan retry/backoff untuk API yang tidak stabil
Pengiriman atau forwarding webhookMetode HTTP dan header standarJangan asal retry POST yang tidak idempoten
Laporan dan dashboardCocok untuk menarik JSON dari APIGunakan pagination dan connection pooling untuk loop
Komunikasi microserviceCocok untuk panggilan HTTP internal sederhanaPertimbangkan Got atau Undici langsung untuk retry, hooks, atau HTTP/2

Untuk proyek Node 22+ yang baru, native fetch adalah default yang masuk akal — kecuali Anda tahu butuh fitur yang tidak disediakannya (interceptor, retry bawaan, HTTP/2, dll.). Angka unduhan npm menunjukkan lanskap yang sedang berubah: node-fetch masih menarik ~144,9 juta unduhan mingguan, tetapi banyak di antaranya adalah warisan dan dependensi transitif. Axios berada di ~108,6 juta, Undici di ~106 juta, Got di ~36 juta, dan Ky di ~5,6 juta. Tren ini jelas: native fetch adalah baseline baru, dan client pihak ketiga dipakai untuk kebutuhan spesifik.

Native Fetch vs node-fetch vs Axios vs Got vs Ky: Matriks Keputusan 2026

Pertanyaan paling umum yang saya lihat di forum developer: "HTTP client mana yang sebaiknya saya pakai di Node.js?" Seorang pengguna Reddit merangkumnya begini: "why import a library…when the language/framework has functionality built in?" Poin yang adil — tapi jawabannya tergantung kebutuhan Anda.

http-client-libraries-comparison.png

FiturNative fetchnode-fetch v3axiosgot v15ky v2
Versi Node.js≥18 (rekomendasi 22/24 LTS)≥12.20Luas≥22≥22
Perlu instalasiTidakYaYaYaYa
Dukungan ESM + CJSKeduanya (global)Hanya ESM (v3)KeduanyaHanya ESMHanya ESM
Auto-reject pada 4xx/5xxTidakTidakYaYaYa
Retry bawaanTidakTidakTidakYaYa
Request interceptorTidakTidakYaYa (hooks)Ya (hooks)
Dukungan streamingWeb ReadableStreamYaTerbatasStream Node yang kuatBerbasis fetch
Jejak bundle/instal0 KB~107 KB, 3 dependensi~2,8 MB, 4 dependensi~355 KB, 12 dependensi~405 KB, 0 dependensi
Dukungan HTTP/2Melalui dispatcher UndiciTidakTidakYaTidak (wrapper fetch)

Catatan singkat soal masalah ESM/CJS: node-fetch v3 hanya ESM, yang mematahkan banyak proyek yang memakai require(). Native fetch bersifat global — ia bekerja di file CJS dan ESM tanpa perlu trik import. Kalau Anda terjebak di node-fetch v2 karena CommonJS, native fetch menyelesaikan masalah itu sepenuhnya.

Dan soal kekhawatiran stabilitas di awal: ya, memang ada bug nyata pada implementasi fetch awal di Node 18. Seorang developer di Reddit menyebut "Had a wild bug with native node 18 fetch recently so had to convert our app." Itu pada 2023. Di 2026, dengan Node 22 dan 24 LTS, masalah itu sudah beres. Native fetch siap dipakai di produksi.

Kapan Sebaiknya Tetap Pakai Native Fetch

Gunakan native fetch ketika:

  • Proyek Anda berjalan di Node 22 LTS atau Node 24 LTS.
  • Request-nya sederhana, berupa REST call (GET, POST, PUT, DELETE).
  • Anda bersedia menambahkan wrapper kecil untuk response.ok, parsing JSON, timeout, dan retry.
  • Anda ingin tanpa surface dependensi dan kekhawatiran supply chain yang lebih kecil.
  • Anda menghargai kesetaraan API browser/server.
  • Anda berada di lingkungan serverless atau edge, di mana API bawaan lebih disukai.

Kapan Axios, Got, atau Ky Lebih Masuk Akal

Axios tepat saat tim Anda mengandalkan request/response interceptor (misalnya refresh token otomatis, header tenant, logging terpusat), saat Anda ingin HTTP error langsung ditolak secara default, atau saat perlu kompatibilitas mundur dengan runtime Node yang lebih lama.

Got dibuat untuk service Node berthroughput tinggi yang butuh retry bawaan, hooks, fase timeout lanjutan, stream, helper pagination, Unix socket, workflow proxy/caching, atau dukungan HTTP/2. Ini adalah pisau Swiss Army untuk pekerjaan HTTP khusus Node.

Ky adalah titik manis kalau Anda suka kesederhanaan fetch tetapi ingin lebih sedikit boilerplate — ia menambahkan retry, timeout, hooks, dan HTTPError dalam paket kecil tanpa dependensi.

Cara Melakukan GET Request dengan Node.js Fetch API

GET request dengan async/await terlihat seperti ini:

const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const post = await response.json();
console.log(post.title);
// → "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

Dan versi rantai .then(), kalau Anda lebih suka:

fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(response => response.json())
  .then(post => console.log(post.title))
  .catch(error => console.error(error));

Keduanya bekerja. Tapi belum satu pun yang aman untuk produksi (nanti saya jelaskan).

Pembaca response yang perlu Anda kenal:

MetodeDipakai saat
response.json()Server mengembalikan JSON
response.text()Server mengembalikan HTML, teks biasa, CSV, Markdown
response.arrayBuffer()Anda butuh data biner (gambar, file)
response.bodyAnda butuh pemrosesan streaming/chunked

Pola yang lebih baik — yang benar-benar memeriksa error:

async function getPost(id) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status} ${response.statusText}`);
  }
  return response.json();
}

const post = await getPost(1);
console.log(post.title);

Baris if (!response.ok) itulah perbedaan antara tutorial dan kode produksi. Dan di situlah jebakan terbesar dimulai.

Cara Mengirim POST Request dengan Node.js Fetch API

POST request mengikuti bentuk yang sama — Anda hanya perlu mengatur method, header, dan body:

const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'Panduan Node fetch',
    body: 'Fetch untuk produksi butuh penanganan error.',
    userId: 1,
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

const created = await response.json();
console.log(created.id); // → 101

Mengirim Tipe Request Lain (PUT, DELETE, PATCH)

PUT, PATCH, dan DELETE memakai struktur yang sama dengan nilai method berbeda:

// PUT — penggantian penuh
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 1, title: 'Diganti', body: 'Penggantian penuh', userId: 1 }),
});

// PATCH — pembaruan sebagian
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Pembaruan sebagian' }),
});

// DELETE
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'DELETE',
});

Jebakan body-parser Express: Kalau Anda mengirim JSON POST ke server Express dan req.body ternyata undefined, perbaikannya hampir selalu ini: gunakan express.json(), bukan express.urlencoded(). Server membutuhkan middleware express.json() sebelum route Anda untuk mem-parse body Content-Type: application/json. Ini salah satu pertanyaan Stack Overflow yang paling sering muncul tentang Express, dan hampir selalu menjebak orang.

import express from 'express';
const app = express();
app.use(express.json()); // ← Ini yang Anda butuhkan untuk body JSON POST
app.post('/api/posts', (req, res) => {
  res.json({ received: req.body });
});

Jebakan Error fetch() yang Merusak Aplikasi Produksi

fetch-error-handling-flowchart.png

Inilah sumber sebagian besar bug fetch di produksi.

fetch() tidak me-reject promise-nya pada error HTTP 4xx atau 5xx. Ia hanya me-reject untuk kegagalan di level jaringan — error DNS, tidak ada internet, request dibatalkan. Kalau server mengembalikan 403 Forbidden atau 500 Internal Server Error, fetch menganggapnya sebagai response yang berhasil. Blok .catch() Anda tidak pernah berjalan. try/catch Anda tidak menangkapnya. Kode Anda dengan senang hati memproses apa pun yang dikirim server.

Dokumentasi MDN menjelaskannya dengan jelas, tapi kebanyakan tutorial melewatinya begitu saja. Hasilnya? Kode seperti ini terlihat baik-baik saja tetapi diam-diam menelan error:

try {
  const response = await fetch('https://api.example.com/private');
  const data = await response.json(); // ← Ini tetap berjalan bahkan pada 403
  console.log('Terlihat berhasil:', data);
} catch (error) {
  // Hanya kegagalan level jaringan yang masuk ke sini
  console.error('Ditangkap:', error);
}

Ringkasan cepat tentang apa yang sebenarnya ditangkap oleh tiap pola:

PolaMenangkap error jaringanMenangkap 4xx/5xxParse JSON dengan amanDapat digunakan ulang
Raw .then(res => res.json())Ya (via .catch())TidakTanpa perlindungan content-typeTidak
try/catch dengan await fetch()YaTidakTanpa perlindungan content-typeTidak
Manual if (!res.ok) per callYaYaTergantung tiap callSebagian
Wrapper fetchJSON() kustomYaYaYaYa

Bangun Wrapper fetchJSON() yang Bisa Dipakai Ulang

Buat satu wrapper. Import di mana-mana. Hentikan kebiasaan copy-paste if (!response.ok) ke setiap file:

export class HTTPError extends Error {
  constructor(message, { status, statusText, url, body }) {
    super(message);
    this.name = 'HTTPError';
    this.status = status;
    this.statusText = statusText;
    this.url = url;
    this.body = body;
  }
}

export async function fetchJSON(url, options = {}) {
  const response = await fetch(url, {
    headers: {
      Accept: 'application/json',
      ...options.headers,
    },
    ...options,
  });

  const contentType = response.headers.get('content-type') || '';
  const isJSON = contentType.includes('application/json');
  const body = isJSON ? await response.json().catch(() => null) : await response.text();

  if (!response.ok) {
    throw new HTTPError(`HTTP ${response.status} ${response.statusText}`, {
      status: response.status,
      statusText: response.statusText,
      url: response.url,
      body,
    });
  }

  return body;
}

Sekarang, ketika server mengembalikan 403:

try {
  const data = await fetchJSON('https://api.example.com/private');
} catch (error) {
  if (error instanceof HTTPError) {
    console.error(`Server mengembalikan ${error.status}:`, error.body);
  } else {
    console.error('Kegagalan jaringan atau lainnya:', error);
  }
}

Error membawa kode status, body response, dan URL — semua yang Anda butuhkan untuk logging, alerting, atau pesan ke pengguna. Import sekali, pakai di mana-mana.

AbortController dan Timeout: Pola Produksi untuk Node.js Fetch API

request-retry-pooling-flowchart.png

Tanpa timeout, call fetch akan menggantung tanpa batas ketika server remote diam saja. Route Express Anda terblokir. Lambda Anda menghabiskan jatah eksekusi. Skrip Anda cuma... diam.

Saya cek hasil pencarian teratas: tidak satu pun tutorial fetch khusus Node.js yang membahas pembatalan request atau timeout. Padahal timeout adalah salah satu alasan utama developer tetap memakai Axios atau Got. Ada thread Reddit yang judulnya memang "Node fetch does not timeout".

Menggunakan AbortSignal.timeout() (Node 18.11+)

Pendekatan paling sederhana — satu opsi tambahan:

try {
  const response = await fetch('https://api.example.com/data', {
    signal: AbortSignal.timeout(5000), // 5 detik
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === 'TimeoutError') {
    console.error('Request timeout setelah 5 detik.');
  } else {
    throw error;
  }
}

Catatan: AbortSignal.timeout() melempar TimeoutError, bukan AbortError. Detail ini bahkan sering keliru dipahami developer yang sudah berpengalaman.

Timeout Manual dengan AbortController

Untuk kontrol yang lebih besar — atau jika Anda perlu membatalkan request berdasarkan tindakan pengguna, bukan sekadar timer:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('https://api.example.com/data', {
    signal: controller.signal,
  });
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.error('Request dibatalkan secara manual.');
  } else {
    throw error;
  }
} finally {
  clearTimeout(timeout);
}

Menangani AbortError vs TimeoutError

Perbedaan ini penting untuk logging dan pesan ke pengguna:

Jalur abortNama error di blok catch
AbortSignal.timeout(ms)TimeoutError
controller.abort()AbortError
Kegagalan DNS/jaringanBiasanya TypeError: fetch failed

Berikut skenario praktis — route Express yang memanggil API eksternal dan harus merespons dalam 3 detik:

app.get('/dashboard', async (req, res, next) => {
  try {
    const data = await fetchJSON('https://api.example.com/report', {
      signal: AbortSignal.timeout(3000),
    });
    res.json(data);
  } catch (error) {
    if (error.name === 'TimeoutError') {
      res.status(504).json({ error: 'API upstream timeout' });
      return;
    }
    next(error);
  }
});

Tanpa pola ini, API upstream yang lambat akan memblokir seluruh route sampai klien menyerah.

Logika Retry dan Connection Reuse: Membuat Node.js Fetch API Siap Produksi

Native fetch tidak punya retry bawaan. Gangguan jaringan sesaat atau 503 sementara berarti request langsung gagal. Untuk kebanyakan operasi baca di produksi, itu tidak bisa diterima.

Wrapper Retry yang Dapat Dirangkai dengan Exponential Backoff

Ini sengaja singkat — sekitar 10 baris logika sungguhan:

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

export async function fetchWithRetry(url, options = {}, retries = 2) {
  for (let attempt = 0; ; attempt++) {
    try {
      const response = await fetch(url, options);
      if (response.ok || ![408, 429, 500, 502, 503, 504].includes(response.status)) {
        return response;
      }
      if (attempt >= retries) return response;
    } catch (error) {
      if (attempt >= retries) throw error;
    }
    await wait(250 * 2 ** attempt); // 250ms, 500ms, 1000ms...
  }
}

Kapan Harus Retry dan Kapan Tidak

  • Lakukan retry: Request GET dan HEAD yang idempoten, status sementara (408, 429, 500, 502, 503, 504), gangguan jaringan sesaat.
  • Jangan retry: Request POST yang tidak idempoten dan membuat record, memproses pembayaran, atau memicu efek samping — kecuali Anda memakai idempotency key.
  • Hormati Retry-After: Untuk 429 (rate limit) dan 503 (service unavailable), periksa header Retry-After sebelum melakukan backoff.

Kalau Anda tidak ingin membangun logika retry sendiri, Ky adalah wrapper fetch ringan yang menambahkan retry, timeout, hooks, dan HTTPError langsung dari kotaknya — tanpa dependensi.

Connection Reuse dengan Agent dan Pool dari Undici

Untuk loop berthroughput tinggi — scraping ratusan halaman, memanggil API secara batch, polling sebuah service — penggunaan ulang koneksi TCP menghemat waktu yang signifikan. Setiap koneksi baru berarti DNS lookup baru, TCP handshake baru, dan (untuk HTTPS) negosiasi TLS baru.

Karena fetch Node didukung oleh Undici, Anda bisa memberikan dispatcher kustom:

import { Agent } from 'undici';

const agent = new Agent({
  keepAliveTimeout: 10_000,
  keepAliveMaxTimeout: 60_000,
});

const response = await fetch('https://api.example.com/data', {
  dispatcher: agent,
});

Untuk kontrol yang lebih besar pada origin tertentu:

import { Pool } from 'undici';

const pool = new Pool('https://api.example.com', { connections: 10 });

const response = await fetch('https://api.example.com/data', {
  dispatcher: pool,
});

// Saat selesai:
await pool.close();

Benchmark README Undici menunjukkan bahwa connection reuse dan pooling bisa meningkatkan throughput secara drastis — undici - dispatch mencapai sekitar 22.234 req/detik versus undici - fetch sekitar 5.904 req/detik dalam benchmark lokal mereka. Angka dunia nyata akan bervariasi, tetapi arahnya jelas: jika Anda melakukan banyak request ke origin yang sama, pooling itu penting.

Satu hal lagi: selalu konsumsi atau batalkan response body. Body yang tidak dikonsumsi bisa menyebabkan kebocoran resource di internal HTTP Node.

Streaming Response dengan Node.js Fetch API

Unduhan file besar, feed JSON terpotong, server-sent events, output LLM — ini semua kasus di mana menunggu seluruh response sebelum memprosesnya membuang waktu dan memori. Streaming memungkinkan Anda menangani data saat data itu tiba.

streaming-data-chunking-process.png

Node 18+ sudah menyertakan ReadableStream yang kompatibel dengan browser. Berikut cara men-stream response JSON dengan pemisah baris dan memproses setiap baris saat masuk:

const response = await fetch('https://example.com/large-file.ndjson');
if (!response.ok) throw new Error(`HTTP ${response.status}`);

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { value, done } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });

  let newlineIndex;
  while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
    const line = buffer.slice(0, newlineIndex).trim();
    buffer = buffer.slice(newlineIndex + 1);
    if (line) {
      const item = JSON.parse(line);
      console.log('Diproses:', item.id);
    }
  }
}

Untuk streaming teks yang lebih sederhana (misalnya, meneruskan output LLM ke stdout):

const response = await fetch('https://example.com/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();

for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  process.stdout.write(decoder.decode(value, { stream: true }));
}

Streaming adalah area di mana native fetch dan Got sama-sama unggul. Dukungan streaming Axios lebih terbatas.

Saat fetch() Mencapai Batasnya: Web Scraping Terstruktur dengan API

Pada suatu titik, fetch bukan lagi bottleneck. Masalah sebenarnya menjadi: "Saya punya HTML, lalu apa?"

web-scraping-vs-ai-extraction-api-comparison.png

Fetch adalah HTTP client — ia mengambil byte, teks, JSON, atau HTML. Ia tidak paham kartu produk, harga, rating, atau tabel kontak. Untuk web scraping terstruktur, stack mentah yang umum biasanya seperti ini:

  1. fetch() untuk mengunduh HTML
  2. Cheerio (atau yang serupa) untuk memilih elemen dengan CSS selector
  3. Logika pagination kustom
  4. Rendering JavaScript ketika halaman dirender di sisi klien
  5. Penanganan proxy/anti-bot/CAPTCHA
  6. Pemeliharaan selector setiap kali layout situs berubah

Berikut contoh fetch + Cheerio yang umum — sekitar 15 baris untuk men-scrape judul produk:

import * as cheerio from 'cheerio';

const response = await fetch('https://example-store.com/products');
if (!response.ok) throw new Error(`HTTP ${response.status}`);

const html = await response.text();
const $ = cheerio.load(html);

const products = $('.product-card')
  .map((_, el) => ({
    name: $(el).find('.product-title').text().trim(),
    price: $(el).find('.price').text().trim(),
    url: new URL($(el).find('a').attr('href'), response.url).href,
  }))
  .get();

console.log(products);

Ini bekerja untuk halaman yang stabil dengan HTML yang bisa diprediksi. Tapi cepat menjadi rapuh — konten yang dirender JavaScript, perubahan nama class, langkah anti-bot, dan pagination semuanya menambah kompleksitas.

Open API Thunderbit: Dari HTML Mentah ke Data Terstruktur dalam Satu Call

Di sinilah jenis alat yang berbeda menjadi berguna. Di Thunderbit, kami membangun layer API yang menangani bagian-bagian rumit — rendering JavaScript, perlindungan anti-bot, perubahan layout — sehingga Anda bisa fokus pada data yang benar-benar Anda inginkan.

Distill API (POST /distill): Mengubah URL apa pun menjadi Markdown yang bersih. Berguna untuk memberi makan LLM, membangun knowledge base, atau analisis konten — tanpa perlu HTML parser.

Extract API (POST /extract): Definisikan JSON Schema yang menggambarkan data terstruktur yang Anda inginkan (nama produk, harga, rating), lalu AI mengekstraknya. Tanpa CSS selector, tanpa rusak saat layout berubah.

Berikut tugas scraping produk yang sama menggunakan Extract API milik Thunderbit — dipanggil dengan native fetch:

const response = await fetch('https://openapi.thunderbit.com/openapi/v1/extract', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.THUNDERBIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example-store.com/products',
    renderMode: 'basic',
    schema: {
      type: 'object',
      properties: {
        products: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              name: { type: 'string', description: 'Nama produk' },
              price: { type: 'string', description: 'Harga produk yang ditampilkan' },
              rating: { type: 'number', description: 'Rata-rata rating pelanggan' },
            },
            required: ['name', 'price'],
          },
        },
      },
      required: ['products'],
    },
  }),
});

if (!response.ok) throw new Error(`Thunderbit API: ${response.status}`);
const result = await response.json();
console.log(result.data);

Perbandingannya: ~15 baris fetch + Cheerio (ditambah selector yang rapuh) versus satu call API yang mengembalikan JSON bersih. Untuk batch job, Thunderbit mendukung hingga 50 URL per batch extract call dan hingga 100 URL per batch distill call.

Thunderbit bukan pengganti fetch — fetch adalah transportnya. Thunderbit adalah layer ekstraksi yang Anda gunakan saat parsing HTML mentah menjadi masalah utamanya. Kalau Anda penasaran soal harga, tier gratis memberi Anda 600 unit API untuk bereksperimen, dan paket berbayar mulai dari $6/bulan. Anda juga bisa mencoba Thunderbit Chrome Extension untuk ekstraksi tanpa kode langsung di browser.

Untuk pembahasan lebih lanjut tentang pendekatan scraping terstruktur, panduan kami tentang alat ekstraksi data terbaik, cara membuat web scraper, dan scraping data dari website ke Excel membahas workflow spesifik secara detail.

Referensi Cepat: Cheat Sheet Node.js Fetch API

Bagian ini cocok disimpan sebagai bookmark. Kembali lagi saat Anda butuh pola yang bisa langsung di-copy-paste.

PolaCuplikan
GET dasarconst res = await fetch(url); const data = await res.json();
POST dasarawait fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
Pengecekan error HTTPif (!res.ok) throw new Error(\\HTTP ${res.status}\);
Timeout (sederhana)await fetch(url, { signal: AbortSignal.timeout(5000) });
Abort manualconst c = new AbortController(); setTimeout(() => c.abort(), 5000); await fetch(url, { signal: c.signal });
Status yang di-retryRetry 408, 429, 500, 502, 503, 504. Jangan asal retry POST.
Wrapper JSONPakai fetchJSON() untuk cek ok, parse content type, dan lempar HTTPError.
Connection poolimport { Pool } from 'undici'; const pool = new Pool(origin, { connections: 10 }); fetch(url, { dispatcher: pool });
Stream chunkconst reader = res.body.getReader(); loop over await reader.read()
Ekstraksi terstrukturPakai Thunderbit Extract API saat tujuan Anda adalah field dari halaman web, bukan HTML mentah.

Kesimpulan dan Poin Utama

Native fetch di Node.js sudah siap produksi di 2026 — tidak perlu node-fetch untuk proyek baru, dan tidak perlu dependensi Axios secara default. Tapi fetch() mentah saja bukan strategi HTTP produksi.

Lima hal yang paling sering dilewatkan tutorial — dan yang dibahas panduan ini:

  1. Jebakan error: fetch() tidak melempar untuk 4xx/5xx. Selalu cek response.ok atau gunakan wrapper seperti fetchJSON().
  2. Timeout: Gunakan AbortSignal.timeout() untuk kasus sederhana. AbortSignal.timeout() melempar TimeoutError; controller.abort() manual melempar AbortError.
  3. Logika retry: Tidak tersedia bawaan. Tambahkan exponential backoff untuk request idempoten dan kegagalan sementara. Atau pakai Ky untuk retry gaya fetch yang siap pakai.
  4. Reuse koneksi: Untuk loop berthroughput tinggi, gunakan Agent atau Pool dari Undici melalui opsi dispatcher.
  5. Ekstraksi terstruktur: Saat Anda butuh data dari halaman web (bukan sekadar HTML mentah), pertimbangkan API ekstraksi seperti Thunderbit alih-alih memelihara CSS selector yang rapuh.

Matriks keputusan dalam satu kalimat: gunakan native fetch untuk sebagian besar proyek, Axios untuk interceptor, Got untuk retry bawaan dan HTTP/2, Ky untuk fetch dengan default yang lebih baik, dan API Thunderbit saat skrip scraping berbasis fetch Anda menjadi terlalu rumit untuk dipelihara.

Coba Thunderbit untuk Ekstraksi Data Terstruktur

Cobalah pola-pola di panduan ini. Dan kalau Anda ingin melihat bagaimana Thunderbit menangani ekstraksi terstruktur, tier gratis adalah tempat yang bagus untuk memulai — atau tonton penjelasan di channel YouTube Thunderbit.

Coba Thunderbit untuk Web Scraping dengan AI Get Started Free

FAQ

1. Apakah fetch sudah встроенный di Node.js atau perlu diinstal?

Fetch sudah встроенный di Node.js 18 ke atas — tidak perlu instalasi. Ia menjadi stabil di Node 21 dan didukung penuh di Node 22 LTS dan Node 24 LTS. Untuk versi Node yang lebih lama, Anda bisa memakai paket npm node-fetch, tetapi proyek baru sebaiknya menargetkan rilis LTS yang masih didukung.

2. Apakah fetch melempar error pada respons 404 atau 500?

Tidak. Fetch hanya me-reject promise pada kegagalan level jaringan (error DNS, tidak ada koneksi, request dibatalkan). Respons HTTP seperti 404, 403, dan 500 akan resolve secara normal dengan response.ok === false. Anda harus memeriksa response.ok atau response.status secara eksplisit — atau gunakan wrapper seperti fungsi fetchJSON() yang ditunjukkan dalam panduan ini.

3. Bagaimana cara menambahkan timeout ke fetch di Node.js?

Pendekatan paling sederhana adalah AbortSignal.timeout(ms), tersedia di Node 18.11+: await fetch(url, { signal: AbortSignal.timeout(5000) }). Ini akan melempar TimeoutError jika request melebihi 5 detik. Untuk kontrol yang lebih besar, buat AbortController secara manual dan panggil controller.abort() dari setTimeout. Tangkap AbortError untuk pola manual dan TimeoutError untuk AbortSignal.timeout().

4. Apakah saya bisa memakai fetch untuk web scraping di Node.js?

Ya, tetapi fetch hanya mengembalikan HTML mentah. Anda tetap membutuhkan parser seperti Cheerio untuk mengekstrak elemen tertentu, plus logika kustom untuk pagination, halaman yang dirender JavaScript, dan langkah anti-bot. Untuk ekstraksi data terstruktur dalam skala besar — saat Anda ingin JSON bersih berisi nama produk, harga, atau info kontak — pertimbangkan Extract API Thunderbit, yang memakai AI untuk mengembalikan data terstruktur tanpa CSS selector atau kode yang bergantung pada layout.

5. Haruskah saya pindah dari Axios ke native fetch di 2026?

Untuk proyek baru di Node 22+, native fetch adalah default yang kuat. Ia tanpa dependensi, berbasis Promise, dan memakai API yang sama seperti fetch di browser. Tetap gunakan Axios jika Anda bergantung pada request/response interceptor, penolakan HTTP error bawaan, atau butuh kompatibilitas mundur dengan versi Node yang lebih lama. Keduanya pilihan yang valid — keputusan bergantung pada fitur apa yang benar-benar dipakai proyek Anda.

Pelajari Lebih Lanjut

Fawad Khan
Fawad Khan
Fawad menulis untuk mencari nafkah, dan sejujurnya, dia memang cukup menyukainya. Selama bertahun-tahun, dia mendalami apa yang membuat sebuah copy melekat di ingatan — dan apa yang membuat pembaca langsung menggulir lewat. Kalau ditanya soal marketing, dia bisa bicara berjam-jam. Kalau ditanya soal carbonara, dia bisa bicara lebih lama lagi.
Daftar Isi

Ambil data halaman web cukup dengan bertanya

Cukup bilang apa yang kamu butuhkan dalam bahasa Inggris sederhana. Atau lebih baik lagi, tak perlu bilang apa-apa.

Coba Thunderbit gratis
Ekstrak Data menggunakan AI
Dengan mudah transfer data ke Google Sheets, Airtable, atau Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week