1.3.0 • Published 2 years ago

@enfo/aws-lambda v1.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

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'
  })
}
1.3.0

2 years ago

1.2.0

2 years ago

1.1.2

2 years ago

1.1.1

2 years ago

1.1.0

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago