Serverless Framework: Deploy a REST API using AWS Lambda and DynamoDB
By Michele Riso
Goals
In my previous tutorial “Serverless Framework: Deploy an HTTP endpoint using NodeJS, Lambda on AWS” we 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

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

In a nutshell, DynamoDB is a serverless database for applications that need high performance at any scale.
So it sounds quite right to also use a serverless DB within 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 a hard-coded “Hello Serverless!” message, however, this wasn’t very useful. Today we will see how we can persist data and retrieve it dynamically. We will, therefore, create a DynamoDB with a User
table in which we will store users
by userId
.

Configuring Serverless
Firstly, 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
Start by copying the following code into our 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: trueresources:
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}
Note that we added 3 more sections this time:
custom
: a custom section that we can use it to save any kind of configuration that we aim to reuse. In particular, we are saving theUser
table nameiamRoleStatements
: this section defines 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 requiredresources
: in this section we can define, using CloudFormation syntax, the stack that AWS needs to use or creates if it does not exist already. 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 our 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/user
to create a new one - Interact with Dynamo DB
As the first step, we need to install the AWS SDK and bodyparser
. The 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 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 then 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, we will see how to run a local Lambda function connected to a local DynamoDB!
Here is a link to the bitbucket repo
Originally published at itnext.io on February 27, 2019.