NodeJs Express in AWS Lambda via SAM
Tags: aws, node, serverless, javascript
Published: 2021-07-11 08:26:04

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"](https://www.meetup.com/Melbourne-AWS-Programming-and-Tools-Meetup/) Meetup I built a small demo using localstack and Dynamo.  A team that is building an adaption of [Scrumbler](http://scrumblr.ca/) 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](https://www.npmjs.com/package/@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)
```javascript
const app = require('./app')
const port = 3000
app.listen(port)
console.log(`listening on http://localhost:${port}`)
```
and 
- lambda.js
```javascript
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 )
```javascript
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
```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
```bash
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:

http://localhost:3000/fruitbox

Where you should see 
```json
[]
```

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](https://github.com/kukielp/aws-sam-nodejs-express/blob/main/api/views/index.ejs)

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.

![image](https://cdn.kukiel.dev/express.gif)

Sam/AWS Deployment TBC

The full repo can be seen [here](https://github.com/kukielp/aws-sam-nodejs-express)