Front-End Web & Mobile

Build a Scalable Search Solution with AWS Amplify and OpenSearch Serverless

Introduction

In today’s data-driven digital ecosystem, building scalable search solutions has become essential for organizations managing large, complex datasets across industries. Whether it’s a streaming platform, an e-commerce marketplace, or a research database, the ability to efficiently search through structured and unstructured data is key to delivering a seamless user experience. Traditional search solutions often involve complex data transformation processes and managing dedicated search clusters, which can increase operational costs and complexity.

By leveraging AWS Amplify, Amazon OpenSearch, and Amazon DynamoDB with Zero ETL, you can build scalable, cost-effective, and serverless search solutions. This approach simplifies infrastructure management, reduces costs, and ensures a seamless user experience even under fluctuating traffic.

In this post, we’ll demonstrate how to create a movie search solution using AWS Amplify and Amazon OpenSearch. This solution will provide search capabilities, allowing users to quickly find relevant movie information. We’ll use AWS Amplify’s streamlined development experience and Amazon OpenSearch’s to build this solution.

Figure 1: A movie collection website displaying a search bar and grid of five popular movie titles with their plot descriptions in white cards.

Figure 1: Movie Search Solution

Prerequisites

  • AWS account: Required to access and deploy AWS services. Note that Amplify is part of the AWS Free Tier.
  • Node.js v18.17+ and npm v9+.
  • Git v2.14.1+: For version control and repository management.
  • Text Editor (VSCode recommended): Provides an integrated development environment.

High Level Architecture

Our serverless search solution combines several AWS services:

  1. AWS Amplify: Provides a modern development framework for building cloud-based applications.
  2. Amazon OpenSearch: Service: Powers the search functionality with advanced search capabilities.
  3. Amazon DynamoDB: Acts as the primary data store for the application.
  4. Amazon DynamoDB Zero ETL: Streamlines the data ingestion process from DynamoDB to OpenSearch Service.
  5. Amazon S3: The S3 bucket will be used for the initial export, which OpenSearch Ingestion will retrieve data from at the beginning.

High-level architecture diagram showing the integration of AWS Amplify, Amazon DynamoDB, Amazon OpenSearch Serverless, and Amazon S3 in a serverless search solution. The diagram illustrates data flow between services, highlighting how DynamoDB Zero ETL synchronizes data with OpenSearch through an S3 staging area.

Figure 2: High-level technical architecture diagram illustrating the integration of AWS Amplify, Amazon DynamoDB, Amazon OpenSearch Serverless, and Amazon S3, with detailed data synchronization and flow paths

Figure 2: Architecture Diagram of the Movie Search Solution

How It Works:

Use the AWS Amplify-powered frontend to submit search queries, which Amazon OpenSearch processes against pre-indexed data. Amazon DynamoDB serves as the primary data store, capturing and updating application data in real time. The Zero ETL feature automatically synchronizes this data with OpenSearch, enabling instant indexing of your latest information.
Amazon S3 serves as an initial staging area during the first data transfer between Dynamo db and OpenSearch. This architecture ensures a responsive, low-latency search experience, providing users with accurate and relevant results while maintaining high scalability and performance.

Implementation Steps

Step 1: Repository Setup

  1. Navigate to the amplify-opensearch-serverless-integration repository on AWS Samples and fork it to your GitHub repositories.
  2. Clone the app by running the command below in your terminal
    git clone https://github.com/<YOUR_GITHUB>/amplify-opensearch-serverless-integration.git
    Bash
  3. Access the newly cloned repository in VSCode by executing the commands below in your terminal.
    cd amplify-opensearch-serverless-integration
    code .
    Bash

    VSCode will open the repository folder, including the Amplify folder, which contains the backend details that we’ll discuss in the next section.

    1. Project Structure Overview: The structure includes folders like amplify, backend, data, and storage, with key configuration files like backend.ts, resource.ts, and searchMovieResolver.js visible.

       Figure 3: Project Structure Overview in Visual Studio Code

      Figure 3: Project Structure Overview in Visual Studio Code

  4.  Run the following command at the project root in the terminal to install the necessary packages, including the Amplify packages.

          npm install

