With API Gateway and Lambda, you can handle client errors gracefully by returning a 4xx response.

module.exports.handler = async (event) => { // run validation logic return { statusCode: 400 }
}

This way, we can communicate clearly to the client that there’s a problem with its request. It also lets the Lambda invocation complete successfully, so the invocation doesn’t count as erroneous. This means it wouldn’t trigger any error alerts you have on your Lambda functions.

Unfortunately, when it comes to AppSync and Lambda, we don’t have this ability anyway. Your function has to either return a valid response or throw an error.

This is problematic as client errors would cause your alerts to trigger and you end up wasting time investigating false alerts and eventually develop alert fatigue and become desensitized to these alerts.

The Workaround

The workaround is to mimic what we’d do with API Gateway and have the Lambda function return a specific response, such as:

{ error: { message: "blah blah", type: "SomeErrorType" }
}

and use a custom response VTL template to turn this into a GraphQL error:

if (!$util.isNull($ctx.result.error)) $util.error($ctx.result.error.message, $ctx.result.error.type)
#end $utils.toJson($ctx.result)

This way, the Lambda invocation was still deemed successful and wouldn’t trigger any alerts on Lambda errors.

However, it can still present a control-flow challenge. Because you have to always return something to the top-level function handler instead of just throwing an error.

Consider this example:

module.exports.handler = async (event) => { const resp = await doSomething(event) return resp
} async function doSomething(event) { doValidation(event) // do something useful here return something
} function doValidation(event) { if (event.arguments.answer !== 42) { throw new Error('wrong answer') }
}

This isn’t what we want! We don’t want to err the Lambda invocation because the client sent in an invalid request.

One approach would be to capture the error state explicitly and always return something:

module.exports.handler = async (event) => { const resp = await doSomething(event) return resp
} async function doSomething(event) { const validationResp = doValidation(event) if (validationResp.error) { return validationResp.error } // do something useful here return something
} function doValidation(event) { if (event.arguments.answer !== 42) { return { error: { message: "wrong answer", type: "ValidationError" } } } else { return {} }
}

While capturing error state explicitly and maintaining referential transparency is a good thing, it’s just not very convenient or idiomatic in languages like JavaScript.

Instead, when working with Node.js functions, I prefer to use a middy middleware to intercept specific errors and handle them.

For example, I’d define a custom error type such as the ValiationError type below.

class ValidationError extends Error { constructor(message) { super(message) this.name = this.constructor.name // This clips the constructor invocation from the stack trace // it makes the stack trace a little nicer Error.captureStackTrace(this, this.constructor) }
}

And the middleware would handle this specific error in the onError handler.

module.exports = () => { return { onError: async (request) => { if (request.error instanceof ValidationError) { // the response vtl template handles this case // where the response is { error: { message, type } } request.response = { error: { message: request.error.message, type: "ValidationError" } } return request.response } } }
}

And now, I can just a ValidationError from anywhere in my code and the error would not fail the Lambda invocation. Instead, it will be turned into a successful response:

{ error: { message: "...", type: "ValidationError" }
}

And the response VTL template would turn it into a GraphQL error.

if (!$util.isNull($ctx.result.error)) $util.error($ctx.result.error.message, $ctx.result.error.type)
#end $utils.toJson($ctx.result)

And voila! You have successfully handled a client error gracefully.

img 60bd763a9c5ef

Liked this article? Support me on Patreon and get direct help from me via a private Slack channel or 1-2-1 mentoring.

Screenshot 2019 10 19 at 11.44.09

img 5dab0c477fc72

Hi, my name is Yan Cui. I’m an AWS Serverless Hero and the author of Production-Ready Serverless. I specialise in rapidly transitioning teams to serverless and building production-ready services on AWS.

Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.

You can contact me via Email, Twitter and LinkedIn.

Hire me.

The post How to handle client errors gracefully with AppSync and Lambda appeared first on theburningmonk.com.