﻿---
title: Using the API
description: The Go client for Elasticsearch offers two API styles, plus fluent DSL builders that enhance the typed API: Low-level API — provides a one-to-one mapping...
url: https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/using-the-api
products:
  - Elasticsearch Client
  - Elasticsearch Go Client
---

# Using the API
The Go client for Elasticsearch offers two API styles, plus fluent DSL builders that enhance the typed API:
- **Low-level API** — provides a one-to-one mapping with the Elasticsearch REST API. Each endpoint accepts and returns raw `io.Reader` bodies and `*esapi.Response` objects. This gives you full control over serialization and request construction.
- **Fully-typed API** — provides a strongly typed, builder-pattern API generated from the [elasticsearch-specification](https://github.com/elastic/elasticsearch-specification). Requests and responses are modeled as Go structs, giving you compile-time safety and IDE autocompletion.
- **[esdsl builders](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/typed-api/esdsl)** — a companion to the typed API that provides fluent, chainable builders for constructing queries, aggregations, mappings, and sort options. Instead of manually assembling nested structs, you use `esdsl.NewMatchQuery("title", "Go")` and pass it directly to the typed client.

All three approaches share the same underlying transport and [configuration](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/configuration). You can mix and match them in the same application.

## Comparing API styles


|                        | Low-level API                                   | Fully-typed API                                  | esdsl API                                                     |
|------------------------|-------------------------------------------------|--------------------------------------------------|---------------------------------------------------------------|
| **Type safety**        | Runtime (raw JSON)                              | Compile-time (Go structs)                        | Compile-time (fluent builders)                                |
| **IDE autocompletion** | Limited                                         | Full support for fields, enums, and methods      | Full support with guided method chains                        |
| **Serialization**      | Manual (`io.Reader`, `json.Marshal`)            | Automatic (structs marshaled by the client)      | Automatic (builders produce typed structs)                    |
| **Response handling**  | Raw `*esapi.Response` with `io.ReadCloser` body | Typed response structs                           | Typed response structs (same as fully-typed)                  |
| **Flexibility**        | Full control over request/response bytes        | Constrained to the specification model           | Constrained to the specification model                        |
| **Code verbosity**     | Lower for simple requests                       | Lower for complex queries with nested structures | Lowest for complex queries - minimal nesting, fluent chaining |

<important>
  When using the low-level API, you **must** always read and close the response body, even if your code does not use it. Failing to do so prevents Go's HTTP client from reusing the underlying TCP connection, which degrades performance and can cause resource leaks.
  ```go
  res, err := client.Search(
      client.Search.WithIndex("my-index"),
      client.Search.WithBody(strings.NewReader(query)),
  )
  if err != nil {
      log.Fatal(err)
  }
  defer res.Body.Close()
  ```
  The fully-typed API handles this automatically. Each endpoint's `Do()` method reads and closes the response body internally, so you only work with typed response structs.
</important>


## When to use which

**Choose the low-level API when:**
- You need full control over request serialization (e.g., custom JSON encoders)
- You are working with endpoints not yet covered by the typed API
- You prefer working with raw JSON bytes

**Choose the typed API with structs when:**
- You want compile-time safety and IDE autocompletion
- You are working with simple, flat request structures
- You want typed response objects instead of parsing JSON manually

**Choose the typed API with esdsl builders when:**
- You are building complex queries with deeply nested structures (bool queries, compound filters)
- You are composing aggregations with sub-aggregations
- You want the most concise code with fluent method chaining
- You are constructing queries dynamically at runtime

<tip>
  You don't have to choose one exclusively. All three approaches share the same transport, so you can use `esdsl` builders for queries, typed structs for simple operations, and fall back to the low-level API for edge cases - all in the same application.
</tip>


## Side-by-side example

Here is the same search operation using all three approaches:
<tab-set>
  <tab-item title="Low-level API">
    ```go
    res, err := es.Search(
        es.Search.WithIndex("my-index"),
        es.Search.WithBody(strings.NewReader(`{
            "query": {
                "match": {
                    "title": "golang"
                }
            }
        }`)),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()

    // Parse the response body manually
    var result map[string]any
    json.NewDecoder(res.Body).Decode(&result)
    ```
  </tab-item>

  <tab-item title="Fully-typed API">
    ```go
    res, err := es.Search().
        Index("my-index").
        Request(&search.Request{
            Query: &types.Query{
                Match: map[string]types.MatchQuery{
                    "title": {Query: "golang"},
                },
            },
        }).
        Do(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    // Response is already typed
    for _, hit := range res.Hits.Hits {
        fmt.Println(hit.Source_)
    }
    ```
  </tab-item>

  <tab-item title="esdsl API">
    The [`esdsl`](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/typed-api/esdsl) package provides fluent builders that plug directly into the typed API:
    ```go
    import "github.com/elastic/go-elasticsearch/v9/typedapi/esdsl"
    ```

    ```go
    res, err := es.Search().
        Index("my-index").
        Query(esdsl.NewMatchQuery("title", "golang")).
        Do(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    // Response is already typed
    for _, hit := range res.Hits.Hits {
        fmt.Println(hit.Source_)
    }
    ```
  </tab-item>
</tab-set>


## What's covered in this section

- [CRUD operations](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/using-the-api/crud-operations) — Index, get, update, and delete documents
- [Searching](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/using-the-api/searching) — Build and run search queries
- [Aggregations](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/using-the-api/aggregations) — Run aggregations on your data
- [Bulk indexing](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/using-the-api/bulk-indexing) — Efficiently ingest large volumes of documents
- [ES|QL](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/using-the-api/esql) — Use the Elasticsearch Query Language
- [Dense vectors and kNN search](https://www.elastic.co/elastic/docs-builder/docs/2948/reference/elasticsearch/clients/go/using-the-api/dense-vectors) — Work with vector embeddings and similarity search