Vandium: The Node.js Framework for AWS Lambda

One of the most exciting new cloud technologies over the last few years has been the emergence of Amazon Web Services’ (AWS) Lambda environment. For the first time, we can now execute a piece of arbitrary code for a specific event such as an upload, database, or an API call. This is just the start to the new trend that we’re seeing as we transition from the server-polling models to full event driven “serverless” cloud architectures. This trend now extends anywhere from handling notification events to transforming data streams to building the next big REST-based API — all without the need to manage servers.

Since Lambda event handlers do not require management of a server process, writing them is a little different than what you might be used to. For instance, there’s no concept of maintaining state inside a Lambda handler. There’s the loading of the handler and handler execution. That’s it. Rather than being billed per hour of server time regardless of activity, you are simply billed in fractions of a cent, for each millisecond your code is actually being executed.

The following sample code shows a basic Lambda function with its event, context, and callback parameters:

'use strict';

exports.handler = function( event, context, callback ) {

// Do something here
callback( null, 'success!' );
});

The event parameter contains information about “why”, “how” and/or “what” triggered the Lambda function. The context object contains information about the execution context including security credentials and utility functions that are part of the Lambda environment. The callbackparameter is a standard staple of Node.js that allows us to complete the Lambda call in an asynchronous manner.

It looks really simple, so why do we need a framework? Let’s look at a real world example to see how things can get a little more busy.

The following Lambda gets triggered by AWS API Gateway, using the lambda-proxy mechanism, for a POST /users HTTPS request.

'use strict';

// our datastore
const db = require( './lib/db' );

exports.handler = function( event, context, callback ) {

// prevent Lambda from being used in requests outside POST
if( event.httpMethod !== 'POST' ) {

return callback( null, {

statusCode: 400,
headers: {},
body: {
message: 'Invalid HTTP Method: ' + event.httpMethod;
}
});
}

let userName = event.body.userName;

db.addUser( userName )
.then( (user) => {

callback( null, {
statusCode: 201,
headers: {},
body: user
});
})
.catch( (err) => {
 
callback( null, {

statusCode: 400,
headers: {},
body: {
message: err.message 
}
});
});
};

In the above code, the following logic is being executed:

  1. Examining the event and making sure it’s a POST request; if not, then we stop executing and return a message
  2. Attempt to add the user using the body of the POST request that contains a userName property.
  3. If the user can be added, we return the user information in the response, otherwise we send back an error message.

Note that we use JavaScript’s Promise implementation to simplify asynchronous operations, otherwise the readability of the code would be a little more difficult.

Introducing the Vandium Framework

Vandium allows developers to quickly implement, test and maintain code without having to write lots of extra “plumbing”. Vandium uses targeted event handlers to focus intent and functionality for the underlying operations that need to be performed. The framework contains powerful validation, security, and enforcement logic to build tight production grade code.

The Vandium project has been around for over a year and contained many changes to accommodate new Lambda functionality. The original concept behind the project was to create a wrapper to add security and functionality to existing Lambda functions. While this approach worked, it did not solve the problem of targeting specific event types and the functionality around them. Version 4.0 has taken a steep departure from the previous releases and focuses more on the framework aspect rather than just wrapping Lambda functions.

Getting Started

Let’s install Vandium into our project using npm:

npm install vandium --save

Now, we’ll take the previous code example and re-implement it using Vandium.

'use strict';

const vandium = require( 'vandium' );

// our datastore
const db = require( './lib/db' );

exports.handler = vandium.api()
.POST( (event) => {

let userName = event.body.userName;

return db.addUser( userName );
});

Using the Vandium framework, we reduced the amount of code significantly — about 1/3 the size, including all that extra whitespace.

There are some very noticeable differences in the two pieces of code:

  1. No need to check that the HTTP method type isPOST
  2. Vandium handles the response logic by returning a promise that returns the value of the user.
  3. Exception processing is also handled by Vandium
  4. Callback logic has been completely removed

Event Validation

The above code could not be used in production because we’re not validating the input. Although one might argue that the db.addUser() code would perform the validation, it’s usually safer and better to have a multi-tiered strategy.

To add validation, we need to add a validation object as the first argument to thePOST() method.

'use strict';

const vandium = require( 'vandium' );

// our datastore

const db = require( './lib/db' );

exports.handler = vandium.api()

.POST( {

body: {

userName: vandium.types.string().min(4).max(100).required()

}

},

(event) => {

let userName = event.body.userName;

return db.addUser( userName );

});

Our validated handler now ensures that the value of userName exists, is a string and between 4 and 100 characters. In addition to validation, Vandium will automatically trim the whitespace characters for you so you don’t need to worry about it.

Cleaning Up after Execution

One of the frustrating issues with writing Lambda functions that open outside connections, like databases or caches, is that they need to be closed before the Lambda will finish execution. This is important because you will be billed until your Lambda no longer has items in its event queue. One way to solve this would be to wrap the callback function with another that ensures connections are closed before exiting.

Vandium solves the problem differently and makes it easy to release resources. At the end of any Vandium handler, you can add a call to finally() which will allow you to execute clean-up code after a successful or failed invocation of your Lambda function.

'use strict';

const vandium = require( 'vandium' );

// our datastore

const db = require( './lib/db' );

exports.handler = vandium.api()

.POST( {

body: {

userName: vandium.types.string().min(4).max(100).required()

}

},

(event) => {

let userName = event.body.userName;

return db.addUser( userName );

})

.finally( () => {

return db.closeCache();

});

In the above code we are executing and returning a Promise from db.closeCache() which will allow the connection to the cache service, like Redis, to be released. If an exception is thrown during this operation, Vandium will log to CloudWatch and complete execution as intended.

Conclusion

The ability to use Promises, targeted event handlers, event validation, and simplified resource cleanup allows developers to write less code, test fewer cases and deliver functionality with less technical debt. Using Vandium not only simplifies writing Lambda functions, but adds functionality that would normally require additional testing and maintenance.

Source: https://medium.com/vandium-software/vandiu...