npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@enfo/aws-lambda

v1.3.0

Published

Simplifies response flow when working with AWS Lambda and API Gateway

Downloads

26

Readme

Introduction

This package exposes functionality for simplifying the process of working with AWS Lambda and API Gateway. In particular the response flow.

Installation

npm install @enfo/aws-lambda --save

Purpose of package

This package aims to simplify the response flow in an AWS Lambda invoked by API Gateway. It exposes functionality that allows for default responses, easy throwing from anywhere within your business logic and handling of those thrown errors. The philosophy behind the package is to separate AWS logic from the business as described as point #1 in Best practices for working with AWS Lambda functions.

APIGatewayHandlerError

APIGatewayHandlerError is an extension of Error. It should be thrown whenever you want a response in your code which is not a part of the happy path. If you for example fail to find a resource you can simply throw a new APIGatewayHandlerError with status code 404. Or if the invoker does not have access to a resource you can return a 403 with a specific message.

import { APIGatewayHandlerError, HTTP_STATUS_CODE } from '@enfo/aws-lambda'

new APIGatewayHandlerError<string>({
  statusCode: HTTP_STATUS_CODE.NOT_FOUND, // HTTP status code to return to invoker
  body: 'Unable to find resource', // Optional body to include in response
  headers: { // Optional headers to include in response
    'my-header': 'cool-value'
  }
})

APIGatewayHelper

APIGatewayHelper is the main class in this package. It exposes methods for setting default headers, responses, parsing JSON and most importantly executing your business logic in an error handling wrapper.

import { APIGatewayHelper } from '@enfo/aws-lambda'
import logger from '@enfo/logger'

const helper = new APIGatewayHelper({
  accessControlAllowOrigin: 'myOrigin', // optional accessControlAllowOrigin header, defaults to '*'
  defaultHeaders: { // optional headers to include in ALL responses
    key: 'value'
  },
  logger: logger // optional logger used to log when errors occur
})

Methods

  • getAccessControlAllowOriginHeader - returns accessControlAllowOrigin header value
  • getDefaultHeaders - returns default headers used in every response
  • setFallbackError - used to set APIGatewayHandlerError used when something goes unintentionally wrong. Used by handleError and wrapLogic
  • setJSONParseFailError - used to set APIGatewayHandlerError thrown when parsing of JSON fails due to a bad string. Used by parseJSON and parseJSONAsPartial
  • setJSONNoBodyError - used to set APIGatewayHandlerError thrown when input to parseJSON and parseJSONAsPartial is empty
  • parseJSON - used to parse a string to T
  • parseJSONAsPartial used to parse a string to RecursivePartial. RecursivePartial is a type which makes every key in object optional. The main purpose of this function is to return a more realistic payload value which can then be used in validation using for example joi
  • buildCustomResponse - builds a custom response. Set http status code, body, headers and isBase64Encoded
  • ok - returns a status 200 response. Set body, headers and isBase64Encoded
  • clientError - returns a status 400 response. Set body, headers and isBase64Encoded
  • serverError - returns a status 500 response. Set body, headers and isBase64Encoded
  • handleError - takes a APIGatewayHandlerError or Error and returns a response. In the case of a regular Error it will attempt to log it using the logger provided in the constructor. In the case of a APIGatewayHandlerError it will be used to build the response
  • wrapLogic - takes a business logic function, executes it while handling errors that might occur

Examples

Simple use

import { APIGatewayHelper, APIGatewayHandlerError, HTTP_STATUS_CODE } from '@enfo/aws-lambda'
import logger from '@enfo/logger'

const helper = new APIGatewayHelper({
  accessControlAllowOrigin: 'myOrigin', // optional accessControlAllowOrigin header, defaults to '*'
  defaultHeaders: { // optional headers to include in ALL responses
    key: 'value'
  },
  logger: logger // optional logger used to log when errors occur
})

const handlerOne = async (event: AWSLambda.APIGatewayProxyEvent) => { // interface from @types/aws-lambda
  return helper.wrapLogic({
    errorMessage: 'Something went wrong in lambda X',
    logic: async () => {
      return helper.ok<string>({ body: 'Everything went fine!' })
    }
  })
}

const handlerTwo = async (event: AWSLambda.APIGatewayProxyEvent) => {
  return helper.wrapLogic({
    errorMessage: 'Something went wrong in lambda Y',
    logic: async () => {
      const body = helper.parseJSON<{a: string}>(event.body) // will throw if something is wrong with the body, which will be handled by the wrapper
      return helper.buildCustomResponse({ statusCode: HTTP_STATUS_CODE.ACCEPTED, body })
    }
  })
}

