Search is everywhere, whether you’re looking for a product on Amazon, a tweet on X, or a log entry in your system. But not all searches are created equal. A simple SQL LIKE '%term%' query just doesn’t cut it when your users expect lightning-fast, typo-tolerant, and contextually smart results.
That's where full-text search engines like OpenSearch, Elasticsearch and Meilisearch come in.
In this article, we’ll explore why full-text search matters, how it works behind the scenes, and walk through a hands-on Meilisearch example you can run locally using Docker, Postman, or any other tool capable of making HTTP requests such as cURL.
What is Full-Text Search?
Full-text search allows users to find relevant results based on textual content and not exact matches.
Instead of scanning through every record in a database, search engines analyze and index text data to make it searchable in milliseconds. They understand context to an extent, handle typos, and rank results by relevance, giving users what they mean, not just what they type.
How Full-Text Search Works
Here’s a high-level view of what happens under the hood.
- Documents are ingested into the search engine.
- They’re tokenized,that is split into searchable terms.
- The engine builds an inverted index, mapping words to documents that contain them.
- When a user searches, their query is analyzed, scored, and ranked by relevance.
Why Full-Text Search Engines Matter
Search engines exist because users expect relevance and speed. The following are some of the reasons why they are essential:
- Speed at Scale - Uses inverted indexes to handle millions of document quickly.
- Relevance Scoring - Ranks results by context, frequency, and meaning.
- Fuzzy Matching - Finds “interstllar” when you meant “Interstellar.”
- Filtering & Sorting - Combine search with advanced filters (price, tags, etc).
- Scalability - From single-node lightweight setups to multi-cluster systems.
Choosing the Right Search Engine
There is no one-size-fits all in full-text search, different engines shine in different contexts. Here are some of the most popular ones and why teams choose them:
| Engine | Best For | Why It’s a Good Choice | 
|---|---|---|
| Elasticsearch | Enterprise-scale search & analytics | Proven, battle-tested, and highly extensible with a rich query DSL. Great for logs, metrics, and large datasets. | 
| OpenSearch | Self-hosted analytics and observability | AWS-backed open-source fork of Elasticsearch. Ideal for dashboards, metrics, and log management with built-in visualization tools. | 
| Meilisearch | Developer-friendly instant search | Lightweight, fast, and typo-tolerant by default. Excellent for product catalogs, SaaS apps, and small-to-medium projects. | 
| Typesense | Product and document search | Easy to deploy with automatic typo tolerance and fine-tuned relevance. Perfect for modern web apps and e-commerce sites. | 
| Apache Solr | Legacy or enterprise systems | Java-based, Lucene-powered, and integrates well with Hadoop and big data pipelines. Solid choice for on-premise deployments. | 
| Algolia | Managed search-as-a-service | Fully hosted, fast, and feature-rich with analytics and instant results. Ideal for teams that prefer not to manage infrastructure. | 
| MongoDB Atlas Search | Search within NoSQL documents | Built on Lucene, enables search directly inside MongoDB Atlas. Great for apps already using MongoDB. | 
| PostgreSQL Full-Text Search | Embedded search in relational apps | Built-in text search ( to_tsvector,to_tsquery) with stemming and ranking. Suitable for smaller-scale or internal systems. | 
When Databases Can Still Handle Search
Sometimes you don’t need a dedicated search engine, your database might already include built-in full-text search capabilities.
Here’s how popular databases stack up for smaller-scale or embedded search needs:
| Database | Search Feature | Description | 
|---|---|---|
| PostgreSQL | to_tsvector,to_tsquery | Native full-text search with stemming, ranking, and weighting. Great for moderate datasets or apps needing SQL + search in one place. | 
| MySQL / MariaDB | MATCH() AGAINST() | Simple full-text search in MyISAMandInnoDBtables. Works for blogs, products, or smaller data sets but lacks fuzzy matching and relevance tuning. | 
| SQLite | FTS5 Extension | Lightweight full-text indexing for mobile or desktop apps. Perfect for embedded search without a separate service. | 
| MongoDB Atlas Search | Lucene-powered search layer | Provides fuzzy matching, scoring, and highlighting directly within MongoDB Atlas. Useful for document-based applications. | 
| Oracle Database | Oracle Text | Enterprise-grade search supporting structured and unstructured data, but complex to configure and manage. | 
| Microsoft SQL Server | Full-Text Search Service | Offers keyword and phrase search within text columns, suitable for enterprise systems already using SQL Server. | 
My advice is use your database's built-in search when your dataset is small, self-contained, or doesn't need typo tolerance. Use a dedicated search engine once you need fast relevance scoring, fuzzy matching, and scaling beyond a few hundred thousand records.
Example: Building Search with Meilisearch
Let's set up Meilisearch locally using Docker Compose. We should also be able to index some data, and explore search queries. I've made the assumption that you have docker locally and understand how to use docker.
Step 1: Create docker-compose.yml
version: '3.8'
services:
  meilisearch:
    image: getmeili/meilisearch:v1.24
    container_name: meilisearch
    environment:
      - MEILI_MASTER_KEY=a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w
    ports:
      - "7700:7700"
    volumes:
      - ./data.ms:/meili_data
