Build a Geolocation API using AWS Lambda and MaxMind
To build our API we will be using AWS Lambda, API Gateway and MaxMind DB. We’ll also need Serverless framework for developing and deploying our API.
To get started make sure you have Serverless framework installed, you can do that by just running the following command in terminal.
npm install -g serverless
Once we’ve serverless installed, we can create a new folder for our project and then inside the folder we can run the serverless create
command for initializing our project.
➜ geo-api serverless create --template aws-nodejs
Serverless: Generating boilerplate...
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v1.27.2
-------'Serverless: Successfully generated boilerplate for template: "aws-nodejs"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name
Once the command is run successfully you should see what is shown above. Inside the folder, you can see that Serverless has created two files, handler.js
and serverless.yml
. Now that we’re ready to get our hands dirty, go ahead open the folder inside an editor and start coding.
handler.js
will contain the actual javascript code that will be executed during lambda invocation. serverless.yml
is the configuration file that basically defines your service. You can read more about the core concepts here.
The first step would be configuring our service using the serverless.yml
file. Here is how it show look:
service: geolocation-apiprovider:
name: aws
runtime: nodejs8.10 stage: dev
region: us-east-1functions:
fetchLocationData:
handler: handler.fetchLocationData
events:
- http:
path: location
method: get
cors: true
- service — a unique name for the micro service, which will be used while creating the cloud formation stack, lambda functions, API gateways etc.
- provider — the cloud provider that we will be using is
aws
and we will be running our code onnodejs8.10
runtime, which is the latest one. - stage — our environment name
- region — AWS region to which we will be deploying
- functions — list of functions in our service.
In the above file, we’ve defined a function fetchLocationData
and the handler or the function that is supposed to be executed is inside the file handler.js
and the function name is fetchLocationData
. Also, we’ve added an API gateway trigger event for this lambda function. The path is /location
and the method is GET
, we’ve also enabled CORS for this endpoint. You can read more about API gateway event here.
Since we updated the runtime to nodejs8.10
we need to make some changes to the handler also. The new runtime natively supports async/await
, so we can make use of that to make our code more concise and cleaner. You can read more about the new runtime here. Our updated code will be:
"use strict"module.exports.fetchLocationData = async event => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: "Go Serverless v1.0! Your function executed successfully!",
input: event
})
}return Promise.resolve(response)
}
As of now our code basically doesn’t nothing, it will just send a 200 OK
response with a message and our input event. To see it in action lets deploy our service using the command sls deploy
➜ geo-api sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (338 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: geolocation-api
stage: dev
region: us-east-1
stack: geolocation-api-dev
api keys:
None
endpoints:
GET - https://6ny7ze91yk.execute-api.us-east-1.amazonaws.com/dev/location
functions:
fetchLocationData: geolocation-api-dev-fetchLocationData
After finishing the deployment you can see our new API URL and on opening that you should see our code come alive!
Next we need to download GeoLite2 database from MaxMind https://dev.maxmind.com/geoip/geoip2/geolite2/, and we will also be using the maxmind
npm package to query the GeoLite2 database. You can see the finished handler.js
file in this github repository https://github.com/arjunkomath/serverless-geolocation-api, the code is fairly simple and easy to understand. Note that, in order to query the MaxMind database, we need the IP address of the client. We get this from the event object that is available inside our lambda function. The source IP of all request is available by default in the API gateway event that triggers our lambda. Alternatively, we can send the IP address as a parameter to our request, but we will just stick to the current implementation for now.
In case you have any doubts regarding the code, feel free to get in touch. And as always feel free to contribute to the project, PRs are always welcome!
Finally we need to add a plugin to ensure that our Lambdas never go cold. A cold start occurs when an AWS Lambda function is invoked after not being used for an extended period of time resulting in increased invocation latency. To avoid this we use a plugin — Serverless WarmUP Plugin (https://github.com/FidelLimited/serverless-plugin-warmup). We need to make few changes in our serverless.yml
and handler.js
file to support warmup plugin. Our updated serverless.yml
file will be:
service: geolocation-apiprovider:
name: aws
runtime: nodejs8.10
memorySize: 256
iamRoleStatements:
- Effect: 'Allow'
Action:
- 'lambda:InvokeFunction'
Resource:
- Fn::Join:
- ':'
- - arn:aws:lambda
- Ref: AWS::Region
- Ref: AWS::AccountId
- function:${self:service}-${opt:stage, self:provider.stage}-* stage: dev
region: us-east-1plugins:
- serverless-plugin-warmupfunctions:
fetchLocationData:
handler: handler.fetchLocationData
warmup: true
events:
- http:
path: location
method: get
cors: true
We’ve added an IAM Role that allows the warmup plugin to invoke our lambda functions, also added serverless-plugin-warmup
to plugins and finally set warmup
as true for our fetchLocationData
function. Now you can deploy and start testing the API :)