SQLite FTS5 vs Meilisearch for Tiny Apps: When a Search Engine Is Overkill
Nico Bram
August 25, 2026
Meilisearch is a lovely engine. It is also a second process, a second volume, a second backup, and a second way for a tiny app to be down while the database is fine. I added it to a notes product with 4,000 documents because a blog said instant search was table stakes. Users typed three letters. SQLite FTS5 would have been rude and sufficient. The engine was overkill. The overkill had a compose file and a healthcheck I forgot to make useful.
This is not a claim Meilisearch is bad. I have praised it against Elasticsearch. It is a claim that a search engine is a product you operate. FTS5 is a virtual table you already back up when you back up the SQLite file. For a lot of indie apps, that is the whole decision.
What tiny means here
Tiny is tens of thousands of documents, not tens of millions. Tiny is one region, one box, one operator. Tiny is a search box on a docs site, a customer list, a personal knowledge base, a catalog that is still a spreadsheet with feelings. If you need faceted navigation that feels like a storefront and typo-tolerant prefixes that feel like Algolia, you may have left tiny. Leave on purpose, not because a template included Meilisearch.
I measure tiny by restore time. If I can restore the SQLite file in a minute and I cannot remember the last Meilisearch dump drill, the engine is the fragile one. Search that is a projection of the database should be rebuildable. FTS5 rebuilds with the file. Meilisearch rebuilds if you wrote the reindex job. I have seen production apps that could not reindex because the only copy of truth was the engine. That is a trap with a pretty UI.

FTS5’s honest limits
FTS5 is not typo-first. If the user types “meilisearch” as “meilisarch,” you will not look clever unless you add your own tricks. Prefix queries are possible. Ranking is MATCH plus whatever you layer in SQL. Snippets exist. It is a librarian, not a concierge.
Tokenization and stemming depend on how you build the table. Unicode is a project. I have shipped English-only FTS5 and been fine. I have shipped a mixed-language corpus and been less fine. If your users live in morphology you do not understand, a dedicated engine may be cheaper than you pretending to be a linguist in SQL.
Concurrency is SQLite’s story: writers block, WAL helps, a huge write plus a search on a tiny VPS can hitch. Meilisearch moves the hitch to RAM and a task queue. Different hitch. For a read-heavy tiny app, FTS5 on WAL is boring in a good way.
Meilisearch’s honest costs
RAM, the task queue, settings you forget to apply, a search-only key in a browser, a version pin, a dashboard you will screenshot into Slack. Instant-search widgets. Typos that make you look like a funded company. Those are real product wins. They are also how a weekend app grows an ops personality.
I still choose Meilisearch when the search box is the product: a public docs site that must forgive spelling, a storefront where “almost” should still sell, a multi-index toy that I will actually operate. I do not choose it for an admin table with 800 rows. LIKE would be ugly. FTS5 is enough.

Sync and the double-write bug
Two stores means two writes. You update SQLite, you forget to update Meilisearch, search lies. You update Meilisearch first, the database rolls back, search lies the other way. A queue can fix this. A queue is another product. FTS5 lives in the same transaction as the row. That property is worth more than a typo on a 2,000-row corpus.
Reindex-from-source is the adult pattern if you do use an engine. The engine is a cache. SQLite is the cache if you only have SQLite. I prefer one story until a user complaint is specifically “it does not forgive my spelling” and not “search is down again.”
Query shapes that flip the choice
Filters plus full text plus sort-by-price is where FTS5 gets awkward and Meilisearch gets comfortable. A docs site with a single box is FTS5-shaped. A catalog with facets is engine-shaped. Be honest about the UI you will actually ship, not the UI on a Dribbble shot.
Highlighting and instant-as-you-type on every keystroke will push you toward an engine or toward a debounce plus FTS5. I debounce FTS5. Users do not notice 150 ms on a tiny corpus. They notice a container OOM.
When I migrate toward an engine
Complaints about typos from people who pay. A corpus that made FTS5 queries ugly. A need to search from a static frontend with a public key I am willing to treat as public. A second language I cannot tokenize well. Then I add Meilisearch, I write the reindex, I do the dump restore once, and I accept the second healthcheck.
I do not migrate because a conference talk used the word relevance. Relevance on 500 docs is “title first, then body.” You can write that in SQL.
When I migrate back
I have deleted Meilisearch from an app that never left private beta. The compose file was longer than the feature. FTS5 plus a rebuild on deploy was enough. The users were me and one friend. We can spell.
If your only search users are admins, give them a SQL-backed box and a “reindex” button that is a no-op because FTS5 is already the table. Admins will forgive a missing typo. They will not forgive a 502 on the engine while the app is up.
A practical default
Start with FTS5 if the documents already live in SQLite. Add a content-sync trigger or rebuild on write. Log zero-result queries in a table. After a hundred logged misses, read them. If they are typos, consider an engine or a simple synonym table. If they are missing documents, fix the corpus. Most “search is bad” bugs are corpus bugs.
Do not start with Elasticsearch. Do not start with three engines. I have written about Typesense versus Meilisearch for people who already need an engine. This piece is the step before that fight. Skip the fight if you can. A single well-placed FTS5 table plus a weekly read of zero-result logs will carry a surprising number of products through their first paying year. The year you need an engine, you will know because users will say so in words that are not “it should feel like Algolia.” They will say they cannot find a SKU they spelled wrong. That is a ticket. Until the ticket exists, you are staffing a container for a compliment.
Hosting shapes that make the engine look free
A PaaS that already includes Meilisearch in a template is how people “start simple” with two bills. The template is not simple. It is a vendor’s upsell wearing YAML. A single SQLite file on a $5 box is simple. Turso and LiteFS change the story if you already split SQLite. Even then I would search in the primary and not add a third logo until I must.
Serverless functions plus FTS5 can be awkward if every instance wants the file. That architecture may force an external engine earlier. Be honest: you did not outgrow FTS5, you outgrew a single file. Different problem. Solve placement first. I have seen teams add Meilisearch because they used a dozen lambdas and then blamed search for the complexity they chose.
Mobile offline-first apps are FTS5’s home turf. The engine is on the device. Meilisearch in a pocket is a joke unless you are very dedicated. If the same product also has a server search, I still keep server FTS5 until the corpus and the UX demand more. Two different search qualities on mobile versus web is a product bug you should name, not a reason to run an engine on a phone.
Tests are cheaper with FTS5. A temp SQLite in CI is a file. A Meilisearch in CI is a container and a flake. I have waited on both. I prefer the file when the product is tiny. Flakes teach the team to skip the search tests. Skipped tests are how relevance rots.
The choice I would make again
For a new tiny app I would put FTS5 in the same migration as the documents table. I would not open a Meilisearch port on day one. I would add the engine when search is a product surface, not a checkbox. A search engine is overkill when the database can already answer, when you have not practiced a dump restore, and when a typo is rarer than an ops night. Overkill feels like engineering. It is often just a second way to be down. If you already run Meilisearch and the app is still tiny, you are allowed to delete it. The permission is the hard part. The migration is a weekend. The quieter pager is the prize.