1. Graphql
  2. Elasticsearch

Graphql

Elasticsearch

IdentityHub provides comprehensive Elasticsearch search functionality through its GraphQL API, allowing you to search documents, apply filters, compute facets, and retrieve highlighted results—all through a unified GraphQL interface.

Overview

The Elasticsearch integration provides:

  • Full-Text Search: Multi-field search with boost values and relevance scoring
  • Advanced Filtering: Boolean logic (AND/OR), term filters, range filters, prefix filters, and exists/not-exists filters
  • Faceted Search: Dynamic aggregations for building filter UIs (terms, date ranges, prefixes)
  • Result Highlighting: Configurable highlighting of search matches in result fields
  • Cursor-Based Pagination: Relay-style cursor pagination with page number information
  • Flexible Sorting: Sort by relevance, custom fields, or multiple fields
  • Source Filtering: Control which fields are returned in search results
  • Query Debugging: Complete query information for troubleshooting

Configuration

Configure Elasticsearch in your config file:

[elasticsearch]
# Elasticsearch endpoint URL
endpoint = "http://localhost:9200"

# Request timeout in milliseconds
timeout = 30000

# Authentication (optional)
auth = { username = "elastic", password = "changeme" }

# TLS configuration (optional)
tls = { rejectUnauthorized = false }

# Default fields to search (if not specified in query)
fields = ["title", "content", "category"]

# Highlight configuration
highlights = { enabled = true }

sortFields = {
  

}

defaultDateRanges = [
  { from = "now-1y", to = "now", key = "P1Y" }
  { from = "now-1M", to = "now", key = "P1M" }
  { from = "now-1w", to = "now", key = "P1W" }
  { from = "now-3d", to = "now", key = "P3D" }
]# Default date ranges for DATE_RANGE facets"title" = "title.exact"# Sort field mappings (GraphQL field name -> Elasticsearch field name)
"date" = "date.sortable"
"relevance" = "_score"
query SearchDocuments {
	esSearch(q: "identity management") {
		nodes {
			id
			score
			source
		}
		pageInfo {
			hasNextPage
			currentPage
			totalPages
		}
	}
}

Phrase search (quoted q)

Wrap terms in double quotes for an exact phrase match (multi_match with type: phrase, slop: 0, same operator / use_dis_max / lenient flags as the legacy app). Phrase queries and highlighting both target parent multifields (e.g. displayTitle from displayTitle.standard in config), matching legacy Aquanet Elasticsearch behaviour. Unquoted free text still uses the configured *.standard fields via query_string with AND.

query PhraseSearch {
	esSearch(q: "\"Wasserfest\" Berlin") {
		nodes {
			id
			score
		}
	}
}
  • "wasserfest" — phrase only
  • "Wasserfest" Berlin — phrase Wasserfest and free-text Berlin
  • wasser" fest — accidental quote: no phrase; the full string is searched as plain text (quotes are escaped for Elasticsearch)
  • Only paired "..." segments count as phrases; unclosed quotes are treated as literal characters

