Reputation: 47871
For instance if I want to return a specific 400 error for invalid parameters or perhaps a 201 when the lambda function call resulted in a create.
I'd like to have different http status codes but it looks like api gateway always returns a 200 status code even if the lambda function is returning an error.
Upvotes: 125
Views: 130743
Reputation: 61
I have an express application and have used api gateway infront of it with http integration. To return status code from my application instead of 200 OK , I have simply added the http status code returned by the error handler of my application to the http status regex in integration response section and it works fine. Make sure your application properly handles error.
Upvotes: 1
Reputation: 935
Valid as of Feb 2021
The easiest way to set custom HTTP status code is to setup a Lambda Proxy Integration in API Gateway.
In API Gateway > Resource > Actions Dropdown > Create Method > tick Lambda Proxy Integration
and select appropriate Lambda function.
Lambda
For async functions just return with an object with statusCode
and body
. For sync function use callback(null,obj)
; refer full documentation.
export const dummyFunction = async (event, context, callback) =>
{
// ... logic
return {
statusCode: 400,
body: JSON.stringify({...data}),
}
};
Result
Custom status code of 400.
Upvotes: 2
Reputation: 815
If you do not want to use a proxy, you can use this template:
#set($context.responseOverride.status = $input.path('$.statusCode'))
Upvotes: 5
Reputation: 34295
The easiest way to do this is to use LAMBDA_PROXY integration. Using this method, you don't need any special transformations to be set into API Gateway pipeline.
Your return object would have to be similar to the snippet below:
module.exports.lambdaHandler = (event, context, done) => {
// ...
let response = {
statusCode: 200, // or any other HTTP code
headers: { // optional
"any-http-header" : "my custom header value"
},
body: JSON.stringify(payload) // data returned by the API Gateway endpoint
};
done(null, response); // always return as a success
};
It does have a few drawbacks: as having to be specially careful about error handling, and coupling your lambda function to the API Gateway endpoint; that said, if you were not really going to use it anywhere else, it is not that much of a problem.
Upvotes: 8
Reputation: 146034
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
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 explicit JSON.parse(event.body)
call (don't forget try/catch
around that). Example is below.statusCode
, body
, and headers
.
body
should be a string, so do JSON.stringify(payload)
as neededstatusCode
can be a numberheaders
is an object of header names to values{
"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
}
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.
Upvotes: 11
Reputation: 27395
I wanted an error from Lambda to be proper 500 error, after doing a lot of research, came up with the below, that works:
On LAMBDA
For a good response, I am returning as below:
exports.handler = (event, context, callback) => {
// ..
var someData1 = {
data: {
httpStatusCode: 200,
details: [
{
prodId: "123",
prodName: "Product 1"
},
{
"more": "213",
"moreDetails": "Product 2"
}
]
}
};
return callback(null, someData1);
}
For a bad response, returning as below
exports.handler = (event, context, callback) => {
// ..
var someError1 = {
error: {
httpStatusCode: 500,
details: [
{
code: "ProductNotFound",
message: "Product not found in Cart",
description: "Product should be present after checkout, but not found in Cart",
source: "/data/attributes/product"
},
{
code: "PasswordConfirmPasswordDoesntMatch",
message: "Password and password confirmation do not match.",
description: "Password and password confirmation must match for registration to succeed.",
source: "/data/attributes/password",
}
]
}
};
return callback(new Error(JSON.stringify(someError1)));
}
On API Gateway
For a GET METHOD, say GET of /res1/service1:
Through Method Response > Add Response, added 3 responses:
- 200
- 300
- 400
Then,
Through 'Integration Response' > 'Add integration response', create a Regex for 400 errors (client error):
Lambda Error Regex .*"httpStatusCode":.*4.*
'Body Mapping Templates' > Add mapping template as:
Content-Type application/json
Template text box* $input.path('$.errorMessage')
Similarly, create a Regex for 500 errors (server error):
Lambda Error Regex .*"httpStatusCode":.*5.*
'Body Mapping Templates' > Add mapping template as:
Content-Type application/json
Template text box* $input.path('$.errorMessage')
Now, publish /res1/service1, hit the published URL, that is connected to above lambda
Used Advanced REST client (or Postman) chrome plugin, you will see proper http codes like server error (500) or 400, instead of 200 http response code for all requests where were given in "httpStatusCode".
From the 'Dashboard' of API, in API Gateway, we can see the http status codes like below:
Upvotes: 10
Reputation: 910
This is how it's recommended on an AWS Compute Blog if using API Gateway. Checking to see if integration works with direct Lambda invocation.
var myErrorObj = {
errorType : "InternalServerError",
httpStatus : 500,
requestId : context.awsRequestId,
message : "An unknown error has occurred. Please try again."
}
callback(JSON.stringify(myErrorObj));
For direct Lambda invocations, this appears to be the best solution parsing on the client-side.
Upvotes: 2
Reputation: 5038
I'm using serverless 0.5. This is how it works, for my case
s-function.json:
{
"name": "temp-err-test",
"description": "Deployed",
"runtime": "nodejs4.3",
"handler": "path/to/handler.handler",
"timeout": 6,
"memorySize": 1024,
"endpoints": [
{
"path": "test-error-handling",
"method": "GET",
"type": "AWS_PROXY",
"responses": {
"default": {
"statusCode": "200"
}
}
}
]
}
handler.js:
'use strict';
function serveRequest(event, context, cb) {
let response = {
statusCode: '400',
body: JSON.stringify({ event, context }),
headers: {
'Content-Type': 'application/json',
}
};
cb(null, response);
}
module.exports.handler = serveRequest;
Upvotes: 1
Reputation: 7021
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.
Upvotes: 113
Reputation: 4348
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/
Upvotes: 21
Reputation: 145
For those who tried everything put on this question and couldn't make this work (like me), check the thedevkit comment on this post (saved my day):
https://forums.aws.amazon.com/thread.jspa?threadID=192918
Reproducing it entirely below:
I've had issues with this myself, and I believe that the newline characters are the culprit.
foo.* will match occurrences of "foo" followed by any characters EXCEPT newline. Typically this is solved by adding the '/s' flag, i.e. "foo.*/s", but the Lambda error regex doesn't seem to respect this.
As an alternative you can use something like: foo(.|\n)*
Upvotes: 7
Reputation: 7835
Here's the fastest way to return custom HTTP Status Codes and a custom errorMessage
:
In the API Gateway dashboard, do the following:
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:
Upvotes: 79