The official Go client for Elasticsearch is github.com/elastic/go-elasticsearch/v9. It offers two API styles: a low-level client that works with raw JSON and an HTTP-level DSL, and a typed API (added in v8) that provides fully generated Go types for every request and response.
This article uses the typed API. It removes the guesswork from query construction and makes IDE completion actually useful when building complex queries.
packageelasticimport("github.com/elastic/go-elasticsearch/v9")// NewClient creates a typed Elasticsearch client.// In production, addresses and credentials come from environment variables.funcNewClient(address,username,passwordstring)(*elasticsearch.TypedClient,error){cfg:=elasticsearch.Config{Addresses:[]string{address},Username:username,Password:password,}returnelasticsearch.NewTypedClient(cfg)}
TypedClient wraps all APIs with generated request/response structs. You never need to manually marshal JSON for standard operations.
For production with API key authentication (preferred over username/password):
Index Creation
Define the mapping as a Go struct using the types package, then create the index:
Document Types
Define Go structs that represent the document shape. These align exactly with the mapping:
Indexing a Single Document
The typed client handles JSON serialization. Pass the struct directly.
Bulk Indexing
For ingesting or synchronizing large sets of documents, use the BulkIndexer helper from esutil:
The BulkIndexer batches requests internally. The OnFailure callback is critical — failed individual documents do not cause the overall Close to return an error, so you must handle them explicitly.
Searching Documents
Build a typed search request. The following implements a paginated full-text search with filter and aggregation:
HTTP Handler Example
Wire the search function into an HTTP handler. I use the standard library net/http with encoding/json:
Deleting a Document
Partial Update
Error Handling Pattern
The typed client returns typed errors. Check for *types.ElasticsearchError to distinguish Elasticsearch-level errors (like index not found or document already exists) from transport errors:
Summary
Use elasticsearch.NewTypedClient for full type safety on request/response structs.
Store the client as a dependency; initialize it once at application startup.
Pass struct values directly to .Request() — the client handles JSON serialization.
Use esutil.BulkIndexer for batch operations; always handle OnFailure.
Parse aggregation results via type assertions on res.Aggregations[name].
Use errors.As with *types.ElasticsearchError for structured error handling.