Dùng fetch lấy dữ liệu từ API và hiển thị danh sách bài viết lên trang web

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Hầu hết ứng dụng web hiện đại đều lấy dữ liệu từ API thay vì viết cứng vào trang. Trong bài này, bạn sẽ dùng fetch và async/await để tải danh sách bài viết từ một API thử nghiệm công khai, hiển thị lên trang, và xử lý các trạng thái đang tải, lỗi, rỗng.

API là gì và fetch làm gì

API là "cửa giao tiếp" để chương trình này lấy dữ liệu từ chương trình khác, thường qua giao thức HTTP và trả về định dạng JSON. Hàm fetch có sẵn trong trình duyệt gửi yêu cầu HTTP và trả về một Promise. Bạn dùng await để chờ kết quả mà không chặn giao diện.

Để thực hành, ta dùng dịch vụ thử nghiệm JSONPlaceholder, cung cấp dữ liệu giả miễn phí tại địa chỉ https://jsonplaceholder.typicode.com/posts. Dịch vụ này chỉ dành cho học tập và thử nghiệm.

Bước 1: HTML khung

<main class="container">
  <h1>Bài viết mới</h1>
  <input id="search" type="search" placeholder="Tìm theo tiêu đề...">
  <p id="status" role="status"></p>
  <ul id="posts"></ul>
</main>
<script src="app.js"></script>

Bước 2: Gọi API với async/await

Điều quan trọng nhất: fetch chỉ báo lỗi (reject) khi mạng hỏng, không báo lỗi khi máy chủ trả 404 hay 500. Vì vậy bạn phải tự kiểm tra response.ok.

const API_URL = 'https://jsonplaceholder.typicode.com/posts?_limit=20';

async function fetchPosts() {
  const response = await fetch(API_URL);
  if (!response.ok) {
    throw new Error('Máy chủ trả về lỗi ' + response.status);
  }
  return response.json();
}

Bước 3: Hiển thị dữ liệu và các trạng thái

Một giao diện tử tế cần xử lý ít nhất bốn trạng thái: đang tải, có dữ liệu, không có kết quả, và lỗi. Đây là phần nhiều người mới bỏ qua.

const statusEl = document.getElementById('status');
const listEl = document.getElementById('posts');
const searchEl = document.getElementById('search');

let allPosts = [];

function render(posts) {
  listEl.innerHTML = '';

  if (posts.length === 0) {
    statusEl.textContent = 'Không tìm thấy bài viết nào.';
    return;
  }

  statusEl.textContent = '';
  const fragment = document.createDocumentFragment();

  posts.forEach((post) => {
    const li = document.createElement('li');
    const title = document.createElement('h2');
    const body = document.createElement('p');

    title.textContent = post.title;
    body.textContent = post.body;

    li.append(title, body);
    fragment.appendChild(li);
  });

  listEl.appendChild(fragment);
}

async function init() {
  statusEl.textContent = 'Đang tải...';
  try {
    allPosts = await fetchPosts();
    render(allPosts);
  } catch (error) {
    statusEl.textContent = 'Không tải được dữ liệu. Vui lòng thử lại.';
    console.error(error);
  }
}

searchEl.addEventListener('input', () => {
  const keyword = searchEl.value.trim().toLowerCase();
  const filtered = allPosts.filter((p) =>
    p.title.toLowerCase().includes(keyword)
  );
  render(filtered);
});

init();

Ta dùng textContent để gán nội dung từ API, tránh nguy cơ chèn mã độc nếu dữ liệu không đáng tin. DocumentFragment giúp thêm nhiều phần tử vào trang chỉ với một lần cập nhật DOM, hiệu quả hơn thêm từng phần tử một.

Gửi dữ liệu lên với POST

Ngoài việc lấy dữ liệu, bạn có thể gửi dữ liệu mới. Cần khai báo phương thức, tiêu đề loại nội dung và thân yêu cầu dạng chuỗi JSON:

async function createPost(title, body) {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, body, userId: 1 }),
  });
  if (!response.ok) throw new Error('Gửi thất bại');
  return response.json();
}

API thử nghiệm sẽ giả lập việc tạo bản ghi và trả về dữ liệu kèm một mã định danh, nhưng không thật sự lưu lại.

Lỗi thường gặp

  • Lỗi CORS: trình duyệt chặn yêu cầu đến tên miền khác nếu máy chủ không cho phép. Lỗi này nằm ở cấu hình máy chủ, không sửa được từ phía trang web của bạn.
  • Quên await: bạn nhận về Promise thay vì dữ liệu. Hãy nhớ await cả fetch lẫn response.json().
  • Chạy trang bằng đường dẫn file://: một số trường hợp sẽ bị hạn chế, nên dùng Live Server hoặc máy chủ cục bộ.
  • Không xử lý lỗi mạng: luôn bọc trong try/catch và hiển thị thông báo thân thiện.

Nâng cấp tiếp theo

  • Thêm phân trang bằng tham số _page và _limit.
  • Dùng AbortController để hủy yêu cầu cũ khi người dùng gõ tìm kiếm nhanh.
  • Thêm kỹ thuật debounce cho ô tìm kiếm.
  • Lưu tạm kết quả vào sessionStorage để tránh gọi lại.

Kết luận

Gọi API là kỹ năng nền tảng của lập trình web. Hãy nhớ ba điều: kiểm tra response.ok, xử lý lỗi bằng try/catch, và luôn thiết kế đủ các trạng thái của giao diện. Có nền tảng này, bạn có thể kết nối với hầu hết dịch vụ dữ liệu khác.

Để học cách xây dựng cả phần giao diện lẫn API, bạn có thể xem các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment