We are trying to use custom error status codes with GraphQL so that our upstream services can better utilize the errors thrown.
We have a simple error class that extends the base Error
class to include a status code:
export class ErrorWithStatusCode extends Error {
public statusCode: number;
constructor({
message,
name,
statusCode,
}: {
message: string;
name: string;
statusCode: number;
}) {
super(message);
this.name = name;
this.statusCode = statusCode;
}
}
export class BadRequestError extends ErrorWithStatusCode {
constructor(message: string) {
super({
message,
name: `BAD_REQUEST`,
statusCode: 400,
});
}
}
And then we throw that error in our code like so:
if (existingResource) {
throw new BadRequestError('An account with the email address already exists');
}
Inside our logs we see the Lambda invoke error:
2022-04-29T13:42:56.530Z 0e1688ac-89f1-46a7-b592-d6aeceb83fd7 ERROR Invoke Error
{
"errorType": "BAD_REQUEST",
"errorMessage": "An account with the email address already exists",
"name": "BAD_REQUEST",
"statusCode": 400,
"stack": [
"BAD_REQUEST: An account with the email address already exists",
" at Runtime.main [as handler] (/var/task/index.js:26908:19)",
" at processTicksAndRejections (internal/process/task_queues.js:95:5)"
]
}
Then in our VTL template, we just associate all properties outside of name
and message
to the custom errorInfo
object as described here
$util.error(String, String, Object, Object) Throws a custom error. This can be used in request or response mapping templates if the template detects an error with the request or with the invocation result. Additionally, an errorType field, a data field, and a errorInfo field can be specified. The data value will be added to the corresponding error block inside errors in the GraphQL response. Note: data will be filtered based on the query selection set. The errorInfo value will be added to the corresponding error block inside errors in the GraphQL response. Note: errorInfo will NOT be filtered based on the query selection set.
#if (!$util.isNull($ctx.error))
$utils.error($ctx.error.errorMessage, $ctx.error.name, $ctx.result, $ctx.error)
#end
$utils.toJson($ctx.result)
But when we get the error back from AppSync (using the console), we do not have any additional error properties:
{
"data": null,
"errors": [
{
"path": [
"createResource"
],
"data": null,
"errorType": null,
"errorInfo": {
"message": "An account with the email address already exists",
"type": "Lambda:Unhandled"
},
"locations": [
{
"line": 2,
"column": 5,
"sourceName": null
}
],
"message": "A custom error was thrown from a mapping template."
}
]
}