Run it:
docker-compose up -d
You may skip step 2, and just go to this section where I've included a postman collection file that you can download and import as a collection to run the sample tests included in this article.
Step 2: Add Sample Data
Create a movies.json file
[
  { "id": 1, "title": "Inception", "genre": "Sci-Fi", "year": 2010 },
  { "id": 2, "title": "Interstellar", "genre": "Sci-Fi", "year": 2014 },
  { "id": 3, "title": "The Dark Knight", "genre": "Action", "year": 2008 },
  { "id": 4, "title": "Tenet", "genre": "Thriller", "year": 2020 }
]
Make the following curl request to index the data:
curl -X POST 'http://localhost:7700/indexes/movies/documents' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data-binary @movies.json
Response:
{"taskUid":0,"indexUid":"movies","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2025-10-31T08:09:40.644831635Z"}
In order for us to be able to filter results in Meilisearch, we also have to update the index settings with the attribute we need to filter by.
curl -X PUT 'http://localhost:7700/indexes/movies/settings/filterable-attributes' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data-binary '["genre", "year"]'
Response:
{"taskUid":1,"indexUid":"movies","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2025-10-31T09:11:12.86560951Z"}
We also need to configure attributes that are sortable;
curl -X PUT 'http://localhost:7700/indexes/movies/settings/sortable-attributes' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data-binary '["year"]'
Response:
{"taskUid":2,"indexUid":"movies","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2025-10-31T09:19:18.009525346Z"}
You may verify all configured attributes by checking the index settings:
curl -X GET 'http://localhost:7700/indexes/movies/settings' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w'
Response:
{"displayedAttributes":["*"],"searchableAttributes":["*"],"filterableAttributes":["genre","year"],"sortableAttributes":["year"],"rankingRules":["words","typo","proximity","attribute","sort","exactness"],"stopWords":[],"nonSeparatorTokens":[],"separatorTokens":[],"dictionary":[],"synonyms":{},"distinctAttribute":null,"proximityPrecision":"byWord","typoTolerance":{"enabled":true,"minWordSizeForTypos":{"oneTypo":5,"twoTypos":9},"disableOnWords":[],"disableOnAttributes":[],"disableOnNumbers":false},"faceting":{"maxValuesPerFacet":100,"sortFacetValuesBy":{"*":"alpha"}},"pagination":{"maxTotalHits":1000},"embedders":{},"searchCutoffMs":null,"localizedAttributes":null,"facetSearch":true,"prefixSearch":"indexingTime"}
Step 3: Try Different Searches
Simple Search
curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "inception" }'
Response:
{"hits":[{"id":1,"title":"Inception","genre":"Sci-Fi","year":2010}],"query":"inception","processingTimeMs":11,"limit":20,"offset":0,"estimatedTotalHits":1,"requestUid":"019a3994-1965-7833-94a8-03fd4553bfeb"}
Fuzzy Search
curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "interstllar" }'
Response:
{"hits":[{"id":2,"title":"Interstellar","genre":"Sci-Fi","year":2014}],"query":"interstllar","processingTimeMs":5,"limit":20,"offset":0,"estimatedTotalHits":1,"requestUid":"019a3994-a647-7993-b8b2-d8bd4882d031"}
Filter by year
curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "Sci-Fi", "filter": "year > 2010" }'
Response:
{"hits":[{"id":2,"title":"Interstellar","genre":"Sci-Fi","year":2014}],"query":"Sci-Fi","processingTimeMs":12,"limit":20,"offset":0,"estimatedTotalHits":1,"requestUid":"019a398c-7ad4-7522-be38-db9cb7152704"}
Sort by Year (Descending)
curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "sci-fi", "sort": ["year:desc"] }'
Response:
{"hits":[{"id":2,"title":"Interstellar","genre":"Sci-Fi","year":2014},{"id":1,"title":"Inception","genre":"Sci-Fi","year":2010}],"query":"sci-fi","processingTimeMs":25,"limit":20,"offset":0,"estimatedTotalHits":2,"requestUid":"019a3996-f4a3-7930-8109-041465231893"}
Download Postman collection
Access Repository on Github
You can find the complete Meilisearch demo, including the Docker setup, sample data and Postman collection, on GitHub.
Final Thoughts
Search quietly shapes how people experience your product. When it works well, it feels natural, fast, and almost invisible. Full-text search makes your app return results that are relevant to what users mean, not just what they type. Whether you use Meilisearch, OpenSearch, or PostgreSQL’s text search, choosing the right tool can turn a simple lookup into an experience that feels truly effortless.
Cover Image: by Teslariu Mihai
 
 
              

 
    
Top comments (0)