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

GoustoTech
Feb 27 · 5 min read

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
The architecture we aim to implement

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

AWS DynamoDB

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: 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}

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 the User table name
  • iamRoleStatements: 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 required
  • resources: 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 /userto 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.

Gousto Engineering & Data Science

Gousto Engineering Blog

GoustoTech

Written by

The official account for the Gousto Technology Team, a London based, technology-driven, recipe-box company.

Gousto Engineering & Data Science

Gousto Engineering Blog