Step 2: Amplify Backend Configuration

In the final application, users can search for movies by entering a title and clicking the search button to retrieve results from Amazon OpenSearch. The code for this functionality is included in the repository you cloned. In this section, we’ll walk through the key steps to integrate your Amplify app with Amazon OpenSearch.

2.1 Define Data Model

Firstly, go to the amplify/data/resource.ts file and add the Movie model to your schema.


import { type ClientSchema, a, defineData } from "@aws-amplify/backend";

const schema = a.schema({
  Movie: a.model({
    title: a.string().required(),
    description: a.string(),
    releaseYear: a.integer(),
    director: a.string(),
    actors: a.string().array(),
    genre: a.string(),
    rating: a.float(),
  }).authorization((allow)=>[allow.owner(), allow.publicApiKey()]),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: "userPool",
  },
});
TypeScript

Important considerations:

To ensure smooth data integration between Amazon DynamoDB and Amazon OpenSearch, you’ll need to configure two critical features:

  1. Point-in-Time Recovery (PITR) PITR allows you to:
    1. Restore your table to any point in the last 35 days.
    2. Capture continuous backups of your data.
    3. Enable reliable data recovery and synchronization.
  2. DynamoDB Streams DynamoDB Streams provide:
    1. Real-time tracking of data modifications.
    2. Capture of all item-level changes.
    3. Essential support for streaming data to OpenSearch.

Navigate to your amplify/backend.ts file and add the following configuration:

// amplify/backend.ts
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
import { data } from "./data/resource";


const backend = defineBackend({
  auth,
  data,
});

// Get reference to the Movie table from Amplify resources
const movieTable =
  backend.data.resources.cfnResources.amplifyDynamoDbTables["Movie"];
// Enable Point-in-Time Recovery (PITR)  
movieTable.pointInTimeRecoveryEnabled = true;
// Configure DynamoDB Streams
movieTable.streamSpecification = {
  streamViewType: dynamodb.StreamViewType.NEW_IMAGE,
};
TypeScript

Step 3: OpenSearch Serverless collection Setup

In your code go to amplify/backend.ts file, create an Amazon OpenSearch serverless collection.

// amplify/backend.ts
import * as oss from "aws-cdk-lib/aws-opensearchserverless";
import { RemovalPolicy,Stack } from "aws-cdk-lib";

// Get the data stack
const openSearchStack = Stack.of(backend.data);
const region = openSearchStack.region;
const collectionName = "dynamodb-etl-collection";
const openSearchServerlessCollection = new oss.CfnCollection(
  openSearchStack,
  "OpenSearchServerlessCollection",
  {
    name: collectionName,
    description: "DynamoDB to OpenSearch Pipeline ETL Integration.",
    type: "SEARCH",
  }
);
// set removalPolicy to DESTROY to make sure the OpenSearch collection is deleted on stack deletion.
openSearchServerlessCollection.applyRemovalPolicy(RemovalPolicy.DESTROY);
TypeScript

3.1 Add OpenSearch as HttpDatasource to backend

To add the OpenSearch data source, add this code to amplify/backend.ts.

// amplify/backend.ts
/**
 * Create OpenSearch Serverless Data Source
 * This configures the OpenSearch endpoint as an HTTP data source for API operations
 */
const openSearchDataSource = backend.data.addHttpDataSource(
  "OpenSearchServerlessDataSource",
  openSearchServerlessCollection.attrCollectionEndpoint,
  {
    authorizationConfig: {
      // Region where the OpenSearch collection exists
      signingRegion: region,
      
      // Service name for request signing (aoss = Amazon OpenSearch Serverless)
      signingServiceName: "aoss",
    },
  }
);

