Skip to content

Search — Overview

The HappyColis API provides a unified, Elasticsearch-backed search system shared across all major domains: orders, products, stock, shipments, and delivery orders. Every searchable entity exposes the same input types, so the patterns you learn in one domain apply everywhere.


How Search Works

Under the hood, every domain index service syncs data from PostgreSQL into Elasticsearch. When you call a search query, the API translates the structured GraphQL input into an Elasticsearch query, executes it, and returns paginated results along with optional facet aggregations.

Because the search layer is Elasticsearch, you benefit from:

  • Sub-second response times even over millions of records
  • Full boolean filter logic (AND / OR / NOT)
  • Facet aggregations (bucket counts per field value)
  • Nested document filtering (filter on arrays of objects, e.g. order lines)
  • Cursor-based pagination for unbounded result sets

Pagination Modes

Two input types are available depending on how many results you expect:

ModeInput TypeUse WhenMax Results
Offset-basedSearchInput< 10,000 results; supports random page access10,000
Cursor-basedCursorSearchInput> 10,000 results; sequential access onlyUnlimited

SearchInput

Use SearchInput for everyday paginated queries. Supports a zero-based page number and hitsPerPage to control the result window.

graphql
input SearchInput {
  query: String                      # Full-text search query (Elasticsearch query_string syntax)
  fields: [String]                   # Fields to search in (default: all text fields)
  complexFilters: ComplexFilterInput # Structured boolean filters (recommended)
  facets: [String]                   # Facet/aggregation names, or ["*"] for all
  sort: [SortInput]                  # Sort instructions
  hitsPerPage: Int                   # Results per page (default: 50)
  page: Int                          # Zero-based page number (default: 0)

  # Deprecated — use complexFilters instead
  filters: JSONObject
  facetFilters: JSONObject
  numericFilters: [NumericFilterInput]
}

CursorSearchInput

Use CursorSearchInput when you need to page through more than 10,000 records (exports, full syncs, etc.). Replace page with a cursor field received from the previous response.

graphql
input CursorSearchInput {
  query: String
  fields: [String]
  complexFilters: ComplexFilterInput
  facets: [String]
  sort: [SortInput]
  hitsPerPage: Int
  cursor: String   # Base64 cursor from previous response's nextCursor
}

Cursor pagination rules:

  1. First request: omit cursor.
  2. The response includes nextCursor (Base64 string) and hasNextPage (boolean).
  3. Pass nextCursor as cursor in the next request.
  4. Stop when hasNextPage is false or nextCursor is null.

Cursor-based pagination does not support jumping to arbitrary pages. Use it for sequential full-dataset processing only.


SortInput

graphql
input SortInput {
  property: String!  # Elasticsearch field name
  order: OrderBy!    # asc | desc
}

Multiple sort fields are applied in order. For cursor-based pagination, a tiebreaker field (objectID) is automatically appended to ensure consistent ordering across pages.

Example — sort by issue date descending, then by ID:

graphql
sort: [
  { property: "issuedAt", order: desc },
  { property: "objectID", order: asc }
]

Facets / Aggregations

Passing facet names alongside a search request returns bucket counts for those fields in the response. This is useful for building filter UIs.

graphql
# Request specific facets
facets: ["state", "type"]

# Request all available facets for the domain
facets: ["*"]

Response shape:

json
{
  "facets": {
    "state": {
      "OPENED": 142,
      "COMPLETED": 87,
      "DRAFT": 23
    },
    "type": {
      "B2C": 198,
      "B2B": 54
    }
  }
}

Response Shape

All search queries return a consistent response structure:

graphql
type OrderSearchResult {
  hits: [OrderRecord!]!    # The matched records for the current page
  nbHits: Int!             # Total number of matching records
  nbPages: Int             # Total number of pages (offset-based only)
  page: Int                # Current page number (offset-based only)
  nextCursor: String       # Cursor for the next page (cursor-based only)
  hasNextPage: Boolean     # Whether more pages exist (cursor-based only)
  facets: JSONObject       # Aggregation buckets (when facets requested)
}

Domain Examples

Orders

graphql
query SearchOrders($input: SearchInput!) {
  orders(search: $input) {
    nodes {
      id
      state
      externalReference
      total
    }
    total
    pageInfo {
      hasNextPage
      endCursor
    }
    facets {
      field
      buckets {
        value
        count
      }
    }
  }
}

Variables:

json
{
  "input": {
    "query": "*",
    "complexFilters": {
      "must": [
        { "property": "organizationId", "operator": "in", "values": ["org-abc123"] },
        { "property": "state", "operator": "in", "values": ["OPENED"] }
      ]
    },
    "sort": [{ "property": "issuedAt", "order": "desc" }],
    "hitsPerPage": 25,
    "page": 0
  }
}

Products

graphql
query SearchProducts($input: SearchInput!) {
  products(search: $input) {
    nodes {
      id
      title
      sku
      status
    }
    total
    facets {
      field
      buckets {
        value
        count
      }
    }
  }
}

Variables:

json
{
  "input": {
    "query": "running shoes",
    "complexFilters": {
      "must": [
        { "property": "organizationId", "operator": "in", "values": ["org-abc123"] },
        { "property": "active", "operator": "in", "values": ["true"] }
      ]
    },
    "facets": ["brand", "category"],
    "sort": [{ "property": "createdAt", "order": "desc" }],
    "hitsPerPage": 24,
    "page": 0
  }
}

Stock References

graphql
query SearchStock($input: SearchInput!) {
  stockReferences(search: $input) {
    nodes {
      id
      sku
      availableQuantity
      locationId
    }
    total
  }
}

Variables:

json
{
  "input": {
    "complexFilters": {
      "must": [
        { "property": "organizationId", "operator": "in", "values": ["org-abc123"] },
        { "property": "locationId", "operator": "in", "values": ["loc-warehouse-1"] },
        { "property": "availableQuantity", "operator": "gt", "value": 0 }
      ]
    },
    "sort": [{ "property": "availableQuantity", "order": "asc" }],
    "hitsPerPage": 100,
    "page": 0
  }
}

Further Reading

HappyColis API Documentation