In-App Search
Search is one of those features that looks trivial until you build it. A box, a list, how hard can it be? Then a user types "expreso" instead of "espresso" and gets nothing, or your LIKE '%query%' query crawls once the table grows, or someone searches "running shoes" and the results come back in database insertion order instead of relevance order. Real search is a solved problem, and the people who solved it are search services. Appsanic wires up Algolia as a managed connector: connect it once in Dashboard → Connectors → Algolia, and every project in your account can use it.
Do you actually need a search service?
Most apps don't, at first. Be honest about which case you're in.
A database query is enough when:
- You're filtering a small list (a few hundred rows) by an exact field. "Show tasks where status is done."
- You're matching the start of a string. "Names that begin with what I typed."
- Results don't need to be ranked by relevance. Newest-first or alphabetical is fine.
- Typos don't matter because users pick from a known set.
Supabase (Postgres) handles all of that well, and Postgres even has decent built-in full-text search for medium-sized datasets. See Database & Data. Start there. Don't reach for a search service you don't need.
You want a real search service when:
- The dataset is large (tens of thousands of records and up) and
LIKEqueries have started to drag. - Users type freely and expect forgiveness for typos ("expreso" should still find espresso).
- You need results ranked by genuine relevance, with the best match first.
- You want results as the user types, updating on every keystroke in a few milliseconds.
- You need facets - those "filter by brand, price, colour" sidebars that update counts live.
That combination is exactly what Algolia is built for, and it's hard to recreate with a database alone.
The setup prompt
"Add instant search to the recipes screen. Search by title, ingredients, and tags. Be forgiving of typos, show results as I type, and let me filter by cuisine and difficulty. Use Algolia."
What the agent does:
- Reads your Algolia Application ID and search-only key from the connector and embeds them in the app (both are safe to bundle - more on that below).
- Builds the instant-search screen: a search box, a results list that updates on each keystroke, and the facet filters you asked for.
- Defines which fields are searchable (title, ingredients, tags) and which are facets (cuisine, difficulty).
- Wires up indexing so your records land in Algolia and stay in sync as they change.
You connect Algolia once and describe what you want searched. The agent does the rest.
Two keys, and why it matters
Algolia gives you two credentials, and the difference is the whole security story:
- The Application ID identifies your Algolia account. Not secret.
- The search-only key (also called the search API key) can run queries and nothing else. It cannot add, change, or delete records. It is publishable - designed to be embedded in client code - so the agent bundles it straight into your app, the same way map tokens and analytics keys are bundled. See Connectors Overview.
- The admin key can write and delete everything in your index. It is a secret. It must never be bundled into an app people download. The agent never embeds it, and you should never paste it into a prompt.
So how do records get into the index if the embedded key can't write? Indexing happens server-side, with the admin key held somewhere the public never sees. That split - read in the app, write on the server - is the heart of how this works safely.
Indexing your data
Algolia doesn't search your database directly. It keeps its own optimised copy of the records you want searchable, called an index. You push records to it, and Algolia builds the structures that make search instant.
A record is just a JSON object. For a recipes app, one record might be:
{
"objectID": "recipe_482",
"title": "Espresso Martini",
"ingredients": ["vodka", "coffee liqueur", "espresso"],
"cuisine": "cocktail",
"difficulty": "easy",
"rating": 4.7
}The objectID is the only required field - it's how Algolia recognises a record so that re-pushing it updates rather than duplicates. Use your database row's primary key and you get safe, repeatable updates for free.
The first push is a one-off backfill: send every existing row up to the index. After that, the index only needs to learn about changes.
Keeping the index in sync
This is the part people forget, and then wonder why a newly added recipe never appears in search. The index is a copy, so when a record changes in your database, the index has to be told. There are two clean patterns the agent can build, both server-side so the admin key stays put:
A Supabase database trigger plus an Edge Function (the tidy default). Add a trigger on the table so that any insert, update, or delete calls a small Supabase Edge Function. That function holds the admin key in its own environment and pushes the one changed record (or deletes it) to Algolia. With Supabase connected and Account access on, Appsanic can deploy that Edge Function for you - you paste the admin key into your Supabase secrets, following the instructions the agent generates. See Connectors Overview for how that relay deployment works.
"When a recipe is added, edited, or deleted in Supabase, sync that single record to Algolia automatically. Keep the Algolia admin key server-side in a Supabase Edge Function, never in the app."
A scheduled re-sync (simpler, slightly behind). A scheduled function runs every few minutes (or nightly) and pushes anything that changed since it last ran. Easier to reason about, but search lags reality by up to one interval. Good for catalogues that don't change by the second.
Whichever you choose, two habits save pain later:
- Send only the fields you search, facet, or display. A leaner record is a faster, cheaper index. Don't mirror your whole table.
- Never put anything sensitive in the index. The search-only key is in the app, so treat every indexed field as effectively public. No private notes, no internal flags, no other users' data.
Instant-search UI patterns
"Instant" search means results update as the user types, not after they press enter. A few patterns make it feel right:
- Debounce the keystrokes. Don't fire a query on every single letter the instant it lands - wait a beat (around 50 to 150 ms) after the user stops typing. Fewer queries, smoother feel, smaller bill.
- Show the empty state. Before anyone types, suggest recent searches or popular results rather than a blank screen.
- Handle no results gracefully. "Nothing for 'expreso martini'" with a suggestion beats a silent blank list. Typo tolerance means this happens less than you'd fear.
- Highlight the match. Algolia returns which part of each result matched the query, so you can bold the matching letters. It makes results feel responsive and accurate.
- Keep the list short and tappable. On a phone, the top handful of results is what matters. Paginate or lazy-load the rest as the user scrolls.
"Show search results as I type with a short debounce, highlight the matching text in each result, and show popular recipes before I've typed anything."
Ranking, typo tolerance, and facets
These three are why a search service earns its keep.
Ranking decides the order results come back in. Algolia ranks by how well a record matches first, then breaks ties using a field you choose - rating, popularity, recency, distance. Tell the agent what "best" means for your app:
"Rank search results by relevance first, then by rating, then by number of reviews."
Typo tolerance is on by default and is the single biggest reason to use Algolia over a raw database query. "expreso", "esspresso", and "expresso" all find espresso. You can tune how forgiving it is (stricter for short words, off for things like product codes where a typo means a genuinely different item).
Facets are the filterable attributes that power "filter by" UIs - cuisine, brand, price band, colour. Mark a field as a facet and Algolia can both filter on it and return live counts ("Italian (42), Thai (17)") that update as other filters change. That live count is hard to do well against a database and trivial with facets.
"Make cuisine and difficulty filterable facets. Show the count next to each option and update the counts as filters change."
Costs to keep an eye on
Algolia's free tier covers a real amount of usage, and pricing is roughly tied to two things: how many records are in your indices, and how many search requests you make. Two practical consequences:
- A debounce isn't just for feel - every keystroke without one is a billable search request.
- Indexing only the fields you need keeps record count and size down.
Check Algolia's current pricing page for the live numbers; their tiers change over time.
Under the hood (for engineers)
- The generated app talks to Algolia with the official client (
algoliasearch), configured with your Application ID and the search-only key, both read from the connector and inlined into the app code. There is no.envfile in an exported project - publishable keys live in the code, exactly as documented in Editing Exported Code. - The admin (indexing) key never touches the client bundle. Writes happen server-side: a Supabase Edge Function (or another small relay) holds the admin key in its own environment and is the only thing that pushes, updates, or deletes records. This mirrors the secret-key relay pattern in Connectors Overview - the only difference is that Algolia's read key is publishable, so reads run directly from the app.
- Sync is event-driven by default: a Postgres trigger on the source table invokes the Edge Function on insert, update, and delete, sending the single changed record (keyed by
objectID, set to the row's primary key for idempotent upserts). A scheduled full re-sync is the simpler alternative. - Search runs against Algolia's index, not your Postgres tables, so search load never touches your database. The two are kept consistent by the sync layer, not by querying live - which is why a missed sync, not a slow query, is the usual cause of "this record won't show up".
- Treat every indexed field as public. The search-only key is embedded and discoverable, so access control lives in what you choose to index, not in the key. Keep private data out of the index and enforce real privacy with Row Level Security in Postgres. See Database & Data.
Next
Read In-app ads for adding AdMob ad units and earning from a free app.
