Is there a way to change the http status codes returned by Amazon API Gateway?
Update per 20-9-2016
Amazon finally made this easy using the Lambda Proxy integration. This allows your Lambda function to return proper HTTP codes and headers:
let response = {
statusCode: '400',
body: JSON.stringify({ error: 'you messed up!' }),
headers: {
'Content-Type': 'application/json',
}
};
context.succeed(response);
Say goodbye request/response mapping in the API Gateway!
Option 2
Integrate an existing Express app with Lambda/API Gateway using aws-serverless-express.
Here's the fastest way to return custom HTTP Status Codes and a custom errorMessage
:
In the API Gateway dashboard, do the following:
- In the method for your resource, click on method response
- In the HTTP Status table, click add response and add in each HTTP Status Code you would like to use.
- In the method for your resource, click on integration response
Add an integration response for each of the HTTP Status Codes you created earlier. Make sure input passthrough is checked. Use lambda error regex to identify which status code should be used when you return an error message from your lambda function. For example:
// Return An Error Message String In Your Lambda Function return context.fail('Bad Request: You submitted invalid input'); // Here is what a Lambda Error Regex should look like. // Be sure to include the period and the asterisk so any text // after your regex is mapped to that specific HTTP Status Code Bad Request: .*
Your API Gateway route should return this:
HTTP Status Code: 400 JSON Error Response: { errorMessage: "Bad Request: You submitted invalid input" }
I see no way to copy these settings and re-use it for different methods, so we have much annoying redundant manual inputting to do!
My Integration Responses look like this:
To be able to return a custom error object as JSON you have to jump through a couple of hoops.
First, you must fail the Lambda and pass it a stringified JSON object:
exports.handler = function(event, context) {
var response = {
status: 400,
errors: [
{
code: "123",
source: "/data/attributes/first-name",
message: "Value is too short",
detail: "First name must contain at least three characters."
},
{
code: "225",
source: "/data/attributes/password",
message: "Passwords must contain a letter, number, and punctuation character.",
detail: "The password provided is missing a punctuation character."
},
{
code: "226",
source: "/data/attributes/password",
message: "Password and password confirmation do not match."
}
]
}
context.fail(JSON.stringify(response));
};
Next, you setup the regex mapping for each of the status codes you would like to return. Using the object I defined above you would setup this regex for 400:
.*"status":400.*
Finally, you setup a Mapping Template to extract the JSON response from the errorMessage property returned by Lambda. The Mapping Template looks like this:
$input.path('$.errorMessage')
I wrote an article on this that goes into more detail and explains the response flow from Lambda to API Gateway here: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object-and-status-code-from-api-gateway-with-lambda/
1) Configure your API Gateway resource to use Lambda Proxy Integration by checking the checkbox labeled "Use Lambda Proxy integration" on the "Integration Request" screen of the API Gateway resource definition. (Or define it in your cloudformation/terraform/serverless/etc config)
2) Change your lambda code in 2 ways
- Process the incoming
event
(1st function argument) appropriately. It is no longer just the bare payload, it represents the entire HTTP request including headers, query string, and body. Sample below. Key point is that JSON bodies will be strings requiring explicitJSON.parse(event.body)
call (don't forgettry/catch
around that). Example is below. - Respond by calling the callback with null then a response object that provides the HTTP details including
statusCode
,body
, andheaders
.body
should be a string, so doJSON.stringify(payload)
as neededstatusCode
can be a numberheaders
is an object of header names to values
Sample Lambda Event Argument for Proxy Integration
{
"resource": "/example-path",
"path": "/example-path",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Content-Type": "application/json",
"Host": "exampleapiid.execute-api.us-west-2.amazonaws.com",
"User-Agent": "insomnia/4.0.12",
"Via": "1.1 9438b4fa578cbce283b48cf092373802.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "oCflC0BzaPQpTF9qVddpN_-v0X57Dnu6oXTbzObgV-uU-PKP5egkFQ==",
"X-Forwarded-For": "73.217.16.234, 216.137.42.129",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"queryStringParameters": {
"bar": "BarValue",
"foo": "FooValue"
},
"pathParameters": null,
"stageVariables": null,
"requestContext": {
"accountId": "666",
"resourceId": "xyz",
"stage": "dev",
"requestId": "5944789f-ce00-11e6-b2a2-dfdbdba4a4ee",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"apiKey": null,
"sourceIp": "73.217.16.234",
"accessKey": null,
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "insomnia/4.0.12",
"user": null
},
"resourcePath": "/example-path",
"httpMethod": "POST",
"apiId": "exampleapiid"
},
"body": "{\n \"foo\": \"FOO\",\n \"bar\": \"BAR\",\n \"baz\": \"BAZ\"\n}\n",
"isBase64Encoded": false
}
Sample Callback Response Shape
callback(null, {
statusCode: 409,
body: JSON.stringify(bodyObject),
headers: {
'Content-Type': 'application/json'
}
})
Notes
- I believe the methods on context
such as context.succeed()
are deprecated. They are no longer documented although they do still seem to work. I think coding to the callback API is the correct thing going forward.