Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Apr 29, 2024

Build search and aggregate queries

You are currently viewing the new GraphQL transformer v2 docs Looking for legacy docs?

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 typeSupported search operations
Stringne, eq, match, matchPhrase, matchPhrasePrefix, multiMatch, exists, wildcard, regexp
Intne, gt, lt, gte, lte, eq, range
Floatne, gt, lt, gte, lte, eq, range
Booleaneq, ne
Enumne, 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 anded. 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 descending 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 descending order and then by dateOfBirth in an ascending 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:

  1. Run amplify env add to create a new environment (e.g. "prod")
  2. Edit the amplify/team-provider-info.json file and set OpenSearchInstanceType 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"
}
}
}
}
}
  1. 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 OBJECT
input SearchableQueryMap {
search: String
}