The Need for Full-Text Search


Standard database `LIKE` queries do not scale. They require full table scans, do not understand relevance ranking, and cannot handle typo tolerance, stemming, or faceted search. Dedicated search engines solve these problems with inverted indexes, relevance scoring, and specialized query parsing.


How Search Engines Work


An inverted index maps terms to the documents containing them:



Document 1: "The quick brown fox"

Document 2: "The lazy dog"



Inverted Index:

"the"   -> [Doc1, Doc2]

"quick" -> [Doc1]

"brown" -> [Doc1]

"fox"   -> [Doc1]

"lazy"  -> [Doc2]

"dog"   -> [Doc2]


When searching for "brown fox," the engine finds documents containing both terms and ranks them by relevance.


Search Engine Comparison


| Feature | Elasticsearch | Meilisearch | Typesense |

|---------|--------------|-------------|-----------|

| Setup complexity | High | Low | Low |

| Query language | Query DSL (JSON) | Simple search parameters | Simple API |

| Relevance tuning | BM25, custom scripts | Built-in (good defaults) | Built-in (good defaults) |

| Typo tolerance | Via fuzzy queries | Built-in (excellent) | Built-in (excellent) |

| Faceted search | Excellent | Good | Good |

| Geospatial search | Excellent | Limited | Built-in |

| Scalability | Very high | Moderate | High |

| Resource usage | High (Java JVM) | Low (Rust) | Low (C++) |

| Managed service | Elastic Cloud | Meilisearch Cloud | Typesense Cloud |


Elasticsearch


Indexing Data



// Create index with mapping

PUT /articles

{

  "settings": {

    "analysis": {

      "analyzer": {

        "custom_analyzer": {

          "type": "standard",

          "stopwords": "_english_"

        }

      }

    }

  },

  "mappings": {

    "properties": {

      "title": {

        "type": "text",

        "analyzer": "custom_analyzer",

        "fields": {

          "keyword": { "type": "keyword" }

        }

      },

      "content": { "type": "text" },

      "author": { "type": "keyword" },

      "tags": { "type": "keyword" },

      "published_at": { "type": "date" },

      "views": { "type": "integer" }

    }

  }

}



// Index a document

POST /articles/_doc/1

{

  "title": "Database Indexing Strategies",

  "content": "A comprehensive guide to indexing...",

  "author": "alice",

  "tags": ["database", "performance"],

  "published_at": "2026-05-11",

  "views": 1200

}


Searching



// Full-text search with relevance

GET /articles/_search

{

  "query": {

    "bool": {

      "must": [

        {

          "multi_match": {

            "query": "database indexing performance",

            "fields": ["title^3", "content^1"],  // Title is 3x more important

            "type": "best_fields"

          }

        }

      ],

      "filter": [

        { "term": { "author": "alice" } },

        { "range": { "views": { "gte": 100 } } }

      ]

    }

  },

  "sort": [

    { "_score": "desc" },

    { "published_at": "desc" }

  ],

  "from": 0,

  "size": 20,

  "aggs": {

    "by_tag": {

      "terms": { "field": "tags" }

    }

  }

}



from elasticsearch import Elasticsearch



es = Elasticsearch(['https://localhost:9200'])



results = es.search(

    index='articles',

    query={

        'multi_match': {

            'query': 'database indexing',

            'fields': ['title^3', 'content']

        }

    },

    aggs={'by_tag': {'terms': {'field': 'tags'}}},

    size=20

)



for hit in results['hits']['hits']:

    print(f"{hit['_score']:.2f}: {hit['_source']['title']}")



# Facet results

for bucket in results['aggregations']['by_tag']['buckets']:

    print(f"{bucket['key']}: {bucket['doc_count']} articles")


Meilisearch


Meilisearch focuses on developer experience with sensible defaults and instant typo-tolerant search.


Setup



docker run -it --rm \

    -p 7700:7700 \

    -v $(pwd)/meili_data:/meili_data \

    getmeili/meilisearch:v1.6 \

    --master-key=MASTER_KEY


Indexing and Search



const { MeiliSearch } = require('meilisearch');



const client = new MeiliSearch({

    host: 'http://localhost:7700',

    apiKey: 'MASTER_KEY',

});



// Index documents

const index = client.index('articles');

await index.addDocuments([

    {

        id: 1,

        title: "Database Indexing Strategies",

        content: "A comprehensive guide...",

        tags: ["database", "performance"],

        views: 1200

    }

]);



// Configure searchable attributes

await index.updateSearchableAttributes(['title', 'content']);

await index.updateSortableAttributes(['views', 'published_at']);

await index.updateFilterableAttributes(['tags', 'author']);



// Search (typo tolerance built-in)

const results = await index.search('database indexing', {

    limit: 20,

    filter: 'views > 100',

    sort: ['views:desc']

});


Typesense


Typesense is a fast, typo-tolerant search engine written in C++.


Setup



# docker-compose.yml

version: '3'

services:

  typesense:

    image: typesense/typesense:27.0

    ports:

      - "8108:8108"

    environment:

      TYPESENSE_API_KEY: "xyz"

      TYPESENSE_DATA_DIR: /data

    volumes:

      - ./typesense-data:/data


Indexing and Search



import typesense



client = typesense.Client({

    'nodes': [{'host': 'localhost', 'port': '8108', 'protocol': 'http'}],

    'api_key': 'xyz',

})



# Create collection (schema)

client.collections.create({

    'name': 'articles',

    'fields': [

        {'name': 'title', 'type': 'string'},

        {'name': 'content', 'type': 'string'},

        {'name': 'tags', 'type': 'string[]', 'facet': True},

        {'name': 'views', 'type': 'int32'},

        {'name': 'published_at', 'type': 'int64'},

    ],

    'default_sorting_field': 'views'

})



# Index documents

client.collections['articles'].documents.create({

    'id': '1',

    'title': 'Database Indexing Strategies',

    'content': 'A comprehensive guide...',

    'tags': ['database', 'performance'],

    'views': 1200,

    'published_at': 1715385600

})



# Search

results = client.collections['articles'].documents.search({

    'q': 'database indexing',

    'query_by': 'title,content',

    'filter_by': 'views:>100',

    'sort_by': 'views:desc',

    'facet_by': 'tags',

    'per_page': 20

})


When to Use Which


| Scenario | Recommended |

|----------|-------------|

| Complex analytics with search | Elasticsearch |

| Simple, fast search out of the box | Meilisearch |

| Low-latency search on limited hardware | Typesense |

| Log analysis and observability | Elasticsearch (ELK stack) |

| E-commerce product search | Meilisearch or Typesense |

| Geo-search and aggregations | Elasticsearch |

| Typo-tolerant search for user-facing features | Meilisearch |

| Large-scale (100M+ documents) | Elasticsearch |


Summary


Full-text search engines are essential for applications that need more than simple SQL LIKE queries. Elasticsearch offers the most features and scalability at the cost of operational complexity. Meilisearch provides the best developer experience with instant typo tolerance and sensible defaults. Typesense offers excellent performance with minimal resource usage. Choose based on your scale, feature requirements, and operational capacity: start with Meilisearch or Typesense for typical web applications and move to Elasticsearch when you need advanced analytics or larger scale.