bodybuilder

repository·master·Indexed 22 days ago

https://github.com/danpaz/bodybuilder

An Elasticsearch query body builder (version 2.5.1) that provides a simple, predictable, and chainable API for constructing complex Elasticsearch DSL queries. It supports building DSL for Elasticsearch 2.x or greater by default, with optional support for 1.x. The library includes methods for creating queries, filters, aggregations, suggestions, sorting, and pagination.

Tokens
6.4K
Snippets
26
Records
63
Agent score
78%

What's inside bodybuilder

  1. How nested aggregations work

    master

    Aggregations can be nested by providing a callback function as the final argument to the .aggregation() method. This callback receives the current aggregation object, allowing you to call further .aggregation() methods on it to create a hierarchy.

    var body = new Bodybuilder()
      .aggregation('terms', 'someField', 'bySomeField',
        agg =>
          agg
            .aggregation('max', 'someOtherField')
            .aggregation('missing', 'anotherField')
       )
      .build();
  2. How to nest aggregations

    master

    To nest aggregations, pass a function as the last parameter to .aggregation(). This function receives the current aggregation instance (which acts as a builder) and should return an Object that is assigned to the .aggs property of the parent aggregation. Inside the function, you can call .aggregation() to add child aggregations.

    var body = bodybuilder().aggregation('terms', 'code', {
          order: { _term: 'desc' },
          size: 1
        }, agg => agg.aggregation('terms', 'name')).build()
    // body == {
    //   "aggregations": {
    //       "agg_terms_code": {
    //           "terms": {
    //               "field": "code",
    //               "order": {
    //                   "_term": "desc"
    //               },
    //               "size": 1
    //           },
    //           "aggs": {
    //               "agg_terms_name": {
    //                   "terms": {
    //                       "field": "name"
    //                   }
    //               }
    //           }
    //       }
    //   }
    // }
  3. Construct Elasticsearch aggregations

    master

    Aggregations allow you to perform complex calculations on your data. You can chain multiple aggregations using the .aggregation() method.

    var body = new Bodybuilder()
      .aggregation('sum', 'grade')
      .build()
  4. Migrate from Bodybuilder 1 to 2

    master

    When upgrading to Bodybuilder 2, several breaking changes require updates to your implementation:

    1. Remove new keyword: Bodybuilder 2 no longer uses JavaScript class syntax. Call the required module as a function instead of using new.
    2. Default Elasticsearch DSL: The default output is now the Elasticsearch > 2.x Query DSL (where filters live in query.bool.filter). If you need to maintain the old Elasticsearch 1.x behavior, you must explicitly pass 'v1' to the .build() method.
    3. Explicit Nested Paths: When using the nested filter type, you must now explicitly include the path in the sub-queries, as it is no longer implicitly provided.
    4. Updated Filter Aggregations: The API for filter aggregations has changed. Instead of passing a callback as the second argument, use the standard nesting pattern where the callback is used to define sub-aggregations on the filter object.
  5. Basic usage of bodybuilder

    master

    To build an Elasticsearch query, create an instance of bodybuilder, chain your desired clauses (queries, filters, aggregations, etc.), and call .build() to retrieve the final JSON query body. By default, .build() produces Elasticsearch 2.x or greater DSL. To support Elasticsearch 1.x, pass 'v1' as an argument to .build().

    var bodybuilder = require('bodybuilder')
    var body = bodybuilder().query('match', 'message', 'this is a test')
    body.build() // Build 2.x or greater DSL (default)
    body.build('v1') // Build 1.x DSL
  6. Use the bodybuilder REPL

    master

    You can test your query constructions interactively using the Node REPL. Run the following command to start the REPL. The bodybuilder instance is available in the global context as bodybuilder.

    # Start the repl
    node ./node_modules/bodybuilder/repl.js
    
    # Inside the REPL:
    bodybuilder > bodybuilder().query('match', 'message', 'this is a test').build()
  7. Combining queries, filters, and aggregations

    master

    You can chain multiple methods to build a complex request. Multiple .query() and .filter() calls are merged using boolean logic.

    Available filter modifiers:

    • .filter(type, field, term): Standard filter.
    • .orFilter(type, field, term): Adds to the should clause.
    • .notFilter(type, field, term): Adds to the must_not clause.

    You can also nest filters and queries by passing a function to .orFilter() or similar methods.

    var body = bodybuilder()
      .query('match', 'message', 'this is a test')
      .filter('term', 'user', 'kimchy')
      .filter('term', 'user', 'herald')
      .orFilter('term', 'user', 'johnny')
      .notFilter('term', 'user', 'cassie')
      .aggregation('terms', 'user')
      .suggest('term', 'user', { text: 'kimchy' })
      .build()
  8. Create a query with .query()

    master

    The .query([arguments]) method creates a query of a specific type.

    Typical arguments follow this pattern:

    • queryType: The name of the query (e.g., 'term', 'match', 'prefix').
    • fieldToQuery: The name of the field in your index.
    • searchTerm: The value to search for.
    var body = bodybuilder().query('match', 'message', 'this is a test').build()
    // body == {
    //   query: {
    //     match: {
    //       message: 'this is a test'
    //     }
    //   }
    // }
  9. Apply queries, filters, and aggregations

    master

    Bodybuilder provides high-level methods to merge different parts of an Elasticsearch query:

    • query(type, ...args): Applies a query of a specific type. Existing queries are merged with the new one.
    • filter(type, ...args): Applies a filter of a specific type. Existing filters are merged with the new one.
    • aggregation(type, ...args): Applies an aggregation. You can nest aggregations by passing a callback function as the last argument. The callback receives the newly built aggregation instance.
    var body = new Bodybuilder()
      .query('match', 'text', 'this is a test')
      .aggregation('terms', 'someField', 'bySomeField',
        // Nest aggregations on "bySomeField"
        agg =>
          agg
            .aggregation('max', 'someOtherField')
            .aggregation('missing', 'anotherField')
       )
      .build();
  10. Create a filter with .filter()

    master

    The .filter([arguments]) method creates a filtered query using a specific filter type. Filters are merged into a boolean query's filter clause.

    Typical arguments follow this pattern:

    • filterType: The name of the filter (e.g., 'regexp', 'exists', 'term').
    • fieldToQuery: The name of the field to filter on.
    • searchTerm: The value to filter by.
    bodybuilder().filter('term', 'message', 'test').build()
    // body == {
    //   query: {
    //     bool: {
    //       filter: {
    //         term: {
    //           message: 'test'
    //         }
    //       }
    //     }
    //   }
    // }
  11. Use the updated API for filter aggregations

    master

    The API for filter aggregations has been standardized to follow the nesting pattern used by other aggregations. Instead of passing a callback for the filter logic as the second argument, pass the aggregation name as the second argument and use the callback to define both the filter and any sub-aggregations.

    // before
    new BodyBuilder()
      .aggregation('filter', filterBuilder => {
        return filterBuilder.filter('term', 'color', 'red')
      }, 'red_products', agg => agg.aggregation('avg', 'price', 'avg_price'))
      .build()
    
    // after
    bodyBuilder().aggregation('filter', 'red_products', (a) => {
      return a.filter('term', 'color', 'red')
              .aggregation('avg', 'price', 'avg_price')
      })
      .build()