DEV Community

Cover image for GeoVista: A Vanilla JavaScript Country Explorer with REST API
Ahmed Niazy
Ahmed Niazy

Posted on

GeoVista: A Vanilla JavaScript Country Explorer with REST API

Image description


“Where in the world?” App: Deep Dive

In this tutorial, we’ll explore a small vanilla‑JavaScript app that:

  • Fetches country data from a REST API
  • Displays each country as a card (flag + key details)
  • Supports search, filter-by-region, pagination
  • Toggles between light/dark themes

All code lives in one HTML file with embedded CSS and JS. Let’s break it down.


HTML Structure

<body>
  <!-- Header & Theme Toggle -->
  <header class="header">
    <h1 class="logo">Where in the world?</h1>
    <button class="mode-toggle">
      <i class="fas fa-moon"></i> Dark Mode
    </button>
  </header>

  <!-- Main Content -->
  <main class="main">
    <!-- Search & Filter Controls -->
    <div class="controls">
      <div class="search-container">
        <input id="searchInput" placeholder="Search for a country…" />
        <i class="fas fa-search search-icon"></i>
      </div>
      <div class="filter-container">
        <select id="regionFilter">
          <option value="">Filter by Region</option>
          <option value="Africa">Africa</option>
          <option value="Americas">Americas</option>
          <option value="Asia">Asia</option>
          <option value="Europe">Europe</option>
          <option value="Oceania">Oceania</option>
        </select>
      </div>
    </div>

    <!-- Country Cards Injected Here -->
    <section id="cardsContainer" class="cards"></section>

    <!-- Pagination Controls -->
    <div class="pagination">
      <button id="prevBtn">Prev</button>
      <span id="pageInfo"></span>
      <button id="nextBtn">Next</button>
    </div>
  </main>
</body>
Enter fullscreen mode Exit fullscreen mode
  • <header>: Displays title + theme‑toggle button.
  • .controls: Contains search input + region filter dropdown.
  • #cardsContainer: JavaScript injects country cards here.
  • .pagination: “Prev” / page info / “Next”.

CSS Highlights

:root {
  --bg-color: hsl(0, 0%, 98%);
  --text-color: hsl(200, 15%, 8%);
  --element-color: #fff;
}
.dark-mode {
  --bg-color: hsl(207, 26%, 17%);
  --text-color: #fff;
  --element-color: hsl(209, 23%, 22%);
}

body {
  background: var(--bg-color);
  color: var(--text-color);
}

/* Cards Grid */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 2rem;
}

/* Country Card */
.card {
  background: var(--element-color);
  border-radius: 5px;
  overflow: hidden;
  box-shadow: 0 2px 9px rgba(0,0,0,0.05);
}
.card img {
  width: 100%;
  height: 150px;
  object-fit: cover;
}
.card .info {
  padding: 1rem;
}
Enter fullscreen mode Exit fullscreen mode
  • CSS variables toggle theme via adding/removing .dark-mode on <body>.
  • Responsive grid for cards ensures neat layout across viewports.

JavaScript Walkthrough

const API_URL = "https://restcountries.com/v2/all";
let countries = [];
let filteredList = [];
let currentPage = 1;
const itemsPerPage = 12;

// 1. Fetch & initialize
async function fetchCountries() {
  const res = await fetch(API_URL);
  countries = await res.json();
  filteredList = countries;
  renderPage();
}
fetchCountries();

// 2. Render cards for current page
function renderPage() {
  const totalPages = Math.ceil(filteredList.length / itemsPerPage);
  const start = (currentPage - 1) * itemsPerPage;
  const slice = filteredList.slice(start, start + itemsPerPage);

  displayCountries(slice);
  updatePagination(totalPages);
}

// 3. Display a list of country cards
function displayCountries(list) {
  const container = document.getElementById("cardsContainer");
  container.innerHTML = "";
  list.forEach(c => {
    const card = document.createElement("div");
    card.className = "card";
    card.innerHTML = `
      <img src="${c.flag}" alt="Flag of ${c.name}" />
      <div class="info">
        <h2>${c.name}</h2>
        <p><strong>Population:</strong> ${c.population.toLocaleString()}</p>
        <p><strong>Region:</strong> ${c.region}</p>
        <p><strong>Capital:</strong> ${c.capital || ""}</p>
      </div>`;
    container.appendChild(card);
  });
}

// 4. Update pagination controls
function updatePagination(totalPages) {
  document.getElementById("pageInfo").textContent = `${currentPage} / ${totalPages}`;
  document.getElementById("prevBtn").disabled = currentPage === 1;
  document.getElementById("nextBtn").disabled = currentPage === totalPages;
}

// 5. Event listeners: Prev / Next
document.getElementById("prevBtn").addEventListener("click", () => {
  if (currentPage > 1) currentPage--, renderPage();
});
document.getElementById("nextBtn").addEventListener("click", () => {
  const totalPages = Math.ceil(filteredList.length / itemsPerPage);
  if (currentPage < totalPages) currentPage++, renderPage();
});

// 6. Search filter
document.getElementById("searchInput").addEventListener("input", e => {
  const term = e.target.value.trim().toLowerCase();
  filteredList = countries.filter(c => c.name.toLowerCase().includes(term));
  currentPage = 1;
  renderPage();
});

// 7. Region filter
document.getElementById("regionFilter").addEventListener("change", e => {
  const region = e.target.value;
  filteredList = region
    ? countries.filter(c => c.region === region)
    : countries;
  currentPage = 1;
  renderPage();
});

// 8. Dark/light mode toggle
document.querySelector(".mode-toggle").addEventListener("click", function() {
  document.body.classList.toggle("dark-mode");
  this.innerHTML = document.body.classList.contains("dark-mode")
    ? `<i class="fas fa-sun"></i> Light Mode`
    : `<i class="fas fa-moon"></i> Dark Mode`;
});
Enter fullscreen mode Exit fullscreen mode

Show Code

  1. Data Fetch: Load all countries once.
  2. Pagination Logic: Slice the filtered list into pages of 12.
  3. Search & Filter: Update filteredList on input/change, reset page to 1.
  4. Theme Toggle: Flip CSS variables via .dark-mode.

Final Thoughts

This compact example demonstrates:

  • Vanilla JS for DOM updates & state management
  • Responsive design with CSS Grid & custom properties
  • Progressive enhancement: users without JS still see the static HTML

Top comments (0)