React Frontend Integration
Series: Elasticsearch 101 | Article: 07
Overview
From the React side, Elasticsearch is just another data source behind an HTTP API. The frontend never talks to Elasticsearch directly β it calls the Go backend, which proxies and shapes the response. This keeps credentials off the client, allows response transformation, and lets the backend enforce business rules before returning results.
This article covers:
The search API contract between the Go backend and React frontend
A reusable
useSearchhookDebounced input handling
Rendering search results with highlighted matches
A faceted filter sidebar driven by aggregation data
Autocomplete with
search_after-style UX
Why You Should Not Hit Elasticsearch Directly from React
It is technically possible to call Elasticsearch from the browser using @elastic/elasticsearch (Node.js) or raw fetch calls. I do not do this and recommend against it:
Credentials exposure: Elasticsearch API keys in the browser are visible to anyone who opens DevTools.
Query leakage: Your entire query structure, field names, and data shape become public.
Missing business logic: Is this user allowed to see unpublished drafts? That check lives in Go, not in a browser.
CORS complexity: Configuring Elasticsearch's CORS headers for browser access adds maintenance overhead.
Keep the pattern: React β Go API β Elasticsearch.
The API Contract
The Go backend from Article 06 exposes:
Response shape (TypeScript interface):
Define these types in a shared src/types/search.ts file.
The useSearch Hook
useSearch HookEncapsulate all search state and logic in a single custom hook. The component layer stays clean.
Key behaviors:
AbortControllercancels in-flight requests when params change β prevents stale responses overwriting newer ones.The
useEffectdependency array uses.join(',')for the tags array to avoid reference instability.Errors are stored in state rather than thrown, keeping the component layer simple.
Debounced Search Input
Firing a search on every keystroke is wasteful. Debounce the query so the API call fires 300ms after the user stops typing:
Search Page Component
Search Input Component
Tag Facet Sidebar
Rendering Search Results with Highlights
When the Go backend returns highlighted excerpts, render them as HTML. Keep the highlight wrapping tags configurable so styled components can override the default <em>:
The sanitizeHighlight regex allows only <em> and </em>. Never render raw backend-supplied HTML without sanitization, even from your own API.
URL State Synchronization
For shareable search URLs, sync the query and filters to the URL:
This gives users shareable URLs like /search?q=elasticsearch&tag=backend&tag=golang.
Autocomplete / Typeahead
Add a GET /api/suggest?q=<prefix> endpoint on the Go side using a match_phrase_prefix query or completion field. On the React side, the pattern is:
Minimum 2 characters before triggering suggestions avoids very broad queries and unnecessary load.
Summary
Never call Elasticsearch directly from the browser β always proxy through the Go backend.
Encapsulate all search logic in a
useSearchhook; keep components presentational.Debounce the text input (300ms is a reasonable default).
Use
AbortControllerto cancel in-flight requests on param change.Sync search state to URL query parameters for shareable links.
Sanitize highlight HTML before setting
dangerouslySetInnerHTML.Drive the facet sidebar from aggregation data returned by the single search request.
Previous: Go Backend Integration | Next: Production Best Practices
Last updated