Serverless + Express + DynamoDB (LocalStack)
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 :