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"
Basic Search
Simple Text Search
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— phraseWasserfestand free-textBerlinwasser" 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" }]
}
Faceted Search
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
- Use keyword fields for exact matches: Use
.keywordsuffix for term filters (e.g.,category.keyword) - Use sortable fields for sorting: Use
.sortableor.exactsuffixes for date/number sorting - Configure default date ranges: Set up
defaultDateRangesin config for consistent date facet behavior - Limit facet sizes: Use reasonable
sizevalues for facets to avoid performance issues - Use source filtering: Only request fields you need to reduce response size
- Monitor query performance: Use
tookandqueryInfoto debug slow queries - Test with status endpoint: Use
esStatusquery to verify connectivity before running searches