How to add search to your app — from a simple filter to search by meaning

Search looks like a big feature. It seems you need something smart, "like Google." Good news: nine times out of ten, search is one query to the database that you'll finish in an evening.
The people who overcomplicate it are the ones who reach for "search by meaning" on day one, when a match by letters would have done. Let's go bottom-up — and you'll stop where it's enough for you.
First, answer one question
Before writing code, decide: are you searching by exact words or by meaning?
- "Find the note with the word report" — by words. That's simple.
- "Find notes about work, even if the word 'work' isn't in them" — by meaning. That's pricier and needed less often than it seems.
Start with search by words. You'll move to meaning only if you genuinely hit a wall. Most projects at level one or two are set forever.
Level 1 — filter a list (5 minutes)
If there's little data and it's already loaded on the page — don't go to the database at all. Filter right in memory.
const found = notes.filter(n =>
n.title.toLowerCase().includes(query.toLowerCase())
);
toLowerCase on both sides — so "Report" is found by "report." That's it. For a list of a few hundred rows it's instant and needs nothing new.
The ceiling of this level is hundreds of records. Beyond that, loading everything to the client gets heavy, and it's time for the database.
Level 2 — search in the database via LIKE
When there are thousands of records, filter where they live — in the database. In SQL it's one line:
select * from notes where title ilike '%report%';
ilike is case-insensitive search, and % on both sides means "anywhere in the string." In Supabase the same thing is done with .ilike('title', '%report%').
What you get: the database sifts through a million rows itself and returns the matches. No need to drag them to the client.
The trap: ilike '%word%' with a percent at the front can't be sped up by an index — on very large tables it starts to lag. That's the signal to move to level 3.
Level 3 — full-text search
When ilike got slow, or you want to search by several words and smartly (accounting for word forms), full-text search kicks in. It's right there in the database — no separate service needed.
In Postgres (which Supabase runs on) that's to_tsvector and to_tsquery. The idea: the database builds an index of words in advance, then searches it instantly.
select * from notes
where to_tsvector('english', title || ' ' || body)
@@ to_tsquery('english', 'report');
What you get: fast search over large text that understands "report," "reports," "reported" as one word. For most apps this is the final stop.
Level 4 — search by meaning
Needed only when the user searches with words that aren't in the text. "How to save money" should find a note about "cutting expenses." There's zero letter overlap — but by meaning it's one thing.
Here a vector database comes in: text is turned into a set of numbers close in meaning. How that works under the hood is in the piece on semantic search.
It's powerful but also pricier: it needs a model call on every text and every query. Don't start here. Get here only when exact search genuinely can't do the job.
What you end up with
An input field, and under it a list that narrows as you type. The only difference is where the filtering happens: in memory (level 1), in the database via ilike (level 2), through a full-text index (level 3), or by meaning (level 4). Start at the bottom and climb only when you hit a wall.
Tip: add a small delay before the query — 300 ms after the last letter — so you don't hit the database on every character. It's called debounce and saves a heap of requests.
Which level should a beginner start at?
Level one if the data is already on the page, otherwise level two — ilike in the database. That covers the vast majority of cases and takes one line. Add full-text and semantic search by actual pain, not in advance.
Do I need a separate search engine like Elasticsearch?
Almost certainly not. A separate search engine is an extra service you have to run and keep in sync with the database. Full-text search inside your own database (level 3) covers the needs of nearly any side project. Bring in a separate engine only when you have tens of millions of records and the built-in search clearly can't cope.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