/**
 * Configure Data Source IAM Permissions
 * Grants necessary permissions for OpenSearch operations using IAM policies
 */
openSearchDataSource.grantPrincipal.addToPrincipalPolicy(
  new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ["aoss:APIAccessAll"],
    resources: [
      // Grant access to the specific collection
      openSearchServerlessCollection.attrArn,
      // Grant access to all indices within the collection
      `${openSearchServerlessCollection.attrArn}/*`,
    ],
  })
);

const httpDataSourceRole = openSearchDataSource.grantPrincipal as iam.Role;
const httpDataSourceRoleArn = httpDataSourceRole.roleArn;
TypeScript

Step 4: Zero ETL Integration

4.1 Storage Configuration

Establish Storage to back up raw events consumed by the Amazon OpenSearch pipeline. Generate a file named amplify/storage/resource.ts and insert the provided content to set up a storage resource. Tailor your storage configurations to regulate access to different paths within your storage bucket.

// amplify/storage/resource.ts 
import { defineStorage } from "@aws-amplify/backend";

export const storage = defineStorage({
  name: "opensearchserverless",
});
TypeScript

In your amplify/backend.ts file, get the s3BucketArn value from storage resource as shown below. Additionally, configure an IAM role for the pipeline and assign the roles as indicated below. For further information on the required IAM roles, please refer to the Setting up roles and users documentation.

// amplify/backend.ts
import { storage } from "./storage/resource";

//Update backend variable by passing storage
const backend = defineBackend({
  auth,
  data,
  storage,
});

// Get the S3Bucket ARN
const s3BucketArn = backend.storage.resources.bucket.bucketArn;
TypeScript

For the Amazon S3 bucket, adhere to standard security practices: block public access, encrypt data at rest, and enable versioning. Note that versioning is enabled by default in Amplify.

4.2 IAM Role Configuration

Understanding IAM Role Setup

In this section, you’ll configure the security permissions that allow your OpenSearch Ingestion Service (OSIS) to securely access and transfer data between different AWS services. Navigate to your amplify/backend.ts file and add the following configuration:

Key Security Objectives:

  • Grant precise access to OpenSearch.
  • Secure DynamoDB and S3 data transfers.
  • Follow the principle of least privilege.

4.2.1 DynamoDB Policies

4.2.1.1 Export Job Policy

Grants permissions to describe tables, check backups, and initiate exports.

// amplify/backend.ts
const tableName = backend.data.resources.tables["Movie"].tableName;
const tableArn = backend.data.resources.tables["Movie"].tableArn;

// Create base role with OpenSearch Ingestion managed policy
const openSearchIntegrationPipelineRole = new iam.Role(
  openSearchStack,
  "OpenSearchIntegrationPipelineRole",
  {
    assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"),
    managedPolicies: [
      iam.ManagedPolicy.fromAwsManagedPolicyName(
        "AmazonOpenSearchIngestionFullAccess"
      ),
    ],
  }
);

/**
 * Policy for DynamoDB export operations
 * Allows:
 * - Describing table configuration
 * - Checking backup capabilities
 * - Initiating table exports
 */
const dynamoDBExportJobPolicy = new iam.PolicyStatement({
  sid: "allowRunExportJob",
  effect: iam.Effect.ALLOW,
  actions: [
    // Required for getting table metadata before export
    "dynamodb:DescribeTable",
    // Required for verifying point-in-time recovery status
    "dynamodb:DescribeContinuousBackups",
    // Required for initiating table export operation
    "dynamodb:ExportTableToPointInTime",
  ],
  resources: [tableArn],
});

openSearchIntegrationPipelineRole.addToPolicy(dynamoDBExportJobPolicy);
TypeScript

4.2.1.2 Export Job Monitoring Policy

Allows tracking of export job status using wildcard permissions for dynamic export IDs.

// amplify/backend.ts
import * as iam from "aws-cdk-lib/aws-iam";

