Home  /  Blog  /  Quran Search Engine

Build a Quran Search Engine with JavaScript

Tutorial7 min readJuly 2026

You will build a working Quran search box that fetches matching ayahs from the UmmahAPI as a user types. No React, no build tools, just plain JavaScript in one HTML file.

What We Are Building

The final result is a search input. The user types a word or phrase. After a short pause, the page sends a request to the UmmahAPI Quran search endpoint and shows the matching ayahs below the input. Each result shows the Arabic text, the translation, and the surah and ayah reference.

Here is the shape of a response from the search endpoint so you know what to expect:

// GET https://ummahapi.com/api/quran/search?q=mercy
// Example response shape:
{
  "query": "mercy",
  "total": 42,
  "results": [
    {
      "surah": 1,
      "ayah": 3,
      "arabic": "ٱلرَّحْمَٰنِ ٱلرَّحِيمِ",
      "translation": "The Most Gracious, the Most Merciful",
      "surah_name": "Al-Fatihah"
    }
  ]
}

Every result has everything you need to display a clean card: the surah name, the ayah reference, the Arabic text, and the translation.

The HTML Skeleton

Start with a single HTML file. No dependencies, no package.json. The structure is simple: a heading, a search input, a status line, and a container for results.

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Quran Search</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link href="https://fonts.googleapis.com/css2?family=Amiri:wght@400;700&family=Inter:wght@400;600&display=swap" rel="stylesheet">
  <style>
    body { font-family: 'Inter', sans-serif; max-width: 680px; margin: 2rem auto; padding: 0 1rem; }
    #search { width: 100%; padding: .75rem 1rem; font-size: 1.1rem; border: 2px solid #ddd; border-radius: 8px; outline: none; }
    #search:focus { border-color: #1a7a4a; }
    #status { font-size: .85rem; color: #666; margin: .5rem 0; min-height: 1.2em; }
    .card { border: 1px solid #e5e7eb; border-radius: 10px; padding: 1.1rem 1.25rem; margin-bottom: .75rem; }
    .arabic { font-family: 'Amiri', serif; font-size: 1.5rem; text-align: right; direction: rtl; margin-bottom: .5rem; color: #1a1a2e; }
    .translation { color: #2d3347; margin-bottom: .5rem; }
    .ref { font-size: .8rem; font-weight: 600; color: #1a7a4a; }
  </style>
</head>
<body>
  <h1>Quran Search</h1>
  <input id="search" type="search" placeholder="Search ayahs (e.g. patience, mercy)...">
  <p id="status"></p>
  <div id="results"></div>
  <script src="search.js"></script>
</body>
</html>

The Debounce Function

Without debouncing, the app fires a request on every single keypress. If someone types "patience", that is 8 requests. Debouncing waits until the user stops typing for a moment before sending anything.

Here is a simple debounce you can copy. It takes a function and a delay in milliseconds and returns a new function that only runs after the delay has passed.

// search.js

function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

A 400 millisecond delay works well. It feels instant to the user but cuts the number of requests significantly.

Fetching Search Results

Now write the function that actually calls the API. It takes the query string, builds the URL, and returns the results array. The search endpoint is GET /api/quran/search?q=keyword.

const BASE = "https://ummahapi.com";

async function searchQuran(query) {
  const url = `${BASE}/api/quran/search?q=${encodeURIComponent(query)}`;
  const res = await fetch(url);

  if (!res.ok) {
    throw new Error(`API error: ${res.status}`);
  }

  const data = await res.json();
  return data.results ?? [];
}

Notice encodeURIComponent on the query. This handles spaces and special characters correctly. Without it, a query like "most merciful" would break the URL.

Tip. UmmahAPI allows 5,000 requests per 15 minutes without a key. For a production app with real traffic, register for a free key and add it as an Authorization header to get unlimited requests.

Rendering Results as Cards

Each item in the results array becomes a card. The card shows the Arabic text, the English translation, and the surah name plus ayah number as a reference line.

function renderResults(results, container, statusEl, query) {
  container.innerHTML = "";

  if (results.length === 0) {
    statusEl.textContent = `No results for "${query}".`;
    return;
  }

  statusEl.textContent = `${results.length} result${results.length !== 1 ? "s" : ""} for "${query}"`;

  results.forEach(item => {
    const card = document.createElement("div");
    card.className = "card";
    card.innerHTML = `
      <div class="arabic">${item.arabic}</div>
      <div class="translation">${item.translation}</div>
      <div class="ref">${item.surah_name} &bull; ${item.surah}:${item.ayah}</div>
    `;
    container.appendChild(card);
  });
}

Using innerHTML is fine here because the content comes from the API, not from the user. The search query itself is only shown in the status text via textContent, so there is no XSS risk.

Wiring It All Together

Now connect the input event to the debounced search function. Grab the DOM elements, set up the event listener, and call both functions on each valid input.

const searchInput = document.getElementById("search");
const resultsContainer = document.getElementById("results");
const statusEl = document.getElementById("status");

const handleSearch = debounce(async (query) => {
  if (query.trim().length < 3) {
    statusEl.textContent = "";
    resultsContainer.innerHTML = "";
    return;
  }

  statusEl.textContent = "Searching...";

  try {
    const results = await searchQuran(query.trim());
    renderResults(results, resultsContainer, statusEl, query.trim());
  } catch (err) {
    statusEl.textContent = "Something went wrong. Please try again.";
    console.error(err);
  }
}, 400);

searchInput.addEventListener("input", (e) => {
  handleSearch(e.target.value);
});

The 3-character minimum stops the API from being called for very short inputs like "a" or "al". You can adjust that threshold to suit your use case.

Adding an Audio Playback Button

You can extend each result card with a play button. UmmahAPI supports audio via the ?reciter= query parameter on individual ayah endpoints. The audio URL follows a predictable pattern you can build client-side.

// Reciters available: alafasy, abdul_basit, sudais, husary, ajamy, shaatri, muaiqly, dosari

function createAudioButton(surah, ayah) {
  const audioUrl = `https://ummahapi.com/api/quran/surah/${surah}/ayah/${ayah}?reciter=alafasy`;
  const btn = document.createElement("button");
  btn.textContent = "▶ Play";
  btn.style.cssText = "margin-top:.5rem;padding:.3rem .8rem;border:1px solid #1a7a4a;color:#1a7a4a;background:none;border-radius:5px;cursor:pointer;font-size:.82rem;";

  let audio = null;

  btn.addEventListener("click", async () => {
    if (audio && !audio.paused) {
      audio.pause();
      btn.textContent = "▶ Play";
      return;
    }

    const res = await fetch(audioUrl);
    const data = await res.json();

    if (data.audio) {
      audio = new Audio(data.audio);
      audio.play();
      btn.textContent = "⏸ Pause";
      audio.addEventListener("ended", () => { btn.textContent = "▶ Play"; });
    }
  });

  return btn;
}

Add the button to your renderResults function by calling card.appendChild(createAudioButton(item.surah, item.ayah)) after setting innerHTML. The button fetches the audio URL lazily, only when clicked, so there is no wasted bandwidth on results the user never taps.

Tip. You can swap alafasy for any other reciter in the list: sudais, husary, abdul_basit, and more. Add a <select> dropdown and pass the selected value into the URL to let users choose their preferred reciter.

Handling the API Key Header

When you are ready to move to production, add your API key as a header on every request. The key removes the rate limit entirely. Register at ummahapi.com/register to get one for free.

const API_KEY = "your_api_key_here"; // keep this in an env variable for real apps

async function searchQuran(query) {
  const url = `https://ummahapi.com/api/quran/search?q=${encodeURIComponent(query)}`;

  const res = await fetch(url, {
    headers: {
      "Authorization": `Bearer ${API_KEY}`
    }
  });

  if (!res.ok) throw new Error(`${res.status}`);
  const data = await res.json();
  return data.results ?? [];
}

If you are building a server-side app with Node.js, keep the API key in a .env file and read it with process.env.UMMAHAPI_KEY. Never put keys directly in client-side JavaScript that ships to the browser.

Quick Ideas to Extend This

Once the basic search is working, there are a few natural next steps:

The full API reference for all these endpoints is in the UmmahAPI docs.

FAQ

How do I search the Quran with JavaScript?

Call the UmmahAPI endpoint https://ummahapi.com/api/quran/search?q=your+keyword using the Fetch API. It returns an array of matching ayahs with Arabic text, translation, surah name, and ayah number. Wrap the call in a debounce function so it does not fire on every keypress.

Does the UmmahAPI Quran search endpoint support Arabic queries?

The endpoint searches across translations by default. You can pass Arabic Unicode characters as the query value. Use encodeURIComponent in JavaScript to make sure the characters are URL-safe before sending the request.

Do I need an API key to use UmmahAPI for Quran search?

No. The API works without a key up to 5,000 requests per 15 minutes. For apps with more traffic, you can register for a free key and add it as an Authorization: Bearer header to remove the rate limit.

Can I debounce a Quran search input in JavaScript?

Yes. Write a debounce wrapper that uses setTimeout and clearTimeout. Pass 400 milliseconds as the delay. Connect the debounced function to the input event on your search field. This gives a smooth feel without hammering the API.

Start building with UmmahAPI today

Free access to Quran, Hadith, Prayer Times, Duas, Qibla, and more, all in one API.

Read the docs

Related

API ReferenceQuran API TutorialBuild a Quran App with React TutorialFree Quran API Guide Embeddable WidgetVerse of the Day Widget