In Azure functions, managing HTTP return code, headers and body is all done from the function itself. A bit less so with AWS, where you need to do some magic trickery in the API gateway1.

The main problem with λ and Gateway integration is that your λ does not have a direct access to the return code and body.

Gateway works like this:

gateway config

The bottom part is where λ’s response is processed. If the callback is not returning an error, by default the response body is returned using a pass-through, and the HTTP response will have a code 200.

What is, in my opinion, stupid, is the behaviour when an error is returned: you will still get a 200, but with a JSON body containing one field errorMessage, itself containing the error sent in callback. If your error is JSON, then it’s gonna look like that:

{
  "errorMessage": "{\"result\":\"validation error\",\"errors\":[\"Validation error: Missing eventType\"]}"
}

Obviously, that’s not great. But we can do better2.

First, we need to add a response code in the Method response section of lambda. Say 400. Then, in the integration response part of the gateway resource, we need to define when to return that response code, and change the body. Constraints are:

  • The only way (AFAIU) to define which response code to send,3 is to configure a regex in the gateway that will be matching the error callback.
  • The response body of the callback is ignored in this case.
  • The error callback needs to be a string.

This means that you will need to return your response object in the error callback as a serialized string.

The response adapter in your λ will probably look like this:

  return (response) => {
    switch (response.result) {
      case responseCodes.SUCCESS:
        callback(null, response);
        break;
      case responseCodes.VALIDATION_ERROR:
       callback(JSON.stringify(response));
        break;
      default:
        throw Error("Result status not supported");
    }

And then in the integration response part of the gateway resource, I add a new integration response that maps to the response code created earlier (400).

Next, I configure an Error Regex that will trigger this integration response. This regex basically checks the error response. Mine is set like this:

.*validation error.*

Finally, I set the body mapping template for JSON:

$util.parseJson($input.json('$.errorMessage'))

This will effectively grab the content of the errorMessage field and parse the JSON in there. The end result looks like this:

Integration response

You can also use the same method to map headers on response.

Finally we need to deploy the resource through Actions > Deploy. For some reason, it looks like I need to do it twice to work4.

{
  "result": "validation error",
  "errors": [
    "Validation error: Missing eventType"
  ]
}

Victory!

Now the problem is, you need to define all return codes in your gateway. You cannot do a real pass through. If you plan on having several success returns, you will need to effectively return them as errors from the λ - plan to have an adapter to prevent polluting your business logic.

This is not great as far as testability is concerned, and you can’t push gateway to your source control. But that’s what you’re left with unless you use serverless or Azure functions.

Notes

  1. This seems to be a pattern that really differentiates the two clouds. Azure tends to give you a big solution that’s doing 400% of what you need and you have to cut out the stuff you don’t want. AWS usually gives you the smaller bits and you have to create the glue that keeps them together. 

  2. It’s just reaaally sad that you have to figure out how to do better. 

  3. Yeah for Oxford commas! 

  4. It’s not just a matter of waiting. Waiting for 10 minutes does not fix the problem, it works only by deploying twice. Weirdest thing ever.