Slspress

alt text alt text

Slspress is a library that was designed to abstract some of the boilerplate away from Serverless application creation to allow you just to focus on the API's that you want to build. Given the popularity and ease of use of the Express framework we wanted to build something that allowed one to build Serverless applications in a similar way. Slspress does not aim to emulate the express APIs but to use a similar approach when wiring up endpoints and middleware etc. This article will show you how to create a simple CRUD api backed by dynamoDB using slspress.

 

Simple CRUD example

alt text

You will require the following additional AWS permissions:

  • dynamodb:CreateTable
  • lambda:CreateEventSourceMapping

 

Note there is no security in the example. In the real world you would need to add a security layer. See this guide on to how to do this with slspress and auth0

To start, let's create a collection table SlspressTestItem on DynamoDB:

aws dynamodb create-table --table-name SlspressTestItem --attribute-definitions AttributeName=Id,AttributeType=S --key-schema AttributeName=Id,KeyType=HASH --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

Initiate npm and install slspress.

  npm init
  npm install --save slspress

 

Lets create a function to write to the table. Add the following config and code to your serverless.yml and a file called app.js.

There is no need to add a dependency on the aws-sdk, it is provided by default.

  functions:
    itemApi:
      handler: app.handle
      events:
        - http:
            path: items/{id}
            method: post
            cors: true
const { create, jsonMiddleware } = require('slspress');
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient();

const app = create();

app.on('handle')
    .middleware(jsonMiddleware)
    .post('/items', (req, res) => {
        const params = {
          TableName: 'SlspressTestItem',
          Item: {
              Id: req.event.body.id,
              Name: req.event.body.name
          }
        }
        return docClient.put(params, err => err ? res.handleError(err) : res.created());
    });

module.exports = app.export();

Deploy your application

  serverless deploy

 

Before your lambda can connect to DynamoDb you will need to grant it access. Create the following policy file for your lambda as policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
              "dynamodb:GetItem", 
              "dynamodb:DeleteItem", 
              "dynamodb:PutItem", 
              "dynamodb:UpdateItem", 
              "dynamodb:Query"
            ],
            "Effect": "Allow", 
            "Resource": [
              "arn:aws:dynamodb:*:*:table/SlspressTestItem"
            ]
        }
    ]
}

 

Locate the role that the Serverless framework automatically created for your lambda function and grant it read/write access to the new table by running the following aws commands:

aws iam list-roles | grep lambdaRole
aws iam put-role-policy --role-name ROLE_FROM_ABOVE_COMMAND --policy-name LambdaDynamoPolicy --policy-document file://policy.json

 

Test the endpoint using curl (or your favorite rest client) and then get the row back out via the DynamoDB CLI.

curl -H "Content-Type: application/json" -X POST -v -d '{"Id": "1234", "name":"New Item!"}' https://{your-api-url}.execute-api.us-east-1.amazonaws.com/dev/items/1234
aws dynamodb scan --table-name SlspressTestItem

 

You should be be able to see the object in DynamoDB.

There are a couple of things to note with the above code.

  1. The middleware call is adding some built in middleware that ensures that requests are parsed as json and responses returned as json.
  2. In this example we are adding all of the code inline in the app.js file but with a bigger application it usually makes sense to split it out into separate files that app.js requires. Then app.js becomes a list of all of your routes.

Add the following config and code to flesh out the other CRUD operations

  functions:
    itemApi:
        handler: app.handle
        events:
          - http:
              path: items
              method: post
              cors: true
          - http:
              path: items/{id}
              method: put
              cors: true
          - http:
              path: items/{id}
              method: get
              cors: true
          - http:
              path: items/{id}
              method: delete
              cors: true

        .put('/items/{id}', (req, res) => {
            const params = {
                TableName: 'SlspressTestItem',
                Item: {
                    Id: req.event.pathParameters.id,
                    Name: req.event.body.name
                }
            }
            return docClient.put(params, err => err ? res.handleError(err) : res.noContent());
        })
        .get('/items/{id}', (req, res) => {
            const params = {
                TableName: 'SlspressTestItem',
                Key: {
                    Id: req.event.pathParameters.id
                }
            };
            return docClient.get(params, (err, data) => err ? res.handleError(err) : res.ok(data));
        })
        .delete('/items/{id}', (req, res) => {
            const params = {
                TableName: 'SlspressTestItem',
                Key: {
                    Id: req.event.pathParameters.id
                }
            };
            return docClient.delete(params, err => err ? res.handleError(err) : res.noContent());
        })

You can deploy your serverless app again and check the new endpoints. e.g.

curl -H "Content-Type: application/json" -X PUT -d '{"name":"New Item3!"}' https://your-api-url.execute-api.us-east-1.amazonaws.com/dev/items/1234 -
curl -H "Content-Type: application/json" -X GET https://your-api-url.execute-api.us-east-1.amazonaws.com/dev/items/1234 -v
curl -H "Content-Type: application/json" -X DELETE https://your-api-url.execute-api.us-east-1.amazonaws.com/dev/items/1234 -v