Ionic 4 + AppSync: Add ElasticSearch to GraphQL API - Part 6

In this tutorial we're going to configure Amazon ElasticSearch Service to index the cards in our app so we can perform searches on them through our GraphQL API.

As you've seen in previous parts, adding AWS backend resources to our GraphQL API is very easy and it's no different for ElasticSearch!

This tutorial is split up into these parts:
Part 1 - Introduction to GraphQL & AWS AppSync
Part 2 - Create a GraphQL API
Part 3 - Add Ionic pages and GraphQL queries
Part 4 - Use GraphQL mutations in Ionic
Part 5 - Set up Authentication with Amazon Cognito
Part 6 - Add ElasticSearch to GraphQL API (this post)
(more parts will be added later)

You can find the source code for this tutorial on my Github.

What is ElasticSearch?

ElasticSearch is an open-source search and analytics engine which enables you to do different types of searches on your data. It's built to handle big data and is blazing fast!

In Part 4 of this tutorial I showed you how to search for cards through the GraphQL API. The query that we used to search for specific cards is just scanning the entire DynamoDB table, retrieving all data and then filtering by our keyword.

Now imagine this table has millions of items... Ooops! 😱 But don't worry, we can send all the card data to ElasticSearch where it will be indexed for searching.

Amazon has a managed service implementation for ElasticSearch, so we don't have to worry about maintaining ElasticSearch ourselves. All we have to do is configure our AWS backend to stream the data from the DynamoDB table to ElasticSearch where it will be indexed.

By now you probably won't be surprised to find out that it's very easy to do this with Amplify, so let's go ahead and see how that's done!

Side note: Amazon ElasticSearch Service is free to get started with. Check the pricing.

Configure ElasticSearch

All we need to do to configure ElasticSearch is add the @searchable directive to the Card type in our GraphQL schema.

type Card @model @searchable @auth(rules: [{ allow: owner }]) {  
  id: ID!
  question: String!
  answer: String
  deck: Deck! @connection(name: "DeckCards")
}

The @searchable directive is responsible for streaming the data of an @model object type to ElasticSearch and configuring search resolvers for searching that data.

Now we need to push this change to AWS so it can set up the DynamoDB stream to send data to ElasticSearch.

$ amplify push

The CLI will ask you some questions about code generation, you can choose as below.

? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types? This will overwrite your current graphql queries, mutations and subscriptions Yes

It might take a while for the backend to create the resources, but when it's done, you should have a new query on your GraphQL API.

You can check the queries in this file: src/graphql/queries.graphql. It should now contain the SearchCards query.

query SearchCards(  
  $filter: SearchableCardFilterInput
  $sort: SearchableCardSortInput
  $limit: Int
  $nextToken: Int
) {
  searchCards(
    filter: $filter
    sort: $sort
    limit: $limit
    nextToken: $nextToken
  ) {
    items {
      id
      question
      answer
      deck {
        id
        name
      }
    }
    nextToken
  }
}

We can now use this query in our Ionic app to search for cards.

Modify Home Page

Let's use an Ionic Alert to prompt our users to input search keywords when they want to start the quiz.

Screenshot Alert

In home.page.ts we are going to import Ionic's AlertController and add it to the constructor.

import { ModalController, AlertController } from '@ionic/angular';

constructor(private modalController: ModalController,  
            private router: Router,
            private alertController: AlertController) {
}

Next, add the method promptQuiz to ask the user to input search keywords to filter the cards for the quiz.

async promptQuiz() {  
    const alert = await this.alertController.create({
        header: 'Which cards?',
        inputs: [
        {
            name: 'filter',
            type: 'text',
            placeholder: 'Type a word to filter the cards'
        }
        ],
        buttons: [
        {
            text: 'Cancel',
            role: 'cancel',
            cssClass: 'secondary'
        }, 
        {
            text: 'Start Quiz',
            handler: async data => {
                await this.startQuiz(data.filter);
            }
        }
        ]
    });

    await alert.present();
}

We need to modify startQuiz to send the filter to the modal.

async startQuiz(filter) {  
    const modal = await this.modalController.create({
        component: QuizPage,
        componentProps: { filter }
    });
    return await modal.present();
}

And finally, modify home.page.html to call promptQuiz when the Quiz Me button is clicked.

<ion-button fill="outline" color="light" (click)="promptQuiz()">  
   Quiz Me
   <ion-icon slot="end" name="albums"></ion-icon>
</ion-button>  

Modify Quiz Page

Add an import for NavParams and add it to the constructor.

import { APIService } from "../api.service";

constructor(  
    public modalController: ModalController,
    private navParams: NavParams,
    public apiService: APIService
) { }

Next, change the call to apiService to call the new SearchCards method and pass in the filter.

ngOnInit() {  
    this.apiService
        .SearchCards({
            question: {
                match: this.navParams.get('filter')
            }
        })
        .then(query => this.cards = query.items);
}

That's it, we are now using ElasticSearch to search through our cards.

Test app

Start the app, add some new cards to it and test if the filtering works.

$ ionic serve

You can find the source code for this tutorial on my Github.

What's next?

I haven't decided yet what to include in the next tutorial, so if you have anything specific you'd like me to look at, let me know in the comments.

If you'd like to know more about databases on AWS and which one to use when, I highly recommend you watch these talks from the recent AWS re:Invent conference:
Databases on AWS: The Right Tool for the Right Job
Building with AWS Databases: Match Your Workload to the Right Databas