The client should pass q unchanged (including quotes). Optional escape sequences from the client (\", \\) are normalized before parsing.

Search with Filters

query SearchWithFilters {
	esSearch(
		q: "user authentication"
		filters: {
			terms: [
				{ field: "category.keyword", value: "documentation" }
				{ field: "status.keyword", value: "published" }
			]
			ranges: [{ field: "date.standard", from: "2025-01-01", to: "now" }]
		}
	) {
		nodes {
			id
			score
			source
		}
	}
}

Advanced Filtering

Boolean Logic (AND/OR)

query ComplexFilters {
	esSearch(
		filters: {
			or: [
				{ terms: [{ field: "category.keyword", value: "news" }] }
				{
					and: [
						{ terms: [{ field: "category.keyword", value: "blog" }] }
						{ ranges: [{ field: "date.standard", from: "2025-01-01" }] }
					]
				}
			]
		}
	) {
		nodes {
			id
			source
		}
	}
}

Filter Types

Term Filters (exact matches):

filters: {
  terms: [
    { field: "category.keyword", value: "technology" }
    { field: "author.keyword", value: "John Doe" }
  ]
}

Range Filters (dates and numbers):

filters: {
  ranges: [
    { field: "date.standard", from: "2025-01-01", to: "2025-12-31" }
    { field: "price", from: "10", to: "100" }
  ]
}

Prefix Filters:

filters: {
  prefix: [
    { field: "index", value: "ad-2000-" }
  ]
}

Exists/Not Exists Filters:

filters: {
  exists: [{ field: "publishedDate.standard" }]
  notExists: [{ field: "deletedAt.standard" }]
}

Facets provide aggregation results for building filter UIs:

Terms Facet

query SearchWithFacets {
	esSearch(
		q: "identity"
		facets: [{ name: "categories", type: TERMS, field: "category.keyword", size: 10 }]
	) {
		nodes {
			id
			source
		}
		facets {
			name
			buckets {
				key
				docCount
			}
		}
	}
}

Date Range Facet

query SearchWithDateFacets {
  esSearch(
    facets: [
      {
        name: "dateRanges"
        type: DATE_RANGE
        field: "date.standard"
        ranges: [
          { from: "now-1y", to: "now", key: "P1Y" }
          { from: "now-1M", to: "now", key = "P1M" }
          { from: "now-1w", to: "now", key = "P1W" }
        ]
      }
    ]
  ) {
    facets {
      name
      buckets {
        key
        docCount
      }
    }
  }
}

Facets with Pre-filters

query FacetsWithFilters {
	esSearch(
		facets: [
			{
				name: "filteredCategories"
				type: TERMS
				field: "category.keyword"
				filters: { ranges: [{ field: "date.standard", from: "2025-01-01" }] }
			}
		]
	) {
		facets {
			name
			buckets {
				key
				docCount
			}
		}
	}
}

Result Highlighting

Highlight search matches in result fields:

query SearchWithHighlighting {
	esSearch(
		q: "identity management"
		highlight: {
			fields: ["title", "content"]
			preTag: "<mark>"
			postTag: "</mark>"
			fragmentSize: 150
			numberOfFragments: 3
		}
	) {
		nodes {
			id
			source
			highlight
		}
	}
}

Pagination

Use cursor-based pagination following the Relay specification. The shared Paging input uses limit, first, last, after, and before — not size. For a simple page size, use paging: { limit: 20 } (capped at 100 per request). If paging is omitted, esSearch defaults to 5 results.

Forward Pagination

query FirstPage {
	esSearch(q: "identity", paging: { first: 10 }) {
		edges {
			node {
				id
				source
			}
			cursor
		}
		pageInfo {
			hasNextPage
			hasPreviousPage
			startCursor
			endCursor
			currentPage
			totalPages
			pageSize
		}
	}
}

Next Page

query NextPage {
	esSearch(q: "identity", paging: { first: 10, after: "eyJvZmZzZXQiOjEwfQ==" }) {
		edges {
			node {
				id
				source
			}
			cursor
		}
		pageInfo {
			hasNextPage
			currentPage
			totalPages
		}
	}
}

Backward Pagination

query PreviousPage {
	esSearch(q: "identity", paging: { last: 10, before: "eyJvZmZzZXQiOjIwfQ==" }) {
		edges {
			node {
				id
				source
			}
			cursor
		}
		pageInfo {
			hasPreviousPage
			currentPage
			totalPages
		}
	}
}

Sorting

Sort results by relevance or custom fields:

Sort by Relevance

query SortByRelevance {
	esSearch(q: "identity management", sort: { by: [relevance] }) {
		nodes {
			id
			score
			source
		}
	}
}

Sort by Custom Field

query SortByDate {
	esSearch(sort: { by: [date], direction: DESCENDING }) {
		nodes {
			id
			source
		}
	}
}

Multi-Field Sorting

query MultiFieldSort {
	esSearch(sort: { by: [date, title], direction: DESCENDING }) {
		nodes {
			id
			source
		}
	}
}

Field Selection

Control which fields are returned:

Include Specific Fields

query SelectFields {
	esSearch(sourceFields: { include: ["title", "summary", "category"] }) {
		nodes {
			id
			source
		}
	}
}

Exclude Fields

query ExcludeFields {
	esSearch(sourceFields: { exclude: ["content", "fullText"] }) {
		nodes {
			id
			source
		}
	}
}

Status Check

Check Elasticsearch connection and cluster status:

query CheckStatus {
	esStatus {
		connected
		clusterName
		version
		clusterHealth
		numberOfNodes
		numberOfDataNodes
		responseTime
		error
	}
}

Query Debugging

Enable query debugging to see the exact Elasticsearch query:

query DebugQuery {
	esSearch(q: "identity", filters: { terms: [{ field: "category.keyword", value: "docs" }] }) {
		queryInfo {
			parsedQuery
			esVersion
		}
	}
}

Index Selection

Search specific indices:

query SearchSpecificIndices {
	esSearch(indices: ["documents", "articles"], q: "identity") {
		nodes {
			id
			index
			source
		}
	}
}

Complete Example

A complete example combining multiple features:

query CompleteSearch {
	esSearch(
		q: "identity management"
		indices: ["documents", "articles"]
		filters: {
			terms: [{ field: "category.keyword", value: "documentation" }]
			ranges: [{ field: "date.standard", from: "2025-01-01", to: "now" }]
			exists: [{ field: "publishedDate.standard" }]
		}
		facets: [
			{ name: "categories", type: TERMS, field: "category.keyword", size: 10 }
			{
				name: "dateRanges"
				type: DATE_RANGE
				field: "date.standard"
				ranges: [
					{ from: "now-1y", to: "now", key: "P1Y" }
					{ from: "now-1M", to: "now", key: "P1M" }
				]
			}
		]
		highlight: { fields: ["title", "content"], preTag: "<mark>", postTag: "</mark>" }
		sort: { by: [date], direction: DESCENDING }
		sourceFields: { include: ["title", "summary", "category", "date"] }
		paging: { first: 20 }
	) {
		nodes {
			id
			score
			source
			highlight
			index
		}
		edges {
			node {
				id
				source
			}
			cursor
		}
		facets {
			name
			buckets {
				key
				docCount
				subFacets {
					name
					buckets {
						key
						docCount
					}
				}
			}
			sumOtherDocCount
		}
		pageInfo {
			hasNextPage
			hasPreviousPage
			startCursor
			endCursor
			currentPage
			totalPages
			pageSize
		}
		estimatedSize
		took
		maxScore
		queryInfo {
			parsedQuery
			esVersion
		}
	}
}

Compatibility

The Elasticsearch integration currently supports Elasticsearch 2.x syntax for compatibility with existing infrastructure. Support for modern Elasticsearch versions (8.x/9.x) is planned for future releases.

Best Practices

  1. Use keyword fields for exact matches: Use .keyword suffix for term filters (e.g., category.keyword)
  2. Use sortable fields for sorting: Use .sortable or .exact suffixes for date/number sorting
  3. Configure default date ranges: Set up defaultDateRanges in config for consistent date facet behavior
  4. Limit facet sizes: Use reasonable size values for facets to avoid performance issues
  5. Use source filtering: Only request fields you need to reduce response size
  6. Monitor query performance: Use took and queryInfo to debug slow queries
  7. Test with status endpoint: Use esStatus query to verify connectivity before running searches