Finding the nearest locations around you using AWS Amplify — Part 1

Enable GraphQL distance-aware searches with @searchable

This is the first article of a series of articles that provides a comprehensive step by step guide to enable distance-aware searches in your full-stack serverless applications using AWS Amplify and AWS AppSync.

In this article, you will learn how to enable GraphQL distance-aware searches starting by making changes to your GraphQL Schema. Next, we will introduce some essential concepts around geolocation and a detailed explanation of the inner workings of the @searchable GraphQL transform.

The whole series includes:

Please let me know if you have any questions or want to learn more at @gerardsans.

Before you continue

In case you are not familiar with AWS Amplify or AWS AppSync, I recommend you the following articles:

Step 1: change your GraphQL Schema to include location coordinates

The first thing you need to do, is to add geolocation fields and types to your GraphQL Schema. This will allow you to store geolocation data, run distance-aware searches and display the results in a map like the one below.

LondonCycles app showing live status for nearby Santander Cycles bike stations.

The blue circle represents the user current location, while orange circles correspond to bike stations from Santander Cycles within 500 meters. The figures inside show each station current capacity using open data from Transport for London unified API. In Part 3 of this series we will build the client UI for LondonCycles app.

A GraphQL Schema allows you to clearly specify your application data types, its relationships, how to query data, make changes and notify subscribers. Learn the basics of GraphQL at graphql.org.

Before doing these changes, let’s introduce few concepts to better understand the coming steps.

Introduction to geolocation

Geolocation is an estimation of a geographic location with the purpose to position an object in a map, usually in the shape of a marker. One way to do this is by providing its longitude and latitude.

There are many formats available to represent and store geographic locations. For example, GPS uses the WGS84 coordinates system as decimal degrees. Find few examples of popular formats below:

(-0.109971, 51.529163)   // GPS/WGS84 (long, lat) 
[-0.109971, 51.529163] // GeoJSON [long, lat]
51°31'44''N, 0°6'35''W // DMS Degrees Minutes Seconds
gcpvjkxgjkpwmsp // Geohash
(-0.1099 , 51.5291) // Use 4 digits for 10m precision
(-0.10997 , 51.52916) // Use 5 digits for 1m precision
(-0.109971, 51.529163) // Use 6 digits for .1m precision

When using a coordinates system the world can be overlapped with a grid system like the one shown below. Highlighted coordinates correspond to a location in London at [-0.109971, 51.529163].

Its origin, at (0, 0) is a location slightly below Ghana aka Null Island.

Note: remember to double check the order for longitude and latitude when using location coordinates in WGS84 or GeoJSON as is a common mistake.

Adding geolocation to your GraphQL Schema

We have seen, that in order to position an object in a map, we need to provide its location coordinates. Add a location field to an existing type using @model in your schema. For our example, using bike stations, we did the following changes to the BikePoint type:

type BikePoint @model {   
location: Location!
}
type Location {
lat: Float!
lon: Float!
}

Types using @model are connected to a table in Amazon DynamoDB. Learn more about how it works at @model GraphQL transform.

As a good practice, we included a separate Location type to store the latitude and longitude coordinates using the GraphQL scalar type Float. Float represents a signed double-precision floating-point value. As all these fields are mandatory we added an exclamation mark to each one.

Learn the basics of GraphQL including Schema definition at graphql.org.

Step 2: enable distance-aware searches using @searchable

In this step, we are going to take advantage of @searchable GraphQL transform provided by AWS AppSync to enable advanced searches including distance-aware searches.

Behind the scenes, AWS AppSync will automatically provision and configure a fully featured search architecture as shown in the diagram below.

GraphQL mutations mapping while using @searchable GraphQL transform

After adding @searchable to your type, every time a client runs a GraphQL mutation (create, update or delete) it will be indexed in Amazon Elasticsearch. This is possible as DynamoDB Streams are automatically enabled by AWS AppSync for the underlying table connected by @model.

DynamoDB Streams capture modifications in your Amazon DynamoDB table used by @model and store them as events in a log for up to 24 hours. A custom AWS Lambda function will access this log and process the data in near-real time mapping each event (insert, modify or remove) to a corresponding Amazon Elasticsearch operation.

At this point the location information will be kept in sync as we add, remove or update our data which is neat.

Find below, an overview of all the steps involved in creating a new record for our BikePoint type, and how the data is transformed, from each stage to the next, matching the steps in the previous diagram.

