Problem
The core failure mode is literal search. LIKE, full-text on the same letters, and “find this substring” assume the reader already knows the wording. Most of the time they do not. They remember a meaning, a paraphrase, or a word in another language.
- Literal search: match the characters the user typed.
- Meaning search: match the idea, even when the spelling and language differ.
So 2:255 must still open Ayat al-Kursi (exact path). On the vector path, imperfect paraphrases should still land nearby - for example Arabic راحت القلب (spelled that way on purpose, not a slip on this page: readers type rough phrases, and meaning search is supposed to understand the intent), and English sense of relief. Those strings do not need to appear in the ayah line. That gap is why OpenSearch embeddings exist in this product - not as decoration on a keyword box.
Chat that invents new sentences is out of scope. The job is to retrieve passages that already exist.
What it is
Tlawati (تلاوتي) is a web reader for the Noble Quran: full text, recitation, tafsir, translation, topics, and juz. The interesting engineering layer is meaning search on top of a wide content catalog.
MySQL remains the store for exact keys, filters, and the text the UI shows. OpenSearch holds the meaning index. Laravel merges the two paths in one search box.
Translations, tafsirs, languages, and reciters
The reader is built around choice, not one locked edition:
- Translations. Many translation editions across languages. The reader picks a book; the API serves that edition verse by verse. Defaults lean practical (for example Sahih International in English), but the catalog is wider.
- Tafsirs. Multiple Arabic tafsir works (and related study editions). Switching tafsir changes the study panel for the same ayah - same verse key, different book.
- Interface languages. The UI ships in dozens of locales (RTL and LTR). Interface language is separate from which translation or tafsir book is open.
- Reciters. A catalog of Hafs murattal reciters with per-ayah stream URLs. Listen by surah or from a verse; repeat verse / surah / next surah. Defaults include a familiar voice (al-Afasy), and the list is not a single-file playlist.
That breadth is why the vector index embeds translations and tafsir windows, not only the Arabic ayah line: a query in another language can still land near the right verse.
Meaning search
- Embeddings. Offline, ayahs, translations, and tafsir windows run through local
BAAI/bge-m3(1024 dimensions). Long tafsir is chunked (600-character windows, 80 overlap, capped per passage) before embed. - Engine. OpenSearch stores both keyword fields and vectors. Nearest neighbors use an HNSW graph (approximate k-NN). Cosine closeness is the distance.
- Hybrid ranking. Every meaning query runs BM25 and vector k-NN, then merges the lists with reciprocal rank fusion (RRF). Exact rare words stay in the race; paraphrases still surface.
- Online path. Embed the query with the same model → ask OpenSearch for neighbors → fuse with BM25 → Laravel loads display text from MySQL by id.
- Exact path stays local. Verse keys, surah numbers, and surah names resolve in MySQL. If the search VM is down, those hits still return. There is no corpus-wide
LIKEfallback for meaning.
The fingerprint retrieves. It does not compose an answer.
How it's built
web- Next.js reader and SEO pages.server- Laravel API. Calls the search service over HTTPS with a bearer token. Serves content and user data from MySQL.search- Dockerized OpenSearch (bound to localhost on the VM) plus a small HTTP service that embeds queries and runs hybrid search. Only that API is public; port 9200 is not.- Index build - Operator machine embeds offline, bulk-loads through an SSH tunnel, then moves the
content_searchalias only after counts match. A failed load does not replace the live index.
The whole stack runs on AWS: the Next.js site, Laravel API, and MySQL for what readers see, plus a search VM (16 GB so the OS can cache the full translation and tafsir index) for OpenSearch and query embeddings.
Technologies
| Layer | Choice |
|---|---|
| UI | Next.js, TypeScript |
| API | Laravel, PHP |
| Canonical text / exact lookup | MySQL 8 |
| Vector + keyword engine | OpenSearch (BM25 + k-NN / HNSW) |
| Embedding model | BAAI/bge-m3 (local, 1024-d) |
| Rank fusion | Reciprocal rank fusion |
| Hosting | AWS (site, API, MySQL, search VM) |
| Search runtime | Docker Compose, TLS + token; OpenSearch on localhost of the search VM |
No third-party embedding API. No public OpenSearch port. Queries are not stored; failure logs keep codes, not the search string.
Result
The site is live at tlawati.com. You can switch translation, tafsir, UI language, and reciter without leaving the ayah. Exact navigation still feels like a database. Meaning search feels like a map of ideas - OpenSearch, local embeddings, and hybrid ranking over that same multilingual corpus.