/**
 * Policy for monitoring export job status
 * Allows:
 * - Checking status of ongoing export operations
 * - Monitoring export progress
 * Note: Uses wildcard for export operations as export IDs are dynamically generated
 */
const dynamoDBExportCheckPolicy = new iam.PolicyStatement({
  sid: "allowCheckExportjob",
  effect: iam.Effect.ALLOW,
  actions: ["dynamodb:DescribeExport"],
  resources: [`${tableArn}/export/*`],
});

openSearchIntegrationPipelineRole.addToPolicy(dynamoDBExportCheckPolicy);
TypeScript

4.2.1.3 Stream Access Policy

Provides real-time synchronization by granting permissions to read stream data and manage iterators.

// amplify/backend.ts
/**
 * Policy for DynamoDB Stream operations
 * Allows:
 * - Reading stream metadata
 * - Accessing stream records
 * - Managing stream iterators
 * Required for real-time data synchronization
 */
const dynamoDBStreamPolicy = new iam.PolicyStatement({
  sid: "allowReadFromStream",
  effect: iam.Effect.ALLOW,
  actions: [
    // Required for getting stream metadata
    "dynamodb:DescribeStream",
    // Required for reading actual records from the stream
    "dynamodb:GetRecords",
    // Required for managing stream position
    "dynamodb:GetShardIterator",
  ],
  resources: [`${tableArn}/stream/*`],
});

openSearchIntegrationPipelineRole.addToPolicy(dynamoDBStreamPolicy);
TypeScript

4.2.2 S3 Policy for Export Operations

Grants permissions for reading and writing data to an S3 bucket during DynamoDB exports.

// amplify/backend.ts
/**
 * Policy for S3 operations during export
 * Allows:
 * - Reading exported data
 * - Writing export files
 * - Managing multipart uploads
 * - Setting object ACLs
 * Scoped to specific export path for security
 */
const s3ExportPolicy = new iam.PolicyStatement({
  sid: "allowReadAndWriteToS3ForExport",
  effect: iam.Effect.ALLOW,
  actions: [
    // Required for reading exported data
    "s3:GetObject",
    // Required for handling failed uploads
    "s3:AbortMultipartUpload",
    // Required for writing export files
    "s3:PutObject",
    // Required for setting object permissions
    "s3:PutObjectAcl",
  ],
  resources: [`${s3BucketArn}`,`${s3BucketArn}/${tableName}/*`],
});
openSearchIntegrationPipelineRole.addToPolicy(s3ExportPolicy);
TypeScript

4.2.3 OpenSearch Policies

4.2.3.1 OpenSearch Collection Policy

Enables indexing, querying, and managing OpenSearch collections.

// amplify/backend.ts
/**
 * Policy for OpenSearch domain access
 * Allows:
 * - HTTP operations for indexing and querying
 * - Domain management operations
 * Includes permissions for both domain-level and index-level operations
 */
const openSearchCollectionPolicy = new iam.PolicyStatement({
  sid: "allowOpenSearchAccess",
  effect: iam.Effect.ALLOW,
  actions: [
    // Allows batch retrieval of collection information
    "aoss:BatchGetCollection",
    // Required for search, index, and administrative operations
    "aoss:APIAccessAll",
    // Needed for encryption and network policy validation
    "aoss:GetSecurityPolicy",
    // Required for initial setup and scaling operations
    "aoss:CreateCollection",
    // Needed for collection discovery and management
    "aoss:ListCollections",
    // Required for updating collection settings and configurations
    "aoss:UpdateCollection",
    // Needed for cleanup and resource management
    "aoss:DeleteCollection",
  ],
  resources: [
    openSearchServerlessCollection.attrArn,
    `${openSearchServerlessCollection.attrArn}/*`,
    `arn:aws:aoss:${region}:${openSearchStack.account}:collection/*`,
  ],
});
openSearchIntegrationPipelineRole.addToPolicy(openSearchCollectionPolicy);
TypeScript

