You will not come across many projects that need a search feature but are dead-set on running Elasticsearch for it. Standing up a dedicated search server to find "black" across a few thousand titles feels like renting a forklift to move a shoebox. If your data already lives in SQLite, there is a better answer that ships with the database itself: FTS5.
What FTS5 actually is
FTS5 is a full-text search engine that ships as part of SQLite. It builds an inverted index, so instead of scanning every row with a LIKE '%term%' query, it keeps a mapping from each token to the rows that contain it. That is the same trick Google-style search engines use, just contained in a single file.
It is not a new add-on you have to hunt down. FTS5 showed up experimentally in SQLite 3.8.11 on July 27, 2015, and became part of the standard amalgamation with SQLite 3.9.0 on October 14, 2015. On most builds today it is available out of the box. If your database has a version number past 3.9, you already have it.
Search without a server
The whole feature is a virtual table. You declare which columns you want to index, nothing more:
CREATE VIRTUAL TABLE articles_fts USING fts5(title, body);
Like any table, you fill it with INSERT statements. Once it has data you search with the MATCH operator:
SELECT title FROM articles_fts
WHERE articles_fts MATCH 'search';
Case is ignored by default, which is usually what you want. A LIKE-based search has no way to rank results; FTS5 tracks how strong each match is and exposes that as a hidden rank column. The bm25() function implements Okapi BM25 relevance scoring with the standard constants k1=1.2 and b=0.75 — the same ranking family that powers relevance in search engines like Elasticsearch. Lower scores mean better matches, so you order ascending:
SELECT title, bm25(articles_fts) AS score
FROM articles_fts
WHERE articles_fts MATCH 'search'
ORDER BY score;
You can also weight columns so a hit in the title counts more than one in the body:
SELECT title FROM articles_fts
WHERE articles_fts MATCH 'search'
ORDER BY bm25(articles_fts, 10.0, 1.0);
That kind of weighting is free in FTS5 but awkward everywhere else. Snippet and highlight helpers make it equally easy to render results with the search terms marked up instead of returning raw rows.
Tokenizers do the real work
The index is only as good as how you split text into tokens. FTS5 ships with a handful of tokenizers and lets you pick per table. The default, unicode61, handles accents and works across languages. The porter tokenizer wraps it with English stemming, so a search for "run" also matches "running." One option to know about is the trigram tokenizer, which treats every three-character sequence as a token and opens the door to general substring matching — things like LIKE '%cdefg%' and typo-tolerant lookups that a normal word index misses entirely.
For a docs site, notes app, or internal tool that just needs people to find things, FTS5 covers a surprising amount of ground with zero infrastructure. No server, no extra dependency, no separate index to keep in sync. If the shape of your problem is "search my SQLite data," the tool has been sitting inside your database file the whole time.