Dowser.Elasticsearch
View Sourcedowser_elasticsearch is an Elixir client library for the Elasticsearch
API, built on top of dowser_client.
It gives you one function per Elasticsearch endpoint — named and shaped after
the endpoint itself — instead of a hand-rolled query builder.
- Predictable functions. Every endpoint maps to one function, named after
Elasticsearch's own operation names (
create_index,get_alias,search, …). Required attributes are positional arguments; everything optional lives in anoptskeyword list. - Every function has a bang variant.
search/2returns{:ok, body}/{:error, error};search!/2returns the body directly or raises.HEADexistence checks follow the same idea with a?variant instead of!. - A repository pattern for free.
Dowser.Elasticsearch.Repositorybinds the index-related functions ofSearch,Document, andIndexto a fixed or computed index, so your code stops repeatingindex: "posts"on every call. - Optional automatic type casting.
Dowser.Elasticsearch.TypeCodeccasts dates, IPs, and other Elasticsearch types to and from native Elixir terms, per index mapping, with no per-call option needed. - Bring your own HTTP/JSON stack. Transport is handled by
dowser_client, which defaults to Erlang's built-in:httpcand Elixir's built-inJSONmodule, or can be pointed atReq/:hackneyandJason/Poison.
Installation
Add dowser_elasticsearch to your list of dependencies in mix.exs:
def deps do
[
{:dowser_elasticsearch, "~> 0.1.0"}
]
endDocumentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/dowser_elasticsearch.
Configuration
Point the client at your cluster via dowser_client's :configs config —
see its README for the full set of
options (auth, headers, HTTP/JSON adapters):
config :dowser_client,
configs: [
default: [endpoint: "http://localhost:9200", auth: {:basic, "user", "changeme"}]
]Every API function accepts a :config option to target a specific entry
(or an ad-hoc inline config) instead of :default.
Usage
Each module maps to one Elasticsearch API tag. Request bodies come first so
calls pipe naturally; the index (when required) follows; everything
optional goes in opts.
Search
alias Dowser.Elasticsearch.Search
%{query: %{match: %{title: "hello"}}}
|> Search.search(index: "posts")
# {:ok, %{"hits" => %{...}}}
Search.count!(%{query: %{term: %{status: "published"}}}, index: "posts")
# %{"count" => 42, "_shards" => %{...}}Document
alias Dowser.Elasticsearch.Document
{:ok, %{"_id" => id}} = Document.index(%{title: "hello"}, "posts")
Document.get!("posts", id)
# %{"_source" => %{"title" => "hello"}, ...}
Document.exists?("posts", id)
# true
Document.delete("posts", id)Index
alias Dowser.Elasticsearch.Index
Index.create_index!(%{mappings: %{properties: %{title: %{type: "text"}}}}, "posts")
Index.index_exists?("posts")
# true
Index.refresh(index: "posts")
Index.delete_index("posts")Repository pattern
Calling Search, Document, and Index directly means repeating
index: "posts" (or recomputing it) on every call. use Dowser.Elasticsearch.Repository generates index-bound versions of those
functions instead:
defmodule MyApp.Posts do
use Dowser.Elasticsearch.Repository,
index: "posts",
only: [
search: [:search, :count],
document: [:index, :get, :delete, :exists],
index: [:create_index, :refresh]
]
end
MyApp.Posts.create_index!(%{mappings: %{properties: %{title: %{type: "text"}}}})
# PUT /posts
{:ok, %{"_id" => id}} = MyApp.Posts.index(%{title: "hello"})
# POST /posts/_doc
MyApp.Posts.search!(%{query: %{match_all: %{}}})
# GET /posts/_search
MyApp.Posts.exists?(id)
# HEAD /posts/_doc/:id — true/false:index also accepts a 1-arity function for a dynamic (e.g. per-tenant or
time-based) index. The generated functions then take a term — positionally
or via the :index option — and resolve it through that function:
defmodule MyApp.TenantLogs do
use Dowser.Elasticsearch.Repository,
index: &__MODULE__.index_name/1,
only: [search: [:search], document: [:index]]
def index_name(tenant), do: "logs_#{tenant}"
end
MyApp.TenantLogs.index(%{message: "boom"}, "acme")
# POST /logs_acme/_doc
MyApp.TenantLogs.search(%{query: %{match_all: %{}}}, index: "acme")
# GET /logs_acme/_searchWithout :only/:except, every index-related function of Search,
Document, and Index is generated. Functions that don't target an index
(templates, reindex, scroll, …) are never generated — call their module
directly.
Type casting
By default, response bodies come back as plain decoded JSON — dates, IPs and
other Elasticsearch types stay strings. Setting
Dowser.Elasticsearch.TypeCodec as :codec_adapter casts them automatically,
per index mapping, on every call to Search and Document:
config :dowser_client,
configs: [
default: [
endpoint: "http://localhost:9200",
codec_adapter: Dowser.Elasticsearch.TypeCodec
]
]Document.get!("posts", "1")
# %{"_source" => %{"published_at" => ~U[2026-08-11 00:00:00Z]}, ...}Mappings are fetched once and cached by Dowser.Elasticsearch.MappingCacher,
which the application supervises automatically. date, date_range, ip,
binary, geo_point and integer_range fields are cast out of the box; see
Dowser.Elasticsearch.Codec for how to add your own field types.
Compatibility
Elasticsearch version
Tested against Elasticsearch 9.x. Earlier versions haven't been tested but should work, since the wrapped endpoints are stable across releases.
Endpoint coverage
Elasticsearch groups its API into tags; only Search, Document, and
Index are currently implemented.
| Endpoint tag | Supported |
|---|---|
| Behavioral analytics | ❌ |
| Compact and aligned text (CAT) | ❌ |
| Cluster | ❌ |
| Cluster - Health | ❌ |
| Connector | ❌ |
| Cross-cluster replication | ❌ |
| Data stream | ❌ |
| Document | ✅ |
| Enrich | ❌ |
| EQL | ❌ |
| ES|QL | ❌ |
| Features | ❌ |
| Fleet | ❌ |
| Graph explore | ❌ |
| Index | ✅ |
| Index lifecycle management | ❌ |
| Inference | ❌ |
| Info | ❌ |
| Ingest | ❌ |
| Licensing | ❌ |
| Logstash | ❌ |
| Machine learning | ❌ |
| Machine learning anomaly detection | ❌ |
| Machine learning data frame analytics | ❌ |
| Machine learning trained model | ❌ |
| Migration | ❌ |
| Query rules | ❌ |
| Reindex | ❌ |
| Rollup | ❌ |
| Script | ❌ |
| Search | ✅ |
| Search application | ❌ |
| Searchable snapshots | ❌ |
| Security | ❌ |
| Snapshot and restore | ❌ |
| Snapshot lifecycle management | ❌ |
| SQL | ❌ |
| Streams | ❌ |
| Synonyms | ❌ |
| Task management | ❌ |
| Text structure | ❌ |
| Transform | ❌ |
| Usage | ❌ |
| Watcher | ❌ |
Trademark Notice
This project is an independent, community-maintained library and is not affiliated with, endorsed by, or sponsored by Elasticsearch B.V.
Elasticsearch is a trademark of Elasticsearch B.V., registered in the U.S. and in other countries.