4.2.3.2 Encryption Policy

Defines encryption settings for OpenSearch Serverless collections.

// amplify/backend.ts

import * as oss from "aws-cdk-lib/aws-opensearchserverless";

/**
 * Create Encryption Policy for OpenSearch Serverless
 * This policy defines how data is encrypted at rest within the collection
 */
const encryptionPolicy = new oss.CfnSecurityPolicy(
  openSearchStack,
  "EncryptionPolicy",
  {
    // Unique identifier for the encryption policy
    name: "ddb-etl-encryption-serverless",
    
    // Policy type - 'encryption' handles data encryption at rest
    type: "encryption",
    
    // Human-readable description for the policy
    description: `Encryption policy for ${collectionName} collection`,
    
    // Policy configuration
    policy: JSON.stringify({
      Rules: [{
        // Specify resource type this encryption applies to
        ResourceType: "collection",
        
        // Define which collections this policy affects
        Resource: [`collection/${collectionName}*`]
      }],
      // Use AWS owned KMS key for encryption
      // Alternative: Set to false and specify 'KmsKeyId' for customer managed key
      AWSOwnedKey: true
      
      /* Example of customer managed key configuration:
      AWSOwnedKey: false,
      KmsKeyId: 'arn:aws:kms:region:account:key/key-id'
      */
    })
  }
);

openSearchServerlessCollection.addDependency(encryptionPolicy);
TypeScript

4.2.3.3. Network Policy

Controls network access to the OpenSearch collection.

// amplify/backend.ts
/**
 * Create Network Policy for OpenSearch Serverless
 * This policy controls network access to the OpenSearch collection
 */
const networkPolicy = new oss.CfnSecurityPolicy(
  openSearchStack,
  "NetworkPolicy",
  {
    // Unique identifier for the network policy
    name: "ddb-etl-network-serverless",
    
    // Policy type - 'network' controls access patterns
    type: "network",
    
    // Human-readable description for the policy
    description: `Network policy for ${collectionName} collection`,
    
    // Network access rules
    policy: JSON.stringify([{
      Rules: [{
        // Specify resource type this network policy applies to
        ResourceType: "collection",
        
        // Define which collections this policy affects
        Resource: [`collection/${collectionName}`]
      }],
      // Allow public access to the collection
      // WARNING: For production, consider restricting access:
      AllowFromPublic: true

    }])
  }
);
// Add policy dependencies

openSearchServerlessCollection.addDependency(networkPolicy);
TypeScript

4.2.3.4 Data Access Policy

Grants permissions for creating, updating, and querying data in OpenSearch collections and indexes.

// amplify/backend.ts
/**
 * Creates a data access policy for OpenSearch Serverless
 * This policy defines who can access what within the collection and its indexes
 */
const dataAccessPolicy = new oss.CfnAccessPolicy(
  openSearchStack,
  "DataAccessPolicy",
  {
    name: `ddb-etl-access-policy`,
    type: "data",
    description: `Data access policy for ${collectionName} collection`,
    policy: JSON.stringify([
      {
        /**
         * Collection Level Permissions
         * - CreateCollectionItems: Required for adding new documents
         * - DeleteCollectionItems: Required for removing documents
         * - UpdateCollectionItems: Required for modifying documents
         * - DescribeCollectionItems: Required for reading documents
         */
        Rules: [
          {
            ResourceType: "collection",
            Resource: [`collection/${collectionName}`],
            Permission: [
              "aoss:CreateCollectionItems",
              "aoss:DeleteCollectionItems",
              "aoss:UpdateCollectionItems",
              "aoss:DescribeCollectionItems",
            ],
          },
          /**
           * Index Level Permissions
           * - ReadDocument: Required for search operations
           * - WriteDocument: Required for document updates
           * - CreateIndex: Required for index initialization
           * - DeleteIndex: Required for index cleanup
           * - UpdateIndex: Required for index modifications
           * - DescribeIndex: Required for index metadata
           */
          {
            ResourceType: "index",
            Resource: [`index/${collectionName}/*`],
            Permission: [
              "aoss:ReadDocument",
              "aoss:WriteDocument",
              "aoss:CreateIndex",
              "aoss:DeleteIndex",
              "aoss:UpdateIndex",
              "aoss:DescribeIndex",
            ],
          },
        ],
        Principal: [
          // ETL Pipeline role
          openSearchIntegrationPipelineRole.roleArn,
         
          // AppSync HTTPDataSource role
          httpDataSourceRoleArn,
        ],
      },
    ]),
  }
);
TypeScript