const handlerThree = async (event: AWSLambda.APIGatewayProxyEvent) => {
  return helper.wrapLogic({
    errorMessage: 'Something went wrong in lambda Z',
    logic: async () => {
      throw new APIGatewayHandlerError<string>({
        statusCode: HTTP_STATUS_CODE.NOT_FOUND,
        body: 'Unable to find resource'
      })
    }
  })
}

Customizing error responses

import { APIGatewayHelper, APIGatewayHandlerError, HTTP_STATUS_CODE } from '@enfo/aws-lambda'

const helper = new APIGatewayHelper({
  accessControlAllowOrigin: 'myOrigin', // optional accessControlAllowOrigin header, defaults to '*'
  defaultHeaders: { // optional headers to include in ALL responses
    key: 'value'
  }
})

Let us modify the JSONParseFailError and then use it. The response to the invoker will be status 400 and the object specified in the body

helper.setJSONParseFailError(new APIGatewayHandlerError({
  statusCode: HTTP_STATUS_CODE.BAD_REQUEST,
  body: {
    message: 'Body in payload was not proper JSON'
  }
}))

const handlerOne = async (event: AWSLambda.APIGatewayProxyEvent) => {
  return helper.wrapLogic({
    errorMessage: 'Something went wrong in lambda X',
    logic: async () => {
      helper.parseJSON('broken[}')
      return helper.ok<string>({ body: 'Everything went fine!' })
    }
  })
}

Now, let us have a look at modifying the fallback response. This response gets used whenever a regular Error gets thrown.

helper.setFallbackError(new APIGatewayHandlerError({
  statusCode: HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR,
  body: {
    message: 'Something went catastrophically wrong processing your request, I am sorry'
  }
}))

const handlerTwo = async (event: AWSLambda.APIGatewayProxyEvent) => {
  return helper.wrapLogic({
    errorMessage: 'Something went wrong in lambda X',
    logic: async () => {
      throw new Error()
    }
  })
}

If you want to you can always use the building blocks to handle thrown errors on your own. The code you see below is essentially what wrapLogic does behind the scenes.

const handlerThree = async (event: AWSLambda.APIGatewayProxyEvent) => {
  try{
    const body = helper.parseJSON(event.body)
    return helper.ok<string>(await doStuff(body))
  }catch(err){
    return helper.handleError(err, 'Something went wrong in lambda X')
  }
}

Full example with file structure

Let us have a look at an example with two lambdas which are a part of a pets API. This example describes an interface for all error returned by the API in the form of APIError. This is then used to modify the instance with the proper answers. Then it is finally consumed within two lambdas using wrapLogic. The logic itself inside "get" and "put" could throw a APIGatewayHandlerError to return a response.

// errors.ts
export interface APIError {
  source: string;
  details: string;
}
// init.ts
import { APIGatewayHandlerError, APIGatewayHelper, HTTP_STATUS_CODE } from '@enfo/aws-lambda'
import { APIError } from './errors'

const gatewayHelper = new APIGatewayHelper({})
gatewayHelper.setJSONParseFailError(new APIGatewayHandlerError<APIError>({
  statusCode: HTTP_STATUS_CODE.BAD_REQUEST,
  body: {
    source: 'Pets application',
    details: 'Bad JSON in body'
  }
}))
gatewayHelper.setJSONNoBodyError(new APIGatewayHandlerError<APIError>({
  statusCode: HTTP_STATUS_CODE.BAD_REQUEST,
  body: {
    source: 'Pets application',
    details: 'No body in request'
  }
}))
gatewayHelper.setFallbackError(new APIGatewayHandlerError<APIError>({
  statusCode: HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR,
  body: {
    source: 'Pets application',
    details: 'Something went wrong processing your request. Sorry for the inconvenience'
  }
}))
// handler.ts
import { HandlerResponse } from '@enfo/aws-lambda'

import { gatewayHelper } from './init'
import { APIError } from './errors.ts'

import { get, put } from './pets' // file containing actual database interaction

export const getPet = async (event: AWSLambda.APIGatewayProxyEvent): Promise<HandlerResponse> => {
  return gatewayHelper.wrapLogic({
    logic: () => {
      return gatewayHelper.ok({
        body: await get(event.id)
      })
    },
    errorMessage: 'Something went wrong getting a pet'
  })
}

export const putPet = async (event: AWSLambda.APIGatewayProxyEvent): Promise<HandlerResponse> => {
  return gatewayHelper.wrapLogic({
    logic: () => {
      const body = gatewayHelper.parseJSONAsPartial(event.body)
      return gatewayHelper.ok({
        body: await put(body)
      })
    },
    errorMessage: 'Something went wrong creating a pet'
  })
}