Serverless + Express + DynamoDB (LocalStack)

Andrés Torres
6 min readApr 10, 2022

--

Introduction

In this tutorial, you will learn to build a REST app using serverless. It means “serverless” technically. What happens is that you stop using a physical server or one in the cloud identified by temporary and stateless containers where the application codes are executed.

In this tutorial we will work with Serverless, exposing microservices and using a localstack to simulate deployments on AWS.

Requirements

  • NodeJS 14
  • Docker
  • Visual Studio Code (or your preferred IDE)
  • Basic knowledge of DynamoDB

Configure environment

First, create the container app folder:

mkdir my-rest-app
cd my-rest-app

Now, install the serverless framework:

npm i serverless -g

To use localstack we will use Docker. If you don’t have Docker, you can install it following the instructions on Docker's official page. After installing it, to start our local environment, we need to create the docker-compose.yml file in the root project with the following template:

version: "3.8"services:  localstack:    container_name: "my-localstack"    image: localstack/localstack    ports:      - "127.0.0.1:4566:4566"    environment:      - AWS_DEFAULT_REGION=us-east-1      - EDGE_PORT=4566    volumes:      - "${TMPDIR:-/tmp}/localstack:/tmp/localstack"      - "/var/run/docker.sock:/var/run/docker.sock"

And execute:

docker-compose -up

You should see something like this in your console:

To verify the health of services go to your preferred browser and type http://localhost:4566/health?reload. The reload parameter allows us to get current values skipping cache. Once that we have localstack running we must set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables with test value (Remember these keys are ONLY for test purposes). To do this we must execute the following into a container (my-localstack):

docker exec -it my-localstack bash
aws configure --profile default

It’s all for now. Later we will make new configurations.

Creating a serverless app

With our infrastructure running it’s time to create our project using AWS and NodeJS templates.

serverless create --template aws-nodejs --path my-serverless-appcd my-serverless-appnpm init -f

Now you have a basic serverless app with the following scaffolding:

And our main file will look like this:

After it’s created we must install the necessary dependencies before starting.

npm install --save express serverless-http

To avoid deployments in a real AWS account it’s necessary to use ‘serverless-offline’ and ‘serverless-localstack’ as dev dependencies:

npm install --save-dev  serverless-offline serverless-localstack  serverless-dotenv-plugin

Due to the new rules for loading variables for .env files and setting up plugins, it’s necessary to add the following tags in the serverless.yml file.

service: my-serverless-appframeworkVersion: '2'useDotenv: truevariablesResolutionMode: 20210326plugins: - serverless-dotenv-plugin - serverless-localstack - serverless-offline

First steps with Express

In our way to learn to expose services using serverless, we need to configure our handler file (please delete old functions). This is a very simple application that returns “Hello World!” when a request comes in the GET path ‘/test’.

‘use strict’;const serverless = require(‘serverless-http’);const express = require(‘express’);const app = express();app.get('/test', function (req, res) {  res.send(‘Hello World!’);})module.exports.handler = serverless(app);

Modify the serverless.yml file to add the HTTP handler. In this case, allow All possible routes with the ‘ANY/’:

functions:  app:  handler: handler.handler  events:    - http:        path: /{proxy+}        method: ANY        cors: true

To deploy our function locally we need to execute:

sls offline

If everything goes well, you will see something like the following image:

Now our new server is deployed in http://localhost:3000 (you can modify the server port using a normal express configuration). If we type SERVER_URL + ‘/test’ in the browser the result must be something like this:

Setting Localstack in our project

At this point all our requests will fetch the resources from AWS, so we must redirect these requests to our Localstack infrastructure. To do this, we must add the following configuration in the serverless.yml file:

custom:  localstack:    stages:      - dev    host: http://localhost    edgePort: 4566    autostart: true    lambda:      mountCode: True

DynamoDB

Let’s go ahead with the next level. Our application will need to persist some data to be useful, so we need to add DynamoDB configuration in serverless.yml. To use other AWS resources, these must be added in the resource section (docker-compose.yml) using CloudFormation syntax.

Add dynamodb service in our localstack. To do this we need to add a new variable in docker-compose.yml to define the services to be mocked:

environment:  - AWS_DEFAULT_REGION=us-east-1  - EDGE_PORT=4566  - SERVICES=dynamodb

We’ll create a users table with email and set this field as the primary key. Remember that once we set a field like the primary key this can’t be changed. Another important concept with DynamoDB tables is that you can store records without an established data model. This is one difference between SQL and NoSQL databases. To create it, follow the next steps:

docker exec -it my-localstack bashaws dynamodb create-table --endpoint-url http://localhost:4566 --table-name users --attribute-definitions AttributeName=email,AttributeType=S --key-schema AttributeName=email,KeyType=HASH --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

To check if our table was created execute the following command in the console:

aws dynamodb list-tables --endpoint-url http://localhost:4566

Once the table is created it’s necessary to add the permissions to use the AWS resources in the serverless.yml file:

provider:  lambdaHashingVersion: 20201221  name: aws  runtime: nodejs14.x  stage: dev  iamRoleStatements:    - Effect: Allow      Action:        - dynamodb:Query        - dynamodb:Scan        - dynamodb:GetItem        - dynamodb:PutItem        - dynamodb:UpdateItem        - dynamodb:DeleteItem      Resource:        - { "Fn::GetAtt": ["UsersDynamoDBTable", "Arn" ] }

If you are not very used to working in the console, we can use dynamodb-admin, a plugin that allows us to have a GUI for our local dynamodb. To enable it we go to the docker-compose.yml file and add the local dynamodb configuration.

NOTE: In this case, we need to do a couple of modifications.

In this step, we created a container for the dynamodb-admin and we related it to our database in localstack.

Creating microservices

First, install the next dependency:

npm install --save aws-sdk

Go to the index and create the CRUD for our users table.

NOTE: In DynamoDB, PUT is a very special method because it creates a new item or replaces an old one with a new one based on the primary key.

Bonus: Deployments in AWS and Domain Manager

Before deploying our application with AWS, we need to skip the node_modules folder to make our function light. In serverless.yml file add:

package:  exclude:    - node_modules/**

The exclude tag indicates the folders or files to ignore and the includes tag, the files to be included.

To deploy our application with AWS just execute ‘sls deploy’ (You need a real AWS account to do this). If everything works you should see something like this:

Unfortunately, using this endpoint or sharing it can be tedious as it is not very explicit. It’s not even useful because if you do another deployment the endpoint will be updated and you will have to change it for the new one.

To avoid this we will use Serverless Domain Manager which allows us to deploy our functions under an existing domain in route 53. This process is easy. Just install the dependency:

npm install serverless-domain-manager --save-dev

Add it to our plugin section in the serverless.yml file.

plugins:  - serverless-dotenv-plugin  - serverless-localstack  - serverless-offline  - serverless-domain-manager

Finally, add the configuration in the custom tag in the serverless.yml file.

custom:
localstack:
stages: - dev host: http://localhost edgePort: 4566 autostart: true lambda: mountCode: True customDomain: domainName: 'your-domain.com' basePath: 'users' # This will be prefixed to all routes stage: ${self:provider.stage} createRoute53Record: true

NOTE: The domain must be registered or associated with Route 53 in AWS.

🎖 Congratulations 🎖 You have tested your microservices with Localstack and have deployed them on AWS!

If you need the code you can find it here :

--

--