4.3 Create OpenSearch Service Pipeline

When using OpenSearch, defining an index template or mapping in advance provides powerful capabilities for precise data ingestion and search. This approach you can:

  • Set specific data types for each document field
  • Optimize search performance
  • Ensure consistent data structure.

The configuration is a data-prepper feature of OpenSearch. For specific documentation on DynamoDB configuration, refer to OpenSearch data-prepper documentation.

// amplify/backend.ts
// OpenSearch template definition  
interface OpenSearchConfig {
  tableArn: string;
  bucketName: string;
  region: string;
  tableName: string;
  pipelineRoleArn: string;
  openSearchEndpoint: string;
}

function createOpenSearchTemplate(config: OpenSearchConfig): string {
  const template = {
    version: "2",
    "dynamodb-pipeline": {
      source: {
        dynamodb: {
          acknowledgments: true,
          tables: [
            {
              table_arn: config.tableArn,
              stream: { start_position: "LATEST" },
              export: {
                s3_bucket: config.bucketName,
                s3_region: config.region,
                s3_prefix: `${config.tableName}/`,
              },
            },
          ],
          aws: { sts_role_arn: config.pipelineRoleArn, region: config.region },
        },
      },
      sink: [
        {
          opensearch: {
            hosts: [config.openSearchEndpoint],
            index: "movie",
            index_type: "custom",
            document_id: '${getMetadata("primary_key")}',
            action: '${getMetadata("opensearch_action")}',
            document_version: '${getMetadata("document_version")}',
            document_version_type: "external",
            flush_timeout: -1,
            aws: {
              sts_role_arn: config.pipelineRoleArn,
              region: config.region,
              serverless: true,
            },
          },
        },
      ],
    },
  };
  return YAML.stringify(template);
}


const openSearchTemplate = createOpenSearchTemplate({
  tableArn: tableArn,
  bucketName: backend.storage.resources.bucket.bucketName,
  region: region,
  tableName: tableName,
  pipelineRoleArn: openSearchIntegrationPipelineRole.roleArn,
  openSearchEndpoint: openSearchServerlessCollection.attrCollectionEndpoint,
});
TypeScript

Pipeline Configuration Breakdown

Source Configuration

  • Specifies DynamoDB as the data source
  • Defines target table for ingestion
  • Sets stream starting point
  • Configures S3 backup bucket
  • Assigns IAM role for pipeline permissions

Sink Configuration

  • Specifies OpenSearch domain cluster
  • Sets index name and type
  • Defines index mapping template
  • Configures document metadata handling
  • Sets bulk request size
  • Assigns IAM role for sink operations

Advanced Configuration Options

  • Sink configuration supports multiple indexes
  • Multiple tables can be configured through separate pipelines

For further guidance, please consult the pipeline section of the OpenSearch documentation.

Now, create the OSIS pipeline resource:

// amplify/backend.ts
import * as osis from "aws-cdk-lib/aws-osis";
import { LogGroup } from "aws-cdk-lib/aws-logs";

// Create a CloudWatch log group
const logGroup = new LogGroup(openSearchStack, "LogGroup", {
  logGroupName: "/aws/vendedlogs/OpenSearchServerlessService/pipelines/dev",
  removalPolicy: RemovalPolicy.DESTROY,
});


