Using Vandium.io to create AWS Lambda functions

April 23, 2018 0 Comments

Using Vandium.io to create AWS Lambda functions

 

 


In recent days I'm playing with Vandium a javascript framework tailored to make AWS Lambda functions.

It's well documented and easy to start with.

Installation

Just like any other node js package.

npm install --save vandium

It's better to also install the testing dependencies. I'm using mocha, chai and lambda tester

npm install --save-dev mocha 
npm install --save-dev chai
npm install --save-dev lambda-tester

Be sure to create a test directory and add this command to the package.json file in the scripts property.

"scripts": { "test": "./nodemodules/mocha/bin/mocha --timeout 5000" 
}

So now you can execute tests with the command npm run test

What we will make

We will make a simple function that sums two numbers. Nothing too complex but will demostrate how everything works.

These will be the params that will be sent to the function using a POST method

"body" : { "operation" : "addition", "elements" : { "first" : 2, "second" : 2 } 
}

An additional restriction will be that the first number must be greater than the second number.

Creating files

We will need at least three files.

index.js that will hold the main logic and test/index.test.post.js that will hold the tests.

touch index.js 
mkdir test
touch test/index.test.post.js

Be sure to set the handler as handler: index.handler in the yml configuration file.

As you can see the tests are named as {filename}.test.{method}.js in order to test POST calls to the index file.

Now it's important to have a mock object with the params used in the lambda handler invocation.

Create a file named params.post.json in the test directory.

touch test/params.post.json 

You must define the "httpMethod": "POST", this will trigger the POST handler.

{ "resource": "/", "path": "/", "httpMethod": "POST", "headers": null, "queryStringParameters": null, "pathParameters": null, "stageVariables": null, "requestContext": { "accountId": "999999999999", "resourceId": "5k21okute99", "stage": "test-stage", "requestId": "test-invoke-request", "identity": { "cognitoIdentityPoolId": null, "accountId": "999999999999", "cognitoIdentityId": null, "caller": "999999999999", "apiKey": "test-invoke-api-key", "sourceIp": "test-invoke-source-ip", "accessKey": "Access", "cognitoAuthenticationType": null, "cognitoAuthenticationProvider": null, "userArn": "arn:aws:iam::999999999999:root", "userAgent": "Apache-HttpClient/4.5.x (Java/1.8.0102)", "user": "999999999999" }, "resourcePath": "/", "apiId": "aabbbcs" }, "body": { }, "isBase64Encoded": false 
}

The final structure would be similar to

index.js 
package.json
package-lock.json
test/ -- index.test.post.js -- params.post.json
nodemodules/

index.js

This will be our index.js file. We are validating the params using the Joi Schema validation lib (included with Vandium). Also we add an additional validation that returns a new error if the first number is greater than the second number.

Vandium works using native javascript Promises, so we return the error using a Promise.reject() function. You can also use the callback way if you prefer it.

 
/** * index.js * Returns the sum of two numbers * only if the first number is greater or equal than the second number */ 'use strict' const vandium = require('vandium') exports.handler = vandium.api() .POST({ /* * Params validation using the fantastic Joi lib * included in Vandium */ body : { operation: vandium.types.string().required().equal('addition'), elements: vandium.types.object().keys({ first: vandium.types.number().required(), second: vandium.types.number().required() }) } }, (event) => { let first = event.body.elements.first let second = event.body.elements.second if(first < second) { return Promise.reject( new Error('first number must be greater or equal than second number') ) } let result = first + second return result })

Responses

Vandium handles the response json and append the corresponding data in a standarized way.

Sample Ok Response

{ "statusCode": 201, "headers": {}, "body": "10", "isBase64Encoded": false } 

Sample Error Response

{ "statusCode": 400, "headers": {}, "body": "{\"type\":\"ValidationError\",\"message\":\"child \"operation\" fails because [\"operation\" must be one of [addition]]\"}", "isBase64Encoded": false } 

Notice the body is a string, so if you return a json object you must parse it before using it.

test/index.test.post.js

In the testing file, I added some basic tests.

These two env vars will help if you use eval() function or have a different node js version than 6.10 or 8.10 which are used in AWS lambda.

