Meili

An idiomatic, high-performance, and lightweight Meilisearch client for Elixir built on top of Req and Finch.

Why Meili?

Installation

Add meili to your list of dependencies in mix.exs:

def deps do
[
{:meili, "~> 0.1.0"}
]
end

Configuration

In your config/config.exs or config/runtime.exs:

config :meili,
url: System.get_env("MEILI_HTTP_ADDR") || "http://localhost:7700",
key: System.get_env("MEILI_MASTER_KEY")

Quick Start

Global Client Mode

If you're using a single Meilisearch instance, configure your credentials in the config and call operations directly:

# Create an index
{:ok, task} = Meili.create_index("movies", primary_key: "id")
# Block until index creation completes
Meili.wait_for_task!(task["taskUid"])
# Add documents
documents = [
%{id: 1, title: "Star Wars: Episode IV", genre: "Sci-Fi"},
%{id: 2, title: "The Dark Knight", genre: "Action"}
]
{:ok, task} = Meili.add_documents("movies", documents)
Meili.wait_for_task!(task["taskUid"])
# Search documents (note: snake_case parameters are automatically converted to camelCase)
{:ok, results} = Meili.search("movies", "star wars", limit: 5, show_ranking_score: true)
IO.inspect(results["hits"])

Explicit Client Mode (Multi-tenant / Dynamic Setup)

If you need to connect to multiple instances dynamically:

client = Meili.client(url: "https://my-meili-instance.com", key: "secret-key")
# Perform search with the explicit client
{:ok, results} = Meili.search(client, "movies", "batman")

Settings Management

settings = %{
searchable_attributes: ["title", "overview"],
filterable_attributes: ["genre"],
ranking_rules: ["words", "typo", "proximity"]
}
{:ok, task} = Meili.Settings.update("movies", settings)
Meili.wait_for_task!(task["taskUid"])

Advanced Req Options

Since Meili is built on Req, you can pass custom options directly down to the underlying HTTP client using :req_options. This is useful for adjusting timeouts, enabling compression, configuring connection pooling, or modifying how JSON is parsed.

client = Meili.Client.new(
url: "http://localhost:7700",
key: "secret-key",
req_options: [
receive_timeout: 60_000,
compress_body: true,
finch: MyApp.MeiliFinch
]
)

Note: meili preserves the raw Meilisearch payloads and returns maps with string keys (e.g., task["taskUid"]). String keys are enforced to prevent VM memory exhaustion (atoms are not garbage collected).

Migration from meilisearch_ex

If you are porting an existing codebase from meilisearch_ex, here is a quick mapping of common functions:

meilisearch_exmeili
Meilisearch.Index.create(uid, primaryKey: pk)Meili.Index.create(client, uid, primary_key: pk)
Meilisearch.Document.add_or_replace(idx, docs)Meili.Document.add_or_replace(client, idx, docs)
Meilisearch.Document.delete(idx, id)Meili.Document.delete(client, idx, id)
Meilisearch.Settings.update(idx, settings)Meili.Settings.update(client, idx, settings)
Meilisearch.Search.search(idx, query, opts)Meili.Search.search(client, idx, query, opts)

Testing

This library comes with a mock-server testing suite built using Bypass. You can run the tests locally without needing a live Meilisearch instance:

mix test