Caffeinate me! Build a serverless app to find the nearest Starbucks.
Uniting Starbucks and serverless, this project uses VueJS and geospatial queries on DynamoDB to provide a quick coffee fix.
If you build mobile apps or any location-aware software, you’ll quickly come across a difficult problem — how to locate the ‘nearest’ or ‘furthest’ of something.
Fortunately, there is a public domain geocoding solution to this problem called geohashing. In this article on A Cloud Guru, I previously explained how to use this to build out a back-end geo-search solution. In this project, we are going to use that approach to create a web app front-end that can help you find all the Starbucks locations within walking distance.
A quick recap of the Geo Library for DynamoDB
Geohashing works by dividing the world into a series of nested grids — each character of the hash identifies a square and reducing the searching space substantially. With just a 5-character string, we can identify a 5km x 5km square on planet Earth.
For proximity searches, it may be required to search more than one square so the algorithm efficiently handles finding neighboring squares. The main decision in building a geospatial database is determining the length of hash (i.e. the square size) to balance the number of squares searched versus the number of results in each square.
Okay, enough theory — all you really need to know is that an algorithm exists to solve this tricky problem and it’s been neatly bundled into an NPM package. The Geo Library for DynamoDB will calculate the hashes, manage the underlying DynamoDB table, and support queries against the list of locations.
Importing a list of Starbucks locations
Proof that the answer to almost any questions lies in a git repo somewhere, I found a list of US Starbucks locations (there are 8,902 in this list in case you’re wondering). Our first task is to import this into DynamoDB using the Geo Library — and before we can do that, we need to initialize the table:
This boilerplate sets a hash length of 5 characters and a table name, and lets the library do the rest — running this function takes a few seconds since the code waits until the creation process is complete.
Now we are ready to load in the locations. The loadTable function has two parts — the first step is loading the list from the JSON file into JSON structure ready for the Geo Library:
Once this is prepared, we use the library to load the data into DynamoDB in batches:
Once the function is finished, you can see the results either in the AWS console or using the CLI command:
aws dynamodb describe-table --table-name askJames-wheresStarbucks
We can then test the query function to see how it performs with the following code, running
node query from the console:
The console output will shows there are 10 Starbucks locations within 1 km of the latitude and longitude position provided:
Deploying the serverless back-end
Download the repo for the back-end service and run
npm install to install the required packages. From the same directory, running
node index will allow you to run the Express server on your local machine to see how it works.
I recommend downloading Postman to test the API — the simple interface makes debugging a breeze. From Postman, if you POST to http://localhost:3001/ with the raw body shown below, you’ll get a response from the Geo Library:
The code in index.js is only a wrapper for the query we tested earlier, extracting the latitude and longitude parameters provided and returning the response. At the moment you are testing locally against the DynamoDB table in the cloud — let’s now push our function to Lambda.
Before publishing, copy the ARN of the DynamoDB table you created earlier into the serverless.yaml file in the root directory. The permissions set here are overly broad, so you should paste the ARN into line 26 to ensure the function can only access this one table.
To deploy, run
sls deploy from the command line and take a note of the resulting endpoint. If you paste the endpoint into Postman and rerun the test, you will get the same query results but this time the function is running on Lambda:
Building the front-end
There are three steps to set this up:
- Clone the GitLab repo and run
npm installto set up the project in the same directory.
- You’ll need a Google Maps API key, which you can request here. Paste this key into main.js (line 9). If you are planning to publish this publicly (like I did), go back after deployment and restrict your key to only the domain name you are using (this will prevent API quota theft).
- Paste your API Gateway endpoint from earlier into App.vue (line 56).
We are using Vue to create an interactive front-end with the minimum amount of work. All of the custom code lives in src/App.vue, but let me introduce the key parts. The entire layout of the front-end looks like this:
This uses the Vue Google Maps package to draw the map and manage the markers. There are three Google Maps components used here:
- The map itself (GmapMap): I set the zoom to 15 (out of 20), the style to ‘terrain’ (avoid those hills!), and most importantly told it to fire the updateCenter function when clicked.
- The markers (GmapMarker): these are the red pins showing the locations. When the markers array is changed, this code will iterate through the list and put new markers on the map.
- A circle (GMapCircle): a light blue circle shows the 1 kilometer radius around where the user clicked, highlighting the scope of the current search.
When the user clicks the map, it will call the updateCenter method shown below:
This code uses the Axios library to call the API Gateway endpoint and place the location results into the Google Map markers. Whenever these markers change, the Vue template is bound to this array so knows to update the display — there’s no need to call for a refresh.
You can run this code locally by typing
npm run serve from a command line in the same directory and visiting http://localhost:8080/.
When you are ready to publish to production, run
npm run build to create the distribution files (in the dist folder). If you copy these to an S3 bucket set to serve a static website, you will have deployed a fully serverless solution.
Time for coffee
Although this tutorial was designed to find all the Starbucks locations within 1km, it could be used to find the locations of anything within a certain distance. For example, where are my customers a driver can reach within 2 miles? Or which photos were taken within 5 miles?
DynamoDB provides constant performance under scale and excels at this kind of application, while the Geo Library abstracts the complexity of managing geohashing. All that remains now is relaxing with a Starbucks and finding my nearest location thanks to https://caffeinate.jbes.dev.
Thanks for following along. Let me know if you have any questions in the comments!