In a previous post on error handling in API Gateway I discussed various ways to map errors from your Lambda function to appropriate API status codes and how to build the response body appropriately for different types of errors.
One common pattern we’ve seen come up is the requirement to return a specific response header value depending on the type of error from the Lambda function. While this is most easily achieved with proxy integrations, some prefer to use the explicitly-mapped “AWS” integration type. This allows their Lambda function implementation to use native error types and decouples it from their API Gateway configuration.
In this example I will show how to manipulate the HTTP status code, the response body, as well as a response header value based on the Lambda error outcome.
Note, this example is specific to the NodeJS runtime, and will only allow to set a single header – please post in the comments if you have similar solutions for other runtimes.
This technique makes use of the fact that Lambda serializes the exception type in the errorType field, which can be mapped to a header value in API Gateway. This is a workaround solution until API Gateway supports JSON-parsing in parameter mapping expressions.
Lambda function
exports.handler = (event, context, callback) => { function MyError(message) { Error.captureStackTrace(this, this.constructor); // mapped to $.stackTrace this.customMessage = message; // custom field this.name = 'test header value' // mapped to $.errorType this.message = JSON.stringify(this); // mapped to $.errorMessage } require('util').inherits(MyError, Error); callback(new MyError("BadRequest - my error message")); };
Observe that this Lambda function sets the error.name field to the value desired in the response header. When the error is serialized by Lambda, this becomes the “errorType” field in the Lambda response. You can also set custom properties in the Lambda error which can be used when rendering the API response body.
This Lambda function outputs the following response:
{ "errorMessage": "{\"customMessage\":\"BadRequest - my error message\",\"name\":\"test header value\"}", "errorType": "test header value", "stackTrace": [ "exports.handler.message (/var/task/index.js:12:14)"] }
API definition
--- swagger: "2.0" info: version: "2017-01-25T19:26:48Z" title: "API Gateway Test API" host: "79rptjwqbk.execute-api.us-east-1.amazonaws.com" basePath: "/test" schemes: - "https" paths: /{proxy+}: x-amazon-apigateway-any-method: produces: - "application/json" parameters: - name: "proxy" in: "path" required: true type: "string" responses: 200: description: "200 response" 400: description: "400 response" headers: test-header: type: "string" 500: description: "500 response" x-amazon-apigateway-integration: responses: default: statusCode: "200" .*BadRequest.*: statusCode: "400" responseParameters: method.response.header.test-header: "integration.response.body.errorType" responseTemplates: application/json: "#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))\n\ \n{\n\"my-message\" : \"$errorMessageObj.customMessage\"\n}" uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:XXXXXXXX:function:errors/invocations" passthroughBehavior: "when_no_match" httpMethod: "POST" contentHandling: "CONVERT_TO_TEXT" type: "aws"
Note that the method response header is set to the value of the “errorType” field in the Lambda error response for 400 responses.
Zooming in on the mapping template for the 400 response:
#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage'))) { "my-message" : "$errorMessageObj.customMessage" }
The “stringified” errorMessage is parsed by the mapping template so that all properties (including custom properties) of the error object can be accessed in the mapping template to build the response body.
Invoking this method produces the following results, appropriately setting the trifecta of status code, response body, and response header.
Request: GET /test Status: 400 Response Body { "my-message": "BadRequest - my error message" } Response Headers {"test-header":"test header value","X-Amzn-Trace-Id":"Root=1-5888fee9-79b51571774c74ebeeb3eb62","Content-Type":"application/json"}
Comments, questions, and improvements are welcome!
-Ryan