Skip to content

Example: autocomplete

An autocomplete panel under the search input, built on GET /suggest. If you do not need a custom look or behaviour, the widget provides the same functionality without any code.

Calling from the browser

js
const ENDPOINT = 'https://search.api.metty.eu/suggest'

async function suggest(query, signal) {
    const params = new URLSearchParams({ key: PUBLIC_API_KEY, q: query, limit: '8' })
    const response = await fetch(`${ENDPOINT}?${params}`, { signal })

    if (!response.ok) {
        throw new Error(`suggest failed: ${response.status}`)
    }

    return response.json()
}

The response holds at most 8 query suggestions and at most 5 products in a compact form — without descriptions and without highlighting.

Debouncing and cancelling the previous request

Without debouncing the input fires a request on every keystroke and responses can arrive out of order. AbortController solves both:

js
let controller = null
let timer = null

input.addEventListener('input', () => {
    const query = input.value.trim()

    clearTimeout(timer)
    controller?.abort()

    if (query.length < 2) {
        render(null)
        return
    }

    timer = setTimeout(async () => {
        controller = new AbortController()

        try {
            render(await suggest(query, controller.signal))
        } catch (error) {
            if (error.name !== 'AbortError') {
                render(null)
            }
        }
    }, 150)
})

A cancelled request raises AbortError; it must not surface as an error, because it is the expected outcome of fast typing.

Rendering

js
function render(data) {
    if (!data || (data.suggestions.length === 0 && data.products.length === 0)) {
        panel.hidden = true
        return
    }

    panel.replaceChildren(
        ...data.suggestions.map(item => option(item.query, `${item.count} products`)),
        ...data.products.map(product => productRow(product))
    )

    panel.hidden = false
}

Suggestions lead to the result page for that query, products go straight to product.url. The price is in price, the catalog currency in currency.

Things to watch

topicrecommendation
minimum query length2 characters; do not send anything shorter
debounce120–200 ms
rate limit1800 requests per minute, counted per public key
empty responsehide the panel, do not show an error message
keyboardhandle arrows, Enter and Escape yourself — the panel is not a native <datalist>

The parameters and the response shape are documented under Autocomplete.

Metty documentation