Meili
An idiomatic, high-performance, and lightweight Meilisearch client for Elixir built on top of Req and Finch.
Why Meili?
- No Bloat: Built directly on
Req, leveraging connection pooling and HTTP/2 stream multiplexing out-of-the-box. No heavy middleware layers. - Idiomatic Elixir:
- Provides both a global/default client configuration (via
Applicationenvironment) and explicit client structs (for multi-tenant setups). - Automatically converts Elixir-style
snake_casekeys to Meilisearch-stylecamelCasekeys for query parameters, search options, and settings. - Standard operations return
{:ok, result}/{:error, error}and suffix-raising bang (!) functions (e.g.,search!/3). - Custom exception structures (
Meili.Error) wrap Meilisearch-specific API errors (code, type, message, link).
- Provides both a global/default client configuration (via
- Asynchronous Polling: Includes a robust
wait_for_task/3helper that polls tasks with backoff and raises clear error messages if tasks fail.
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"])
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