Developing a Custom Search UI with the Search API

In this tutorial, you’ll make your first API calls to power a minimal custom search UI. The goal is to build a working MVP you can extend.

Prerequisites

Before you start, you’ll need:

  • A SearchStax Site Search account
  • Search App with data already ingested
  • Your Search & Indexing endpoints (/emselect and /emsuggest)
  • A Read‑Only token or basic auth credentials for client calls
  • Permission to modify your app or site code

Warning: Don’t embed Read & write credentials in client code.

1. Collect Your API Configuration

  1. In Site Search, open your Search App.
  2. Go to App settings > All APIs and copy:
    • Select Endpoint (/emselect) endpoint
    • Auto‑Suggest (/emsuggest) endpoint
    • Analytics Tracking Key (optional, but recommended)
  3. If your app is multi‑language, note the active language code.

Open the copied Select Endpoint (/emselect) URL in a browser without parameters. Confirm you see an authentication or parameter error from the Search API, which verifies the endpoint is reachable.

2. Run Your First Search Request

The Search API returns JSON for a query. Use q for the search term.

Example request with a read‑only token:

curl -H "Authorization: Token <YOUR_READONLY_TOKEN>" \
  "https://<cluster>/<app>/emselect?q=telehealth"

Expect a JSON response containing matching documents from your index.

Frequently asked questions:

  • Which parameters should I start with? Begin with q and a small page size. Add paging and filters later.
  • Where do field names come from? Field names map to the fields you indexed. See your Search fields and Results fields configuration.

3. Render a Minimal Results List

The following HTML + JavaScript fetches results and renders a title list. Replace placeholders with your values.

Load the page and run a query you know should match. Confirm a list of results appears.

Tip: If your API requires basic auth, route requests through a lightweight server proxy. Don't expose secrets in client code.

<div id="search">
  <label for="q">Search</label>
  <input id="q" placeholder="Search" autocomplete="off" />
  <button id="go">Search</button>
  <ul id="suggestions" role="listbox"></ul>
  <ul id="results"></ul>
</div>

<script>
document.addEventListener('DOMContentLoaded', () => {
  const config = {
    apiKey: '<YOUR_READONLY_TOKEN>',                 // client-safe, read-only
    searchURL: 'https://<cluster>/<app>/emselect',   // from Dashboard → All APIs
    suggestURL: 'https://<cluster>/<app>/emsuggest'  // from Dashboard → All APIs
  };

  const headers = { Authorization: `Token ${config.apiKey}`, Accept: 'application/json' };

  const input = document.getElementById('q');
  const go = document.getElementById('go');
  const results = document.getElementById('results');
  const suggestions = document.getElementById('suggestions');

  go.addEventListener('click', runSearch);
  input.addEventListener('keydown', e => { if (e.key === 'Enter') runSearch(); });

  async function runSearch() {
    const q = input.value.trim();
    if (!q) return;
    const u = new URL(config.searchURL);
    u.searchParams.set('q', q);

    const r = await fetch(u.toString(), { headers });
    if (!r.ok) { console.error('Search HTTP', r.status); return; }
    const data = await r.json();

    const docs = data.response?.docs ?? [];
    results.innerHTML = '';
    for (const doc of docs) {
      const li = document.createElement('li');
      const title = pickFirst(doc.title) || pickFirst(doc.name) || doc.url || '(untitled)';
      li.textContent = title;
      results.appendChild(li);
    }
  }

  function pickFirst(v) { return Array.isArray(v) ? v[0] : v; }
});
</script>

4. Add Typeahead with the Auto‑Suggest API (Optional)

Add these lines to the script from Step 3 (inside the same DOMContentLoaded callback). They use your existing config, headers, input, go, and suggestions variables.

Note: For Auto-Suggest (/emsuggest), send your Read-Only token in the Authorization header. If the response is empty, either the suggest dictionary isn't populated yet or your handler expects the parameter suggest.q instead of suggest.

<script>
/* --- Add below your search code, inside the same DOMContentLoaded callback --- */

// 1) Debounce and listen for input
let timer;
input.addEventListener('input', () => {
  clearTimeout(timer);
  const term = input.value.trim();
  suggestions.innerHTML = '';
  if (!term) return;
  timer = setTimeout(fetchSuggest, 120);
});

// 2) Fetch suggestions and render
async function fetchSuggest() {
  const term = input.value.trim();
  if (!term) return;

  const u = new URL(config.suggestURL);
  u.searchParams.set('q', term); // if your handler uses Solr-style, use 'suggest.q'

  const r = await fetch(u.toString(), { headers });
  if (!r.ok) { console.error('Suggest HTTP', r.status); return; }
  const s = await r.json();

  // parse suggestions from different response formats
  const dict = s.suggest ?? {};
  const firstDict = Object.values(dict)[0] ?? {};
  const entry = firstDict[term] || Object.values(firstDict)[0] || {};
  const raw = Array.isArray(s.suggestions) ? s.suggestions : (entry.suggestions || []);
  const items = raw.map(v => typeof v === 'string' ? v : (v.term || v.payload)).filter(Boolean);

  items.forEach(text => {
    const li = document.createElement('li');
    li.textContent = text;
    li.role = 'option';
    li.tabIndex = 0;
    li.onclick = () => { input.value = text; go.click(); };
    li.onkeydown = e => { if (e.key === 'Enter') li.onclick(); };
    suggestions.appendChild(li);
  });
}
</script>

Development Best Practices

  • Use Read‑Only credentials for client apps. Move secret keys server‑side.
  • Keep tokens out of source control. Load via environment variables.
  • Validate accessibility. Test keyboard support and labels after styling.
  • Plan for paging and filters. Add rows, start, and filter params as needed.
  • Measure with analytics. Include your Analytics Tracking Key and validate events in Analytics > Dashboard.

What’s Next

You now have an MVP custom UI calling core APIs. Next, review the pre-launch checklist to make sure your configuration, indexing, and analytics tracking are ready for production.

Articles in this section