process.env.LAMBDATESTERNODEVERSIONCHECK = false 
process.env.VANDIUM
PREVENTEVAL = false
 
/** * Test index.js POST method calls */ 'use strict' // ENV Vars
process.env.LAMBDA
TESTERNODEVERSIONCHECK = false
process.env.VANDIUM
PREVENT_EVAL = false // Lambda handler
const handler = require('../index').handler // Testing libs
const LambdaTester = require('lambda-tester')
const expect = require('chai').expect // Load params used in the handler calls
let params = require('./params.post.json') // Begin tests
describe('index.js', function() { beforeEach(function() { }) after( function() { // Reload Params params = require('./params.post.json') }) describe('Testing Response Structure', () => { it('test that ok response structure is correct', (done) => { // Define the params sent to the handler params.body = { operation : 'addition', elements : { first : 2, second : 2 } } // Call the handler with the params LambdaTester(handler) .event(params) .expectResult( (result) => { expect(result).to.be.an('object') expect(result).to.have.property('statusCode') expect(result.statusCode).to.be.a('number') expect(result.statusCode).to.equal(201) expect(result).to.have.property('headers') expect(result.headers).to.be.an('object') expect(result).to.have.property('body') expect(result.body).to.be.a('string') expect(result.body).to.be.not.empty let body = JSON.parse(result.body) expect(body).to.be.a('number') // We handle the promise and call done() function to end the test }).then(() => { return done() }) .catch((error) => { return done(error) }) }) it('test that error response structure is correct', (done) => { // These params should trigger a Promise error // since the first number is less than the second number params.body = { operation : 'addition', elements : { first : 1, second : 2 } } LambdaTester(handler) .event(params) .expectResult( (result) => { expect(result).to.be.an('object') expect(result).to.have.property('statusCode') expect(result.statusCode).to.be.a('number') expect(result.statusCode).to.equal(500) expect(result).to.have.property('headers') expect(result.headers).to.be.an('object') expect(result).to.have.property('body') expect(result.body).to.be.a('string') let body = JSON.parse(result.body) expect(body).to.be.an('object') expect(body).to.have.property('type') expect(body.type).to.be.a('string') expect(body.type).to.equal('Error') expect(body).to.have.property('message') expect(body.message).to.be.a('string') expect(body.message).to.be.not.empty }).then(() => { return done() }) .catch((error) => { return done(error) }) }) }) describe('Testing Params', () => { params.body = { operation : 'addition', elements : { first : 2, second : 2 } } it('test that 2 + 2 = 4', (done) => { params.body = { operation : 'addition', elements : { first : 2, second : 2 } } LambdaTester(handler) .event(params) .expectResult( (result) => { let number = JSON.parse(result.body) expect(number).to.be.a('number') expect(number).to.equal(4) }).then(() => { return done() }) .catch((error) => { return done(error) }) }) it('test that 8 + 2 = 10', (done) => { params.body = { operation : 'addition', elements : { first : 8, second : 2 } } LambdaTester(handler) .event(params) .expectResult( (result) => { let number = JSON.parse(result.body) expect(number).to.be.a('number') expect(number).to.equal(10) }).then(() => { return done() }) .catch((error) => { return done(error) }) }) it('test that wrong operation params throws error', (done) => { params.body = { operation : 'sum', elements : { first : 2, second : 2 } } LambdaTester(handler) .event(params) .expectResult( (result) => { let body = JSON.parse(result.body) let message = body.message expect(message).to.contain('operation') }).then(() => { return done() }) .catch((error) => { return done(error) }) }) })
})

Testing

Now if we execute npm run test we should have something similar to

 index.js Testing Response Structure ✓ test that ok response structure is correct ✓ test that error response structure is correct Testing Params ✓ test that 2 + 2 = 4 ✓ test that 8 + 2 = 10 ✓ test that wrong operation params throws error 

Conclusion

Vandium gives a simple framework for AWS Lambda functions. Normally you will have to manually inspect the params, validate them or use traditional frameworks like Express in order to modularize.

Vandium also includes some helpful libs to interact with Amazon services events like Dynamo DB or Cognito. Additionally works with the Serverless framework too.

In conclusion it's a fine tool that will make your AWS Lambda functions more readable and easier to maintain and test.


Tag cloud