Build search and aggregate queries
Add the @searchable
directive to an @model
type to enable OpenSearch-based data search and result aggregations. This gives you the ability to:
- search for data using advanced filters, such as substring matching, wildcards, regex,
and
/or
/not
conditions - get aggregation values, such as sum, average, min, max, terms
- retrieve total search result count
- sort the search results across one or multiple fields
type Student @model @searchable { name: String dateOfBirth: AWSDate email: AWSEmail examsCompleted: Int}
Once the
@searchable
directive is added, all new records added to the model are streamed to OpenSearch. To backfill existing data, see Backfill OpenSearch index from DynamoDB table.
Search and filter data
Every model with a @searchable
directive attached generates a new "search" GraphQL query to search and filter for records. The example above provides you the ability to search for "Student" records using a "searchStudents" query.
The filter
parameter allows you to filter for records based on their field values.
query SearchStudentsByEmail { searchStudents(filter: { name: { eq: "Rene Brandel" } }) { items { id name email } }}
In the example above, the search result consists of students with the name "Rene Brandel"
Supported search operations
Field type | Supported search operations |
---|---|
String | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp |
Int | ne, gt, lt, gte, lte, eq, range |
Float | ne, gt, lt, gte, lte, eq, range |
Boolean | eq, ne |
Enum | ne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp |
Nested search conditions (and, or, not)
Use the filter parameter to pass a nested and
/or
/not
condition.
query MyQuery { searchStudents( filter: { name: { wildcard: "*Brandel" } or: [{ dateOfBirth: { lt: "2000-01-01" } }, { email: { exists: true } }] } ) { items { id name email dateOfBirth } }}
By default, every operation in the filter properties is and
ed. Use the or
or not
properties in the search query's filter
parameter to override this behavior.
The query above returns a "Student" if:
- their name ends with "Brandel"
and
- their date of birth is earlier than 2000-01-01
or
- their email exists.
Sort search results
Use the sort
parameter to sort your search results by a field in ascending or descending order. The field
argument accepts any field available on the model. The direction
accepts either asc
or desc
.
query SearchAndSort { searchStudents( filter: { name: { wildcard: "*Brandel" } } sort: { direction: desc, field: name } ) { items { name id } }}
In the example above, the search result is sorted based on their name
in a desc
ending order.
Sort search result over multiple fields
To sort over multiple fields, provide array of sort conditions. When sorting over multiple fields, the sort conditions are applied in the sort
array's order.
query SearchAndSort { searchStudents( filter: { name: { wildcard: "*Brandel" } } sort: [ { field: name, direction: desc } # Sort condition #1 { field: dateOfBirth, direction: asc } # Sort condition #2 ] ) { items { id name dateOfBirth } }}
In the example above, the search result is first sorted by name
in a desc
ending order and then by dateOfBirth
in an asc
ending order.
Paginate over search results
By default, the search result page size is 100. To customize the page size modify the limit
parameter. Query for the nextToken
and use it in your subsequent pagination requests:
query MyQuery { searchTodos(nextToken: "<YOUR_NEXT_TOKEN>") { # Pass in your nextToken in query items { description id name createdAt } nextToken # Next token to paginate on }}
Total count of search results
Add the total
field in your query response to get the total count of search result hits.
query MyQuery { searchStudents(filter: { name: { wildcard: "*Brandel" } }) { items { id } total # Specify to get total counts }}
In the example above, the response's total
field contains the total search result count for "Students" whose name ends with "Brandel". Note: total
is calculated based on all records, irrespective of pagination configurations.
Aggregate values for search result (minimum, maximum, average, sum, terms)
Use the aggregates
parameter to get aggregate values such as "minimum", "maximum", "average", and "sum" returned in the aggregateItems
field. Note: aggregates
are calculated based on all records, irrespective of pagination configurations.
Provide the min
value as the aggregate
type
and specify the aggregateItems
in the response field.
query MyQuery { searchStudents( aggregates: { type: min # Specifies that you want the "min" value field: examsCompleted # Specifies the field for the aggregate value name: "minimumExams" # provides a name to reference in the response field } filter: { name: { wildcard: "Rene*" } } ) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } }}
In the example above, the response includes the minimum value of "examsCompleted" for all Students whose name starts with "Rene".
{ "data": { "searchStudents": { "aggregateItems": [{ "name": "minimumExams", "result": { "value": 7 } }] } }}
Provide the max
value as the aggregate
type
and specify the aggregateItems
in the response field.
query MyQuery { searchStudents( aggregates: { type: max # Specifies that you want the "max" value field: examsCompleted # Specifies the field for the aggregate value name: "maximumExams" # provides a name to reference in the response field } filter: { name: { wildcard: "Rene*" } } ) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } }}
In the example above, the response includes the maximum value of "examsCompleted" for all Students whose name starts with "Rene".
{ "data": { "searchStudents": { "aggregateItems": [{ "name": "maximumExams", "result": { "value": 28 } }] } }}
Provide the avg
value as the aggregate
type
and specify the aggregateItems
in the response field.
query MyQuery { searchStudents( aggregates: { type: avg # Specifies that you want the "avg" value field: examsCompleted # Specifies the field for the aggregate value name: "averageExams" # provides a name to reference in the response field } filter: { name: { wildcard: "Rene*" } } ) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } }}
In the example above, the response includes the average value of "examsCompleted" for all Students whose name starts with "Rene".
{ "data": { "searchStudents": { "aggregateItems": [{ "name": "averageExams", "result": { "value": 17.3 } }] } }}
Provide the sum
value as the aggregate
type
and specify the aggregateItems
in the response field.
query MyQuery { searchStudents( aggregates: { type: sum # Specifies that you want the "sum" value field: examsCompleted # Specifies the field for the aggregate value name: "examsSum" # provides a name to reference in the response field } filter: { name: { wildcard: "Rene*" } } ) { aggregateItems { name result { ... on SearchableAggregateScalarResult { value } } } }}
In the example above, the response includes the sum of all "examsCompleted" values for all Students whose name starts with "Rene".
{ "data": { "searchStudents": { "aggregateItems": [{ "name": "examsSum", "result": { "value": 392 } }] } }}
Provide the terms
value as the aggregate
type
and specify the aggregateItems
in the response field.
query MyQuery { searchTodos( aggregates: { field: description, type: terms, name: "descriptionTerms" } ) { aggregateItems { result { ... on SearchableAggregateBucketResult { __typename buckets { doc_count key } } } name } }}
In the example above, the response includes the terms for the description and their count:
{ "data": { "searchTodos": { "aggregateItems": [ { "result": { "__typename": "SearchableAggregateBucketResult", "buckets": [{ "doc_count": 2, "key": "Shopping list" }, { "doc_count": 1, "key": "Me next todo" }] }, "name": "descriptionTerms" } ] } }}
Set up OpenSearch for production environments
By default, Amplify CLI will configure a t2.small instance type. This is great for getting started and prototyping but not recommended to be used in the production environment per the OpenSearch best practices.
To configure the OpenSearch instance type per environment:
- Run
amplify env add
to create a new environment (e.g. "prod") - Edit the
amplify/team-provider-info.json
file and setOpenSearchInstanceType
to the instance type that works for your application
{ "dev": { "categories": { "api": { "<your-api-name>": { "OpenSearchInstanceType": "t2.small.elasticsearch" } } } }, "prod": { "categories": { "api": { "<your-api-name>": { "OpenSearchInstanceType": "t2.medium.elasticsearch" } } } }}
- Deploy your changes with
amplify push
Learn more about Amazon OpenSearch Service instance types here.
How it works
The @searchable
directive streams the data of an @model type to Amazon OpenSearch Service and configures search resolvers to query against OpenSearch.
Type definition of the @searchable
directive:
# Streams data from DynamoDB to OpenSearch and exposes search capabilities.directive @searchable(queries: SearchableQueryMap) on OBJECTinput SearchableQueryMap { search: String}