Keeping your Lambda functions safe with Joi

Tal Bereznitskey
Feb 12 · 3 min read

We’re big fans of Serverless functions at Torii, and we’re always looking for ways to increase the robustness and our productivity.

Photo that has nothing to do with Lambda fucnctions by Matthew M

One of the problems we face with Lambda functions is making sure invocations are passed the correct inputs. As we’re also (happy) users of Hapi and Joi, we decided to borrow the input validation idea and apply it to our Lambda functions.

Example validation

Here’s a simple Lambda function that calculates a full name based on first and last names:

export const handler = async (e, context) => {
const { firstName, lastName } = e
return [firstName, lastName].filter(Boolean).join(' ')
}

We’d like to make sure that firstName and lastName are always sent in the event e and that those are valid strings.

In order to validate the input with Joi, we’ve written a utility function to wrap our handler, handlerWrapper and this is how we use it:

import Joi from '@hapi/joi'
import { handlerWrapper } from './handlerWrapper'
export const handler = handlerWrapper({
validation: {
e: {
firstName: Joi.string().min(3).max(20).required(),
lastName: Joi.string().min(3).max(20).required()
}
},
handler: async (e, context) => {
const { firstName, lastName } = e
return [firstName, lastName].filter(Boolean).join(' ')
}
})

We pass the handlerWrapper two arguments:

  • The validation schema, powered by Joi

The event e will be validated based on the Joi schema provided and will throw an exception if it did not pass validation. Keeping your function safe and exposing invalid invocations.

Testing

Let’s invoke the function using the Serverless Framework with some valid and invalid inputs:

serverless invoke -f fullname --data '{ "firstName": "Steve" }'// Throws an exception
// Error: "lastName" is required

And another:

serverless invoke -f fullname --data '{ "firstName": "St", lastName: "Jobs" }'// Throws an exception
// Error "firstName" length must be at least 3 characters long

And a valid input:

serverless invoke -f fullname --data '{ "firstName": "Steve", lastName: "Jobs" }'RESULT:
"Steve Jobs"

The code

This is the code for handlerwrapper.js:

import Joi from '@hapi/joi'

export const handlerWrapper = (options) => {
const { validate = {}, handler } = validateAndThrow(options, schema)

return async (e, ctx) => {
const eventWithDefaults = validateAndThrow(e, validate.e || {})
return handler(eventWithDefaults, ctx)
}
}

const validateAndThrow = (e, schema) => {
const { result, isValid, errors } = validateEvent(e, schema)
if (!isValid) {
throw new Error(errors.join(', '))
}

return result
}

const validateEvent = (e, schema) => {
const result = Joi.validate(e, schema)
const isValid = (result.error === null)
const errors = isValid ? null : result.error.details.map(detail => detail.message)

return { result: result.value, isValid, errors }
}

const schema = {
validate: Joi.object({
e: Joi.object()
})
,
handler: Joi.func().maxArity(2)
}

Other ideas?

If you have other ideas for making Lambda functions more robust, please share with me in the comments or over twitter at ketacode.

The Startup

Medium's largest active publication, followed by +609K people. Follow to join our community.

Tal Bereznitskey

Written by

Making stuff with ASCII letters — CTO @ https://toriihq.com

The Startup

Medium's largest active publication, followed by +609K people. Follow to join our community.

More From Medium

More from The Startup

More from The Startup

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade