AWS: Passing errors from Lambda to API Gateway

Alternate Resources

This article by Kenn Brodhagen is the best article I have found on this topic:

How to return a custom error object and status code from API Gateway with Lambda

Here are some others:

AWS Docs: Handle Lambda errors in API Gateway

AWS Blog: Error Handling Patterns in Amazon API Gateway and AWS Lambda

dev.classmethod.jp: How to map lambda error to API Gateway error response with Serverless Framework

Intro

One of the commonly annoying things about a serverless workflow with API Gateway and Lambda is that if you trigger a Lambda function with API Gateway and the function fails, you will still get a status code of 200 on the request.

status 200 on error

Because as far as API Gateway is concerned, it did it’s job, it got your request through the Gateway and called the function. You will only get non 200 responses from API Gateway if your authentication fails, a mapping error, or some other error that happens within API Gateway.

This is not very useful for catching errors in your client however. So to fix this we just have to do a little bit of extra mapping magic in API Gateway and make sure we are throwing our errors properly in Lambda.

Throwing the Error

One of the first and most important concepts you have to understand here is that if you want API Gateway to return an error status code, you must throw an exception in Lambda. You cannot simply return a response object with a status code manually set to 400 like this example here.

exports.handler = async event => {
  let response = {
    status: 200,
    body: undefined,
    headers: {
      "Content-Type": "application/json",
    },
  }

  try {
    // purposely throw error
    console.log(variableThatDoesntExist)
    response.body = "This will be skipped"
  } catch (error) {
    response.status = 400    response.body = "We referenced a non-existent variable (on purpose)"
  }

  //  dynamic response
  return response
}

If we run this function the status code is 200.

response code 200

Furthermore there is no way to utilize a response mapping template in API Gateway to give yourself a 400 code unless you actually throw a proper error like this:

exports.handler = async (event, context) => {
  let response = {
    status: 200,
    body: undefined,
    headers: {
      "Content-Type": "application/json",
    },
  }

  try {
    // purposely throw error
    console.log(variableThatDoesntExist)

    response.body = "This will be skipped"
  } catch (error) {
    response.status = 400
    response.body = "We referenced a non-existent variable (on purpose)"
    context.fail(JSON.stringify(response))  }

  //  dynamic response
  return response
}

or you could use

exports.handler = async event => {
  let response = {
    status: 200,
    body: undefined,
    headers: {
      "Content-Type": "application/json",
    },
  }

  try {
    // purposely throw error
    console.log(variableThatDoesntExist)

    response.body = "This will be skipped"
  } catch (error) {
    throw new Error("We referenced an undefined variable")  }

  //  dynamic response
  return response
}

etc etc… there are many ways to throw an error.

I cannot emphasize this enough, you must throw an actual error or you will not be able to map a non 200 response

Create Method Response

Moving over to API Gateway now, start by picking whichever method you are working with and navigate to Method Response > Add Response

method response

add response

Add a 400 status (or whatever) and then head over to the integration response.

Mapping the Error

Lastly we need to do two things. We need to create a very simple mapping template, and add a Regex string that will trigger the 400 code we just created.

Navigate to Integration Response > Add Integration Response

integration response

add integration response

The regex that is used here is a special java format call Pattern.

The Regex string is looking for matches in the output and if it finds them it will throw the corresponding status code that it matched. We will use the following.

Lambda Error Regex: .*"status":400.*

and then we select the 400 response status that we created in the previous step.

Lastly we need to create a very short mapping template that will tell API Gateway where we are looking for this regex match.

Mapping_Template
$input.path('$.errorMessage')

mapping template

We are looking inside the errorMessage object, which is the object that is created when you throw the error in Lambda.

Conclusion

And there you have it, if we run the function again and get an error we will get the following

correct status code

This was a very simple implementation. You can create more complicated regex that can capture a wider range of items from the error message object and there are examples of that elsewhere. But hopefully now the basic idea is clear.