Getting started with Serverless microservices with AWS Lambda

In this article, you’ll learn how to build a node.js serverless microservice — deploy it on AWS Lambda with DynamoDb and place it behind AWS API Gateway.

We’ll be using the serverless framework — an open-source, application framework to easily build serverless architectures on AWS Lambda.

Prerequisites

  • Node.js installed
$ node -v
v4.3.2
  • Serverless framework installed — see here
$ serverless -v
1.10.2
  • Install AWS CLI & create IAM Credentials — For the purpose of this demo, we’ll be using an IAM user policy with `AdministratorAccess` (Don’t do that for your production apps)
$ cat ~/.aws/credentials
[serverless-demo]
aws_access_key_id=XXXXX
aws_secret_access_key=XXXXX

Please note the AWS custom profile name is set to `[serverless-demo]` here. We’ll be using this profile in the next step.

Create the application

Let’s go ahead and generate the application template.

$ mkdir serverless-demo
$ cd serverless-demo
$ serverless create — template aws-nodejs
Serverless: Generating boilerplate…
_______ __
| _ . — — -. — — . — . — . — — -. — — | . — — -. — — -. — — -.
| |___| -__| _| | | -__| _| | -__|__ — |__ — |
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v1.10.2
— — — -’
Serverless: Successfully generated boilerplate for template: “aws-nodejs”
Serverless: NOTE: Please update the “service” property in serverless.yml with your service name

You’ll see the following files in your working directory:

#AppRoot/handler.js — By default template will provide a `hello` function handler.

‘use strict’;
module.exports.hello = (event, context, callback) => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: ‘Go Serverless v1.0! Your function executed successfully!’,
input: event,
}),
};
callback(null, response);
// Use this code if you don’t use the http event with the LAMBDA-PROXY integration
// callback(null, { message: ‘Go Serverless v1.0! Your function executed successfully!’, event });
};

AWS Lambda can invoke this handler when the service executes your code. Handler signature has these three arguments.

  • event — AWS Lambda uses this parameter to pass in event data to the handler.
  • context — AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. Ex: AWS requestId, logs, timeout values etc.
  • callback — You can use the optional callback to return information to the caller, otherwise return value is null.

read more on function handler here.

#AppRoot/serverless.yml — Set the stage, profile, region parameters. The updated file should look like this.

service: serverless-demo
provider:
name: aws
runtime: nodejs4.3
stage: dev # Set the default stage used. Default is dev
profile: serverless-demo # The AWS credentials profile to use with this service
region: us-east-1
functions:
hello:
handler: handler.hello

This is more than enough to test our setup. Let’s deploy our first function to Lambda.

$ serverless deploy
Serverless: Packaging service…
Serverless: Uploading CloudFormation file to S3…
Serverless: Uploading function .zip files to S3…
Serverless: Uploading service .zip file to S3 (409 B)…
Serverless: Updating Stack…
Serverless: Checking Stack update progress…
………
Serverless: Stack update finished…
Service Information
service: serverless-demo
stage: dev
region: us-east-1
api keys:
None
endpoints:
None
functions:
hello: serverless-demo-dev-hello

Once completed, lambda function can be viewed from the aws console.

While the function doesn’t do anything other than printing a message, you can still test it by clicking the “test” action.

Creating the MicroService

Let’s create an example microservice, which has a RESTful CRUD API to manage book information. The book data will be stored on a DynamoDB table.

CREATE a new book — First we’ll need to add new handler to our application.

#AppRoot/books/create.js

‘use strict’;
const uuid = require(‘uuid’);
const AWS = require(‘aws-sdk’);
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.create = (event, context, callback) => {
const timestamp = new Date().getTime();
const data = JSON.parse(event.body);
const params = {
TableName: process.env.DYNAMODB_TABLE,
Item: {
id: uuid.v4(),
title: data.title,
price: data.price,
author: data.author,
createdAt: timestamp,
updatedAt: timestamp,
},
};
// write the book to the database
dynamoDb.put(params, (error) => {
// handle potential errors
if (error) {
console.error(error);
callback(new Error(‘Couldn\’t create the book item.’));
return;
}
// create a response
const response = {
statusCode: 201,
body: JSON.stringify(params.Item),
};
callback(null, response);
});
};

Let’s update the function definition in `serverless.yml` to point this new handler.

functions:
hello:
handler: handler.hello
booksCreate: # A Function
handler: books/create.create
events: # The Events that trigger this Function
— http:
path: books
method: post
cors: true

This setup specifies that the booksCreate function should be run when someone accesses the API gateway at books/ via a POST request. Further the HTTP endpoints defined here, have cross-site requests enabled for all source domains.

Creating the DynamoDB Table

One thing I really like with Serverless framework is, by simply adding the aws resource configuration to serverless.yml we can bring up other aws resources like dynamodb tables, s3 buckets etc. Serverless Framework translates all syntax in serverless.yml to a AWS CloudFormation template — automagically.

Add DynamoDB config to serverless.yml

service: serverless-demo
provider:
name: aws
runtime: nodejs4.3
stage: dev # Set the default stage used. Default is dev
profile: serverless-demo # The AWS credentials profile to use with this service
region: us-east-1
environment:
DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
iamRoleStatements:
— Effect: Allow
Action:
— dynamodb:Query
— dynamodb:Scan
— dynamodb:GetItem
— dynamodb:PutItem
— dynamodb:UpdateItem
— dynamodb:DeleteItem
Resource: “arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}”
resources:
Resources:
AppointmentsDynamoDbTable:
Type: ‘AWS::DynamoDB::Table’
DeletionPolicy: Retain
Properties:
AttributeDefinitions:
-
AttributeName: id
AttributeType: S
KeySchema:
-
AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:provider.environment.DYNAMODB_TABLE}
functions:
hello:
handler: handler.hello

You can learn more about AWS resources configurations here.

Deploy to AWS

During the deployment, it will setup a DynamoDb table, an API Gateway endpoint and your CREATE function will be added to Lambda functions list.

$ serverless deploy 
Serverless: Packaging service…
…………………………………..
Serverless: Stack update finished…
Service Information
service: serverless-demo
stage: dev
region: us-east-1
api keys:
None
endpoints:
POST — https://XXX.execute-api.us-east-1.amazonaws.com/dev/books
functions:
hello: serverless-demo-dev-hello
booksCreate: serverless-demo-dev-booksCreate

That’s it. You now have your API Gateway endpoint to invoke the REST API call. Let’s add some book data.

Your DynamoDb table is named as `serverless-demo-dev`

Monitoring

AWS Lambda automatically monitors functions and these metrics include total requests, latency, and error rates is available through AWS CloudWatch.

You can find the complete source code in GitHub
Happy coding ❤