Serverless Framework: Deploy a REST API using AWS Lambda and DynamoDB

Michele Riso
ITNEXT
Published in
5 min readFeb 27, 2019

--

Goals

In my previous tutorial “Serverless Framework: Deploy an HTTP endpoint using NodeJS, Lambda on AWS” we have learnt how to create an AWS Lambda HTTP endpoint implemented in NodeJS with Express using the Serverless framework.

Today we are going to learn how to:

  • Create and deploy a REST API with 2 endpoints (GET, POST) using Express, Serverless and AWS API Gateway
  • Provision a DynamoDB table using the Serverless syntax
  • Connect to the DynamoDB using the AWS SDK
The architecture we aim to implement

What’s DynamoDB?

Amazon DynamoDB is a fully managed NoSQL database service that provides fast and predictable performance with seamless scalability. — docs.aws.amazon.com

DynamoDB logo

In a nutshell, DynamoDB is a serverless database for applications that need high performance at any scale.

So it sounds quite right to use a serverless DB with a serverless application!

Prerequisites

This tutorial takes into consideration that you already followed my previous tutorial and you are familiar with the basic concepts of the Serverless. If not or you want simply to refresh your mind, please have a look at “Serverless Framework: Deploy an HTTP endpoint using NodeJS, Lambda on AWS

Let’s start!

In my previous tutorial, It was fun to deploy an endpoint that was replying with an hard-coded “Hello Serverless!”, however, it wasn’t so valuable. Today we will see how to persist data and retrieve them dynamically. We will, therefore, create a DynamoDB within a User table in which we will store users by userId.

Configuring Serverless

As the first step, we need to configure Serverless in order to:

  • Give our Lambda read and write access to DynamoDB
  • Provision the User table in the resources section

Let’s copy the following code on the serverless.yml

service: serverless-aws-nodejs-dynamodbcustom:
tableName: 'users-table-${self:provider.stage}'
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: eu-central-1
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- { "Fn::GetAtt": ["UsersDynamoDBTable", "Arn" ] }
environment:
USERS_TABLE: ${self:custom.tableName}
functions:
app:
handler: app.server
events:
- http:
path: /
method: ANY
cors: true
- http:
path: /{proxy+}
method: ANY
cors: true
resources:
Resources:
UsersDynamoDBTable:
Type: 'AWS::DynamoDB::Table'
Properties:
AttributeDefinitions:
-
AttributeName: userId
AttributeType: S
KeySchema:
-
AttributeName: userId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:custom.tableName}

We can notice that we added 3 more sections:

  • Custom: it is a custom section that we can use it to save any kind of configuration that we aim to reuse. In particular, we are saving the User table name
  • iamRoleStatements: through this section, we can define the permissions that our lambda function needs to interact with the AWS DynamoDB. For the scope of this tutorial, we gave the Lambda admin access. In real scenario remember to use the “least privilege” principle and always give the minimum permissions required
  • resources: using this section we can define, using the CloudFormation syntax, the stack that AWS needs to use or creates if not existing. In particular, we are defining and provisioning the User table. If you are not familiar with CloudFormation please have a look at the official documentation on Amazon website. AWS CloudFormation

Edit the server logic

After having defined the AWS stack, we need to modify the NodeJS App in order to:

  • Implement GET/POST endpoint: we will use GET /user/{userId}to retrieve the user information and POST /userto create a new one
  • Interact with Dynamo DB

As the first step, we need to install the AWS SDK and bodyparser. AWS SDK is the official tool that enable us to interact with all the AWS services and components Bodyparser is used to parse the body of the HTTP requests

$ npm install --save aws-sdk body-parser

Now let’s copy the following code into the app.js:

// app.js 
const sls = require('serverless-http');
const bodyParser = require('body-parser');
const express = require('express')
const app = express()
const AWS = require('aws-sdk');
const USERS_TABLE = process.env.USERS_TABLE;
const dynamoDb = new AWS.DynamoDB.DocumentClient();
app.use(bodyParser.json({ strict: false }));// Create User endpoint
app.post('/users', function (req, res) {
const { userId, name } = req.body;
const params = {
TableName: USERS_TABLE,
Item: {
userId: userId,
name: name,
},
};
dynamoDb.put(params, (error) => {
if (error) {
console.log(error);
res.status(400).json({ error: `Could not create user ${userId}` });
}
res.json({ userId, name });
});
})
// Get User endpoint
app.get('/users/:userId', function (req, res) {
const params = {
TableName: USERS_TABLE,
Key: {
userId: req.params.userId,
},
}
dynamoDb.get(params, (error, result) => {
if (error) {
console.log(error);
res.status(400).json({ error: `Could not get user ${userId}` });
}
if (result.Item) {
const {userId, name} = result.Item;
res.json({ userId, name });
} else {
res.status(404).json({ error: `User ${userId} not found` });
}
});
})
module.exports.server = sls(app)

We have removed the generic endpoint and added 2 new ones:

  • POST /users that we can use to create a new user
  • GET /users/{userId that we’ll use to retrieve the user provided its userId

For simplicity, we haven’t implemented any safety checks on the parameters of the requests. However, in a real-world scenario please keep in mind that you need to check at least the type (e.g. if the userId is a String). In the POST request you can also purify the parameters to avoid the most common attacks (e.g. using DOM PURIFY)

Deploy!

Let’s deploy it again with the already well-known command sls deploy. The output is the same as before, however, this time Serverless has provisioned a DynamoDB as well.

Testing

Let’s try to create a new user using curl:

$ curl -X POST "https://xxxxxxxx.execute-api.eu-central-1.amazonaws.com/dev/users" -d '{"userId":"micheleriso","name":"Michele Riso"}' -H "Content-Type: application/json"{"userId":"micheleriso","name":"Michele Riso"}%#THE OUTPUT

and now retrieve it:

$ curl -X GET "https://xxxxxxxx.execute-api.eu-central-1.amazonaws.com/dev/users/micheleriso" -H "Content-Type: application/json"{"userId":"micheleriso","name":"Michele Riso"}% #THE OUTPUT

We have created a new user on DynamoDB! It’s really cool, isn’t? :-)

Conclusion

In this tutorial, we’ve learnt how to deploy a REST API Lambda function interconnect to DynamoDB using the Serverless Framework.

In the next tutorial, Serverless Framework: Warming up AWS Lambda to avoid “cold start”, we will see what is the “cold start” issue for AWS Lambda functions and how to tackle it using the Serverless Framework

Here the link to the bitbucket repo

--

--

Cloud Architect — Cloud Modernisation SME — Serverless SME — #AWS #Azure #Openshift linkedin.com/in/micheleriso