API Gateway integration response setup for AWS Lambda
Tags: aws, awsthai, lambda, apigateway
Published: 2021-09-24 10:33:08

__Disclaimer: This article cover Lambda custom integration only, Lambda proxy integration will work differently__

It's a very common strategy to create your function in AWS Lambda and create REST API on API Gateway and integrate them together, however, there's one common and simple mistake that many developer overlook when doing so, especially if you move from local development to Lambda. Let's see the example below

```javascript
exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 400,  // Change from 200 to 400
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};
```

The above code blocks is a little modification from pre created code when choose "Author from scratch" when creating Lambda with NodeJS. The only change is from statusCode 200 to 400. Then create a new REST API method point to this lambda function... deploy and done. This is how simple it look like to create REST API, When looking at the code, many developer will say that this will return HTTP Status 400 right? but it's not. You will get response 200 with data `statusCode: 400`. This case can easily interpret that it's because we return content not HTTP Status.

(For Lambda proxy integration this function will response correctly with HTTP 400 Bad Request) 

_( For more information about HTTP Status [Read Here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) )_

![HTTP 200 with statusCode 400](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3olqmdqyjjf3njx7c7a.png)

Let's try another example which is more obvious, this time intentionally throw Error in Lambda.
```javascript
exports.handler = async (event) => {
    // TODO implement
    throw new Error('Internal Error - Intentionally throw this error');
};
```
Try calling it and get HTTP 200 again with error message as content.

(For Lambda proxy integration this function will response correctly with HTTP 502 Bad Gateway)

![HTTP 200 with Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t1b78al18zltk0tosksb.png)
> Look like It's Okay to Not Be Okay
![It's Okay to not be Okay](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i9y1ee0mqg2h59jfkbbz.jpg)
_(Image from [Kdramalove.com](https://www.kdramalove.com/ItsOkayToNotBeOkay.html) - Yes... I like Korean series.. and Seo Ye-ji is gorgeous)_

If you have experience throwing error and get HTTP 500 when developing locally with express or any framework. That happen because framework handle that error for you and generate correct HTTP Status, while Lambda and API Gateway is totally different services and what API Gateway looking for, by default, is that Lambda answer something and HTTP 200 you get mean __yes, your code was executed.__

## Setup Method Response in API Gateway ##

First we need to define which HTTP Status we want to send back to client. We can do this in Method Response in API Gateway. So let's add the following error
* HTTP 500 (Internal Server Error) for error that has been generated when we call `throw Error()` (Second case above). According to AWS Documentation, this type of error call **Standard Lambda Error**
* HTTP 400 (Bad Request) for error that we intentionally response with specific formatted string (First case above). According to AWS Documentation, this type of error call **Custom Lambda Error**
![Method Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/um91vldcu7g1kxpw0izx.png)

## Setup Integration Response in API Gateway ##
Next, move to Integration Response to configure each case. The general idea for configuring this is to set a pattern (regular expression) that will be tested against response from Lambda function, if the pattern matched, we will get HTTP Response governing that pattern. If no patterns matched, we will get the default HTTP response. (configured to 200 by default or customize by configured with pattern `.*`)
Furthermore, we can setup response body using Mapping Template.

### Handle Standard Lambda Error (500) ###
For this type of error, when we call `throw Error()`, Lambda will automatically generate response as shown in screenshot above. In order to test pattern against this type of error, API Gateway will test configured pattern against `errorMessage` field instead of whole error object. 

In our case, we send our error message when generating this error by adding prefix 'Internal Error' in front of any detail. Let's setup using regular expression `^Internal Error.*` which will match whole errorMessage. According to my test, if we use partial match (e.g. Internal*) it will not work and we will still get HTTP 200 instead of 500.

![Config HTTP 500](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1epym7pvbwwwfpvbz14u.png)

Testing on postman again, now we will get HTTP 500 (Internal Server Error) with the same previous content because we use Passthrough for content handling above.

![Postman Test HTTP 500](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xiv1qvl4yw3iims260xi.png)

Now that we got correct error, but it's strange to stack trace to client. Let's set Mapping Template for `application/json` type and return only errorMessage to client.
![Config Mapping for HTTP 500](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qd3noygm1be0vf2ydkkb.png)
(There's a strange UX behavior here, when editing Mapping Template you have to grey __Save__ down below text editor instead of clicking blue __Save__ button above, which is more attractive to click.)

Deploy and test it to get the response we want!
![Correct HTTP 500 Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6r6e9ejuxjut0jzq1oag.png)


### Handle Custom Lambda Error (400) ###
For this case where we want to create our own response with error message and we know that this response is gonna be an error. The easiest way is to forcefully make this function failed and return and error using `context.fail` instead of `return`. Additionally, in order for API Gateway to parse error message, we need to convert JSON response to text and the code will be change to the following:-
```JavaScript
exports.handler = async (event, context) => {
    // TODO implement
    const response = {
        statusCode: 400,  // Change from 200 to 400
        body: JSON.stringify('Hello from Lambda!'),
    };
    context.fail(JSON.stringify(response))
};
```
Now that we cause error to occurred, Lambda will return and error object similar to standard error in previous case and we can use regex `.*"statusCode":400.*` to detect which statusCode we send and return correct HTTP Response.
![Config HTTP400](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/js4qbkurun09oqnq7xs7.png)

After testing, you will get HTTP Status 400 (Bad Request) with generated Lambda error object
![HTTP400 with Error Object](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1gjr1l00p1b723gpwnh.png)

Last step is to setup Mapping Template to return our intended object
![HTTP400 Mapping Template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu65s4i8qiid4tw7iwsd.png)

Finally, we will get our same old object but with HTTP Status 400 instead of 200 as intended.
![HTTP400 with response data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j20vfy7hu1dr4su4exaa.png)

## Conclusion ##
There's a lot more to explore and configure when working with AWS service, this one is just an example of a common mistake mostly happen when start using Lambda and API Gateway or when in need of a quick api creation. Anyway, I personally prefer using Lambda proxy integration which let me have more control. I hope this article help anyway to happen to pass through here or looking for this problem. 

Lastly, I would be very appreciate for any comment, knowledge sharing and feedback. Happy coding!


## References ##
* [Handle Lambda errors in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/handle-errors-in-lambda-integration.html#handle-standard-errors-in-lambda-integration)