////////////////////////////////////////////////////////////////////
// Step 1: run GraphQL mutation (simplified)
$input = {
"id": "BikePoints_1",
"name": "River Street , Clerkenwell",
"location": {
"lat": 51.529163,
"lon": -0.109971
},
}
mutation createBikePoint($input: CreateBikePointInput!) {
createBikePoint(input:$input) {
id name location { lat lon }
}
}
////////////////////////////////////////////////////////////////////
// Step 2: run AWS AppSync Resolver DynamoDB mapping (simplified)
{
"version": "2017-02-28",
"operation": "PutItem",
"id": "BikePoints_1",
"attributeValues": {
"__typename": "BikePoint",
"name": "River Street , Clerkenwell",
"location": {
"lat": 51.529163,
"lon": -0.109971
},
}
}
////////////////////////////////////////////////////////////////////// Step 3: process DynamoDB Stream Event Record (simplified){
"eventID": "...",
"eventName": "INSERT",
"eventSource": "aws:dynamodb",
"awsRegion": "us-west-1",
"dynamodb": {
"Keys": {
"id": { "S": "BikePoints_1" },
},
"NewImage": {
"name": { "S": "River Street , Clerkenwell" },
...
},
},
"eventSourceARN": "arn:aws:...table/BikePoint/stream/..."
}
////////////////////////////////////////////////////////////////////
// Step 4: run Elasticsearch document operation (simplified)
PUT /bikepoint/_mapping/doc
{
"properties": {
"id": "BikePoints_1"
"name": "River Street , Clerkenwell",
"location": {
"lat": 51.529163,
"lon": -0.109971
}
}
}

Now that we know a bit more about how @searchable works internally, let’s go ahead and make the necessary changes.

Adding searchable GraphQL transform

Add the searchable transform to the type you want to enable geolocation searches as shown below

type BikePoint @model @searchable {   
location: Location
}
type Location {
lat: Float!
lon: Float!
}

We added @searchable to our type. Remember that @searchable requires our type to use @model so is already connected to an Amazon DynamoDB Table.

Pushing changes to the cloud

Run the following command to enable searches for your GraphQL type:

Important: enabling searches in your application will incur in costs as shown in the table at the end of this article. As part of the Free Tier you are able to use searches for at least 30 days and up to 1GB data transfer with no cost.

amplify push

As part of this command an Amazon EC2 instance will be provisioned to run Amazon Elasticsearch with default type: t2.small.elasticsearch. You can change the default instance to meet your requirements. Evaluate carefully as each instance incurs in different costs. Pricing details are available at the end of this article. AWS Free Tier offers great pricing for t2.micro.elasticsearch if available in your AWS Region.

Once this command finishes, the search architecture we introduced earlier will be provisioned and ready to use. From this moment, any mutations run by clients will be automatically indexed to Amazon Elasticsearch.

Important: DO NOT update records with location information just yet. Wait until completing Part 2 of this series.

Default search query: SearchBikePoints

AWS AppSync creates a default search query for you so you can try out some searches for your type. These searches automatically support the following GraphQL types: ID, String, Int, Float and Boolean.

To find the default search query open src/graphql/queries.graphql in your Amplify project. In our example, AWS AppSync created SearchBikePoints. Find below a simplified version:

query SearchBikePoints(...) 
{
searchBikePoints(...) {
items {
id name location { lat lon }
}
nextToken
total
}
}

SearchBikePoints query already supports advanced text searches like filtering, sorting and pagination. See below an example filtering results using a pattern match, sorting by the name field in ascending order and limiting results to two items. All of these features and more are generated by default by AWS AppSync. Navigate to learn more about all possible search options.

You can test your default search query using amplify console api from the command line and selecting GraphQL.

The main limitation, from the default query, is that it can not run distance-aware searches; or find items nearby automatically calculating the distance from the users location.

In order to solve this limitation, we will create a custom query to leverage our location data. We will see how to do that in Part 2 of this series.

Conclusion

Well done! You have learnt how to enable GraphQL distance-aware searches using @searchable and a bit more of how it works behind the scenes. Try it out!

In Part 2, we will cover how to create a custom GraphQL distance-aware search query using AWS AppSync.

Ready to code?

You don’t have an AWS Account? Use the next few minutes to create one and activate the free plan for a whole year. Follow steps at AWS knowledge center.

Once your free plan expires for Amazon Elasticsearch service, you are charged only for instance hours, Amazon EBS storage, and data transfer. See the pricing table overview:

Free tier for a new AWS Account. Check out latest pricing.

Thanks for reading!

Have you got any questions regarding this article AWS Amplify or AWS AppSync? Feel free to ping me anytime at @gerardsans.

My Name is Gerard Sans. I am a Developer Advocate at AWS Mobile working with AWS Amplify and AWS AppSync teams.

GraphQL is an open-source data query and manipulation language for APIs.

Mapbox is an American provider of custom online maps.

Angular is an open source project from Google.

Santander Cycles is a public bicycle hire scheme in London.

Transport for London (TfL) is a local government body responsible for the transport system in Greater London, England.

Developer Advocate | ex @AWSCloud | Just be AWSome | MC Speaker Trainer Community Leader | @fullstackcon @ReactiveConf @ngcruise @UphillConf