NodeJs Express in AWS Lambda via SAM

I like NodeJS and Express it's handy, easy to use and light weight. I have used this at work and in personal projects even though I tend to choose Python and Flask these days. What I like about express is that I can host my Frontend ( and SSR content ) and API in the same project. If I am building a PoC or a small site/application this make perfect sense.

A year or so ago as I was building out a lab for the local "AWS Programming and Tools" Meetup I built a small demo using localstack and Dynamo. A team that is building an adaption of Scrumbler Where having some issues so I whipped up a quick example of Express, wrapped up into a SAM template and deployed to AWS Lambda.

The set this up simply I have used @vendia/serverless-express as a wrapper/proxy for lambda. In this way I can develop locally with hot reloading and push the same code without change to Lambda.

There are 2 entry points:

  • Local (app.local.js)
const app = require('./app')
const port = 3000
app.listen(port)
console.log(`listening on http://localhost:${port}`)

and

  • lambda.js
const serverlessExpress = require('@vendia/serverless-express')
const app = require('./app');

exports.handler = serverlessExpress({ app })

And then of course the application itself. This application is simple and small and is all in one file ( app.js )

const express = require('express')
const app = express()
const router = express.Router()
const bodyParser = require('body-parser')
const cors = require('cors')
const path = require('path')

let FruitBox = [];

//Static content ie images
app.use('/static', express.static('static'))

router.use(cors())
router.use(bodyParser.json())
router.use(bodyParser.urlencoded({ extended: true }))

app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

router.get('/', function(req, res) {
    res.render('index', { fruitBox: FruitBox })
})

router.get('/index', function(req, res) {
    res.render('index', { fruitBox: FruitBox })
})

router.get('/react', function(req, res) {
    res.render('react')
})

router.get("/ping", async (req, res) => {
    const result = { incomming : 'ping ', resonse : 'pong '}
    res.send(JSON.stringify(result))
});

router.get("/fruitbox", async (req, res) => {
    res.send(JSON.stringify(FruitBox))
});

router.get("/fruitbox/:item", async (req, res) => {
    const item = parseInt(req.params.item)
    res.send(JSON.stringify(FruitBox[item]))
});


router.post('/fruitbox', async (req, res) => {
    let result
    try{
        const fruitName = req.body.fruitName;
        const qty = req.body.qty;
        const item = { fruit: fruitName, qty : qty}
        FruitBox.push(item)
        result = FruitBox
        res.status(200)
    }catch(e){
        console.log(e)
        result = { errorMessage : 'Ensure your POST body contains both a fruitName and a qty and content type is application/json '}
        res.status(500);
    }

    res.send(result)

})

app.use('/', router)

module.exports = app;

The package.json

{
  "name": "FruitBox",
  "version": "1.0.0",
  "description": "Express JS in AWS Lambda deployed to AWS Lambda via SAM",
  "main": "app.js",
  "repository": "https://github.com/kukielp/aws-sam-nodejs-express",
  "author": "Paul Kukiel",
  "license": "MIT",
  "dependencies": {
    "@vendia/serverless-express": "^4.3.2",
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "ejs": "^3.1.6",
    "express": "^4.17.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.11",
    "npm-watch": "^0.7.0"
  },
  "scripts": {
    "dev": "nodemon app.local.js"
  }
}

At this point we should be able to perform an npm install and run the app

npm install
npm run dev

The app should start up and nodemon will be watching for changes ( hot reloading ). However the base route ( / ) will error as we have not yet defined the view. The api will work you can try this by browsing to:

Where you should see

[]

Yes an empty array.

Let's now add the code for the view. For this particular view I have built a small html and js app that will demonstrate interactions with the API and allow you to POST new fruit items to the fruitbox and GET both the whole fruitbox and individual items based on index.

Rather then pasting that file here I'll just linke directly to it: Here

Express also has a rather nice feature that along side the @vendia/serverless-express module will allow the serving of static assets upto 5mb directly from lambda. In a production environment I would offload this to S3 and CloudFront but this is very handy for local development.

Below is a short clip of this running.

Sam/AWS Deployment TBC

The full repo can be seen here

25