Hari 18: JavaScript Fetch API – Mengambil Data dari Server

Bagaimana Website Seperti Media Sosial Bisa Menampilkan Data Real-Time? Rahasianya Adalah API! Hari Ini Kita Akan Belajar Mengambil Data dari API!

Javascript Fetch Api - Mengambil Data dari Server

Keywords: JavaScript fetch API, HTTP requests, API calls, promises, async await

API memungkinkan aplikasi web untuk berkomunikasi dengan server. Hari ini kita akan mempelajari cara mengambil data dari API. Setelah mempelajari form validation, sekarang saatnya kita melangkah ke level berikutnya: mengambil data dari server tanpa reload halaman – kemampuan fundamental yang membedakan website modern dari website statis!

Mengapa Fetch API Penting dalam Web Development?

Fetch API adalah interface modern untuk melakukan HTTP request di browser. Menurut survei State of JS 2022, 87% developer JavaScript menggunakan Fetch API secara rutin. Dengan Fetch API, Anda dapat:

  • Mengambil data dari server tanpa reload halaman (AJAX)
  • Mengirim data ke server untuk diproses
  • Membuat aplikasi web yang dinamis dan responsif
  • Mengintegrasikan dengan layanan pihak ketiga (Google Maps, Twitter, dll)
  • Membangun Single Page Applications (SPA)
  • Membuat aplikasi real-time dengan polling atau WebSockets

Tanpa kemampuan mengambil data dari server, website akan terbatas pada konten statis. Fetch API adalah jembatan antara frontend dan backend yang memungkinkan pertukaran data secara dinamis.

Apa itu Fetch API?

Fetch API adalah interface JavaScript yang menyediakan cara untuk mengakses dan memanipulasi bagian-bagian protokol HTTP, seperti request dan response. Fetch API menggantikan XMLHttpRequest (XHR) yang lebih lama dengan API yang lebih powerful dan fleksibel.

Keunggulan Fetch API:

  • Berbasis Promise, memudahkan penanganan operasi asynchronous
  • Syntax yang lebih bersih dan mudah dibaca
  • Dukungan untuk Stream API
  • Dapat digunakan di Service Workers
  • Tidak memerlukan library eksternal

Dasar-dasar Fetch API

Struktur Dasar Fetch API

fetch(url, options)
    .then(response => {
        // Handle response
    })
    .catch(error => {
        // Handle error
    });

Fetch GET Request

Mengambil data dari server:

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => {
        // Periksa apakah response sukses
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        return response.json(); // Parse JSON dari response
    })
    .then(data => {
        console.log(data); // Data yang diambil
    })
    .catch(error => {
        console.error('There has been a problem with your fetch operation:', error);
    });

Fetch POST Request

Mengirim data ke server:

const postData = {
    title: 'foo',
    body: 'bar',
    userId: 1
};

fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(postData)
})
    .then(response => response.json())
    .then(data => {
        console.log('Success:', data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

Menangani Response dengan Promises

Fetch API berbasis Promise, yang memudahkan penanganan operasi asynchronous:

Menggunakan Promise Chains

fetch('https://jsonplaceholder.typicode.com/posts')
    .then(response => {
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        return response.json();
    })
    .then(posts => {
        // Tampilkan posts
        console.log(posts);

        // Ambil data tambahan untuk post pertama
        return fetch(`https://jsonplaceholder.typicode.com/posts/${posts[0].id}/comments`);
    })
    .then(response => response.json())
    .then(comments => {
        console.log('Comments for first post:', comments);
    })
    .catch(error => {
        console.error('Fetch error:', error);
    });

Error Handling yang Komprehensif

fetch('https://jsonplaceholder.typicode.com/nonexistent')
    .then(response => {
        if (response.status === 404) {
            throw new Error('Resource not found (404)');
        }
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error('Fetch error:', error.message);
        // Tampilkan pesan error ke pengguna
        document.getElementById('error-message').textContent = error.message;
    });

Menggunakan Async/Await dengan Fetch API

Async/await adalah sintaks yang lebih modern untuk bekerja dengan Promise:

Dasar Async/Await

async function fetchData() {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');

        if (!response.ok) {
            throw new Error('Network response was not ok');
        }

        const data = await response.json();
        console.log(data);
        return data;
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

// Panggil fungsi
fetchData();

Async/Await dengan Multiple Requests

async function fetchMultipleData() {
    try {
        // Jalankan multiple requests secara paralel
        const [postsResponse, usersResponse] = await Promise.all([
            fetch('https://jsonplaceholder.typicode.com/posts'),
            fetch('https://jsonplaceholder.typicode.com/users')
        ]);

        if (!postsResponse.ok || !usersResponse.ok) {
            throw new Error('One or more requests failed');
        }

        const posts = await postsResponse.json();
        const users = await usersResponse.json();

        console.log('Posts:', posts);
        console.log('Users:', users);

        return { posts, users };
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

fetchMultipleData();

Sequential Requests dengan Async/Await

async function fetchSequentialData() {
    try {
        // Request pertama
        const postsResponse = await fetch('https://jsonplaceholder.typicode.com/posts');

        if (!postsResponse.ok) {
            throw new Error('Posts request failed');
        }

        const posts = await postsResponse.json();

        // Request kedua (bergantung pada hasil pertama)
        const firstPostId = posts[0].id;
        const commentsResponse = await fetch(`https://jsonplaceholder.typicode.com/posts/${firstPostId}/comments`);

        if (!commentsResponse.ok) {
            throw new Error('Comments request failed');
        }

        const comments = await commentsResponse.json();

        console.log('First post:', posts[0]);
        console.log('Comments:', comments);

        return { post: posts[0], comments };
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

fetchSequentialData();

Fetch API Options

Fetch API menerima berbagai opsi untuk mengontrol request:

HTTP Methods

// GET (default)
fetch('https://jsonplaceholder.typicode.com/posts');

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

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

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

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

Headers

fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer your_token_here',
        'Accept': 'application/json'
    },
    body: JSON.stringify({
        title: 'New Post',
        body: 'Post content',
        userId: 1
    })
});

Mode, Credentials, dan Cache

fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'GET',
    mode: 'cors', // cors, no-cors, same-origin
    credentials: 'include', // include, same-origin, omit
    cache: 'default', // default, no-store, reload, no-cache, force-cache
    redirect: 'follow', // follow, error, manual
    referrerPolicy: 'no-referrer-when-downgrade'
});

Response Properties dan Methods

Response Properties

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => {
        console.log('Status:', response.status); // 200
        console.log('Status Text:', response.statusText); // "OK"
        console.log('OK:', response.ok); // true
        console.log('Headers:', response.headers);
        console.log('URL:', response.url);
        console.log('Type:', response.type); // "basic", "cors", "opaque", etc.
        console.log('Redirected:', response.redirected);

        return response.json();
    })
    .then(data => console.log(data));

Response Methods

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => {
        // Parse response sebagai JSON
        return response.json();
    })
    .then(data => console.log(data));

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => {
        // Parse response sebagai text
        return response.text();
    })
    .then(text => console.log(text));

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => {
        // Parse response sebagai blob (untuk file)
        return response.blob();
    })
    .then(blob => console.log(blob));

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => {
        // Parse response sebagai FormData
        return response.formData();
    })
    .then(formData => console.log(formData));

Contoh Implementasi: Aplikasi Berita Sederhana

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>News App dengan Fetch API</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }

        .header {
            text-align: center;
            margin-bottom: 30px;
        }

        .news-container {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
        }

        .news-card {
            border: 1px solid #ddd;
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            transition: transform 0.3s ease;
        }

        .news-card:hover {
            transform: translateY(-5px);
        }

        .news-image {
            width: 100%;
            height: 200px;
            object-fit: cover;
        }

        .news-content {
            padding: 15px;
        }

        .news-title {
            font-size: 18px;
            margin-bottom: 10px;
            font-weight: bold;
        }

        .news-description {
            color: #666;
            margin-bottom: 15px;
        }

        .news-meta {
            display: flex;
            justify-content: space-between;
            font-size: 14px;
            color: #999;
        }

        .loading {
            text-align: center;
            padding: 20px;
            font-size: 18px;
        }

        .error {
            text-align: center;
            padding: 20px;
            color: #e74c3c;
            background-color: #fdecea;
            border-radius: 8px;
            margin-bottom: 20px;
        }

        .controls {
            margin-bottom: 20px;
            display: flex;
            gap: 10px;
        }

        .controls select, .controls button {
            padding: 8px 15px;
            border-radius: 4px;
            border: 1px solid #ddd;
        }

        .controls button {
            background-color: #3498db;
            color: white;
            cursor: pointer;
        }

        .controls button:hover {
            background-color: #2980b9;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>Berita Terkini</h1>
        <p>Dapatkan berita terbaru dari berbagai sumber</p>
    </div>

    <div class="controls">
        <select id="categorySelect">
            <option value="general">Umum</option>
            <option value="technology">Teknologi</option>
            <option value="business">Bisnis</option>
            <option value="entertainment">Hiburan</option>
        </select>
        <button id="refreshButton">Refresh</button>
    </div>

    <div id="errorContainer"></div>
    <div id="loadingIndicator" class="loading" style="display: none;">Memuat berita...</div>
    <div id="newsContainer" class="news-container"></div>

    <script>
        // API Key (gunakan API key yang valid dari newsapi.org)
        const API_KEY = 'your_api_key_here';
        const API_URL = 'https://newsapi.org/v2/top-headlines';

        // Mengakses elemen DOM
        const newsContainer = document.getElementById('newsContainer');
        const loadingIndicator = document.getElementById('loadingIndicator');
        const errorContainer = document.getElementById('errorContainer');
        const categorySelect = document.getElementById('categorySelect');
        const refreshButton = document.getElementById('refreshButton');

        // Fungsi untuk menampilkan loading
        function showLoading() {
            loadingIndicator.style.display = 'block';
            newsContainer.innerHTML = '';
            errorContainer.innerHTML = '';
        }

        // Fungsi untuk menyembunyikan loading
        function hideLoading() {
            loadingIndicator.style.display = 'none';
        }

        // Fungsi untuk menampilkan error
        function showError(message) {
            errorContainer.innerHTML = `<div class="error">${message}</div>`;
        }

        // Fungsi untuk membuat card berita
        function createNewsCard(article) {
            const card = document.createElement('div');
            card.className = 'news-card';

            // Gambar default jika tidak ada
            const imageUrl = article.urlToImage || 'https://picsum.photos/seed/news/400/200.jpg';

            card.innerHTML = `
                <img src="${imageUrl}" alt="${article.title}" class="news-image" onerror="this.src='https://picsum.photos/seed/news/400/200.jpg'">
                <div class="news-content">
                    <h3 class="news-title">${article.title}</h3>
                    <p class="news-description">${article.description || 'Tidak ada deskripsi'}</p>
                    <div class="news-meta">
                        <span>${article.source.name}</span>
                        <span>${new Date(article.publishedAt).toLocaleDateString('id-ID')}</span>
                    </div>
                </div>
            `;

            // Tambahkan event listener untuk membuka artikel
            card.addEventListener('click', () => {
                window.open(article.url, '_blank');
            });

            return card;
        }

        // Fungsi untuk mengambil berita
        async function fetchNews(category = 'general') {
            showLoading();

            try {
                // Karena kita tidak memiliki API key yang valid, kita akan menggunakan mock data
                // Dalam aplikasi nyata, gunakan:
                // const response = await fetch(`${API_URL}?country=id&category=${category}&apiKey=${API_KEY}`);

                // Simulasi delay untuk demo
                await new Promise(resolve => setTimeout(resolve, 1000));

                // Mock data untuk demo
                const mockData = {
                    status: "ok",
                    totalResults: 4,
                    articles: [
                        {
                            source: { id: null, name: "CNN Indonesia" },
                            author: "CNN Indonesia",
                            title: "Teknologi AI Mengubah Cara Kerja Industri",
                            description: "Kecerdasan buatan semakin mengintegrasikan diri dalam berbagai sektor industri.",
                            url: "https://example.com/article1",
                            urlToImage: "https://picsum.photos/seed/tech1/400/200.jpg",
                            publishedAt: "2023-05-15T10:30:00Z"
                        },
                        {
                            source: { id: null, name: "Detik.com" },
                            author: "Detik Tech",
                            title: "Startup Lokal Raih Pendanaan Besar",
                            description: "Sebuah startup teknologi asal Indonesia berhasil mendapatkan pendanaan seri A.",
                            url: "https://example.com/article2",
                            urlToImage: "https://picsum.photos/seed/startup/400/200.jpg",
                            publishedAt: "2023-05-14T14:45:00Z"
                        },
                        {
                            source: { id: null, name: "Kompas.com" },
                            author: "Kompas Tekno",
                            title: "Pemerintah Luncurkan Platform Digital Nasional",
                            description: "Platform baru ini diharapkan dapat meningkatkan layanan publik secara digital.",
                            url: "https://example.com/article3",
                            urlToImage: "https://picsum.photos/seed/government/400/200.jpg",
                            publishedAt: "2023-05-13T09:15:00Z"
                        },
                        {
                            source: { id: null, name: "Tempo.co" },
                            author: "Tempo Teknologi",
                            title: "Tren Smartphone 2023: Apa yang Baru?",
                            description: "Industri smartphone terus berinovasi dengan fitur-fitur canggih.",
                            url: "https://example.com/article4",
                            urlToImage: "https://picsum.photos/seed/smartphone/400/200.jpg",
                            publishedAt: "2023-05-12T16:20:00Z"
                        }
                    ]
                };

                // Proses data
                const data = mockData;

                if (data.status !== 'ok') {
                    throw new Error('Failed to fetch news');
                }

                // Tampilkan berita
                newsContainer.innerHTML = '';
                data.articles.forEach(article => {
                    const card = createNewsCard(article);
                    newsContainer.appendChild(card);
                });

            } catch (error) {
                console.error('Error fetching news:', error);
                showError('Gagal memuat berita. Silakan coba lagi nanti.');
            } finally {
                hideLoading();
            }
        }

        // Event listeners
        categorySelect.addEventListener('change', (e) => {
            fetchNews(e.target.value);
        });

        refreshButton.addEventListener('click', () => {
            fetchNews(categorySelect.value);
        });

        // Load initial news
        fetchNews();
    </script>
</body>
</html>

Praktik Terbaik dalam Menggunakan Fetch API

  1. Selalu Handle Errors – Jangan lupa menangani error network dan response:
   fetch(url)
       .then(response => {
           if (!response.ok) {
               throw new Error(`HTTP error! Status: ${response.status}`);
           }
           return response.json();
       })
       .catch(error => {
           console.error('Fetch error:', error);
       });
  1. Gunakan Async/Await untuk Kode yang Lebih Bersih – Lebih mudah dibaca dan di-debug:
   async function fetchData() {
       try {
           const response = await fetch(url);
           if (!response.ok) throw new Error('Network response was not ok');
           const data = await response.json();
           return data;
       } catch (error) {
           console.error('Fetch error:', error);
       }
   }
  1. Set Timeout untuk Request – Mencegah request yang terlalu lama:
   async function fetchWithTimeout(url, options = {}, timeout = 5000) {
       const controller = new AbortController();
       const timeoutId = setTimeout(() => controller.abort(), timeout);

       try {
           const response = await fetch(url, {
               ...options,
               signal: controller.signal
           });
           clearTimeout(timeoutId);
           return response;
       } catch (error) {
           if (error.name === 'AbortError') {
               throw new Error('Request timed out');
           }
           throw error;
       }
   }
  1. Gunakan Cache untuk Response yang Sering Diakses – Meningkatkan performa:
   const cache = {};

   async function fetchWithCache(url) {
       if (cache[url]) {
           return cache[url];
       }

       const response = await fetch(url);
       const data = await response.json();
       cache[url] = data;
       return data;
   }
  1. Validasi Response Sebelum Parsing – Pastikan response valid:
   async function safeFetch(url) {
       try {
           const response = await fetch(url);

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

           const contentType = response.headers.get('content-type');
           if (!contentType || !contentType.includes('application/json')) {
               throw new Error('Response is not JSON');
           }

           return await response.json();
       } catch (error) {
           console.error('Fetch error:', error);
           throw error;
       }
   }
  1. Gunakan AbortController untuk Membatalkan Request – Berguna untuk SPA:
   const controller = new AbortController();

   fetch(url, { signal: controller.signal })
       .then(response => response.json())
       .then(data => console.log(data))
       .catch(error => {
           if (error.name === 'AbortError') {
               console.log('Request was aborted');
           } else {
               console.error('Fetch error:', error);
           }
       });

   // Batalkan request
   controller.abort();

Debugging Fetch API

Saat mengalami masalah dengan Fetch API:

  1. Periksa Network Tab di Browser DevTools – Lihat request dan response
  2. Log Response Details – Periksa status, headers, dan body:
   fetch(url)
       .then(response => {
           console.log('Response status:', response.status);
           console.log('Response headers:', response.headers);
           return response.text();
       })
       .then(text => console.log('Response body:', text));
  1. Periksa CORS Issues – Pastikan server mengizinkan request dari domain Anda
  2. Validasi URL dan Options – Pastikan URL benar dan options valid
  3. Periksa API Documentation – Pastikan Anda menggunakan endpoint dan parameter yang benar

Kesimpulan

Fetch API adalah kemampuan fundamental yang memungkinkan JavaScript berkomunikasi dengan server. Hari ini Anda telah mempelajari:

  • Cara menggunakan Fetch API untuk HTTP request
  • Menangani response dan error dengan Promises
  • Menggunakan async/await untuk kode yang lebih bersih
  • Berbagai opsi dan konfigurasi Fetch API
  • Implementasi nyata dengan aplikasi berita
  • Praktik terbaik dalam menggunakan Fetch API

Dengan menguasai Fetch API, Anda telah membuka kemampuan untuk membuat aplikasi web yang dinamis dan terhubung dengan backend. Konsep ini adalah fondasi untuk topik lebih lanjut seperti RESTful APIs, GraphQL, dan bahkan framework JavaScript modern.

Teruslah berlatih dengan mengintegrasikan berbagai API publik ke dalam proyek Anda. Semakin sering Anda berlatih, semakin alami konsep ini akan terasa dalam pembuatan aplikasi web sehari-hari Anda.

#JavaScript #FetchAPI #WebAPI #JSIndonesia

Buat aplikasi sederhana yang mengambil data dari API publik dan tampilkan di halaman web, lalu share screenshotnya! Kami akan memberikan feedback dan tips untuk meningkatkan keterampilan coding Anda. Paling kreatif akan kita highlight di postingan minggu depan dan dapatkan kesempatan untuk ditampilkan di galeri proyek kami! Jangan lupa gunakan hashtag #30HariWebDevChallenge!

Leave a Comment