// Create an OpenSearch Integration Service pipeline
const cfnPipeline = new osis.CfnPipeline(
  openSearchStack,
  "OpenSearchIntegrationPipeline",
  {
    maxUnits: 4,
    minUnits: 1,
    pipelineConfigurationBody: openSearchTemplate,
    pipelineName: "dynamodb-integration",
    logPublishingOptions: {
      isLoggingEnabled: true,
      cloudWatchLogDestination: {
        logGroup: logGroup.logGroupName,
      },
    },
  }
);
TypeScript

After deploying your resources, you’ll want to confirm that your data pipeline is set up correctly before adding items to your Movie table. Here’s how to verify your pipeline configuration:

  • Open the AWS Management Console Navigate to the Amazon OpenSearch Service section.
  • Check the Pipelines Section Look for the pipeline you just created. Specifically, you’ll want to:
    • Confirm the pipeline name matches your configuration.
    • Verify the source and destination settings.
    • Check the current status of the pipeline.

The image shows a configured pipeline with details including pipeline name, status, source, and destination settings.

Figure 4 Ingestion Pipeline configuration

Figure 4: Ingestion Pipeline configuration

Step 5: OpenSearch Query Implementation

Create Resolver and attach to query

Let’s create the search resolver. Create a new file named amplify/data/searchMovieResolver.js and paste the following code. For additional details please refer to Amazon OpenSearch Service Resolvers.

// amplify/data/searchMovieResolver.js
export function request(ctx) {
  return {
    version: "2018-05-29",
    method: "GET",
    params: {
      headers: {
        "Content-Type": "application/json",
      },
      body: {
        from: 0,
        size: 50,
        query: { match: { title: ctx.args.title } },
      },
    },
    resourcePath: `/movie/_search`,
  };
}
/**
 * Returns the fetched items
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */

export function response(ctx) {
  const { statusCode, body } = ctx.result;
  if (statusCode === 200) {
    return JSON.parse(body).hits.hits.map((hit) => hit._source);
  }
}
TypeScript

Navigate to the amplify/data/resource.ts file and modify the schema to include the search query:

// amplify/data/resource.ts

   searchMovie: a
    .query()
    .arguments({
      content: a.string(),
    })
    .returns(a.ref("Movie").array())
    .authorization((allow) => [allow.publicApiKey()])
    .handler(
      a.handler.custom({
        entry: "./searchMovieResolver.js",
        dataSource: "OpenSearchServerlessDataSource",
      })
    ),
TypeScript

Step 6: Deployment and Verification

1. Open your terminal and run the following command to start your personal cloud sandbox:

npx ampx sandbox
Bash

2. Open a separate terminal and run the following command to start a local development server.

npm run dev
Bash

The Movie Search Solution interface displays movie search results, presenting a list of titles and descriptions retrieved from OpenSearch.

Figure 5 Web application interface for movie search, displaying a search input field and a list of movie results with titles and descriptions retrieved from OpenSearch

Figure 5: Interfacing with the Movie Search Solution

Clean up

Now that you’ve completed this walkthrough, be sure to delete the backend resources to avoid any unexpected costs. You can remove the app as outlined below and verify that all resources are terminated in the AWS Console.

npx ampx sandbox delete
Bash

Conclusion

Congratulations! You’ve successfully created a scalable, serverless movie collection search solution using AWS Amplify and Amazon OpenSearch Serverless. You’ve also validated the search results through an Amplify-powered frontend. This approach delivers efficient, cost-effective search capabilities with minimal infrastructure management. Explore further about Amplify by trying our Quickstart tutorial, and join our Community Discord to provide feedback or feature requests.

Anil Maktala

Anil Maktala is a Developer Experience Engineer (Solutions Architect) at AWS, bringing extensive experience in developing a diverse range of applications, products, and architectural solutions. He is deeply committed to assisting customers in designing scalable solutions tailored to their unique needs.