AWS Amplify: Cascade Deletion for GraphQL in a Lambda API (Part 2)

evan kirkiles
5 min readMar 7, 2022

--

In Part 1, we built a Lambda layer with a GraphQL client for signing requests to our GraphQL API with the identity of the caller of the Lambda function. Now, we’ll construct the Lambda functions to use this client, in this case for implementing cascading deletes on tables with connecting relationships between them.

Photo by Lorenzo Herrera on Unsplash

This post was similarly inspired by Paulo Pires’ post here, which shows how to implement cascade deletion triggered by DynamoDB streams. Now, we’ll do them in a Lambda function behind a REST API, which will eliminate unnecessary calls to our serverless Lambda function at the expense of needing to deterministically run this delete function when we desire cascade deletion.

In the last post, we set up a GraphQL Client in a Lambda layer that would sign HTTP Requests to our GraphQL API endpoint with an authorization token supplied in its constructor. But where does this authorization token come from?

The answer is that if we use Cognito user-pool authorization on a REST API, and provide the Authorization: Bearer XXXX header to the endpoint in our client, that token is available in the request to the Lambda function invoked by the authorized user. We simply need to extract it and then pass it into our client for all of our queries to work, nice and scoped.

Cascade Deletion: Example Schema

Consider the following schema for a wardrobe app, which has been reduced to just the essentials:

In the above schema, we define two main types: Clothes and Outfits , with child types ClotheImage and OutfitComponent respectively––a Clothe may have any number of ClotheImages , and an Outfit may have any number of OutfitComponents which reference a single ClotheImage each along with other metadata not included.

When we delete a Clothe from the database, we want to also delete all its corresponding ClotheImages . However, we also need to delete all the OutfitComponents corresponding to the ClotheImages being deleted, else we will be left with null references where there cannot be. So we need a twice-nested cascade deletion in this case. Not to fear––Lambda is here.

Cascade Deletion: Lambda Function

Let’s build a clotheAPI Lambda function that can handle any sort of complex action we want to perform on a clothe, be it cascade deletion or something else in the future. When we add our REST API, we will place this Lambda function behind a path like /clothe/{clotheId} to be able to use standard HTTP methods likeGET or DELETE to perform our desired actions.

Add the function to Amplify:

$ amplify add function
? Provide an AWS Lambda function name: clotheAPI
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Serverless ExpressJS function (Integration with API Gateway)
Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration
? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? Yes
? Select the categories you want this function to have access to. api
? Select the operations you want to permit on <my-wardrobe-api> Query, Mutation
You can access the following resource attributes as environment variables from your Lambda function
API_<MY-WARDROBE-API>_GRAPHQLAPIENDPOINTOUTPUT
API_<MY-WARDROBE-API>_GRAPHQLAPIIDOUTPUT
ENV
REGION
? Do you want to invoke this function on a recurring schedule?
No
? Do you want to enable Lambda layers for this function? Yes
? Provide existing layers or select layers in this project to access from this function (pick up to 5): <graphql-client-layer-from-part1>
? Do you want to configure environment variables for this function? No
? Do you want to configure secret values this function can access? No
? Do you want to edit the local lambda function now? No
Successfully added resource clotheAPI locally.

Okay, great. Now we have a serverless express Lambda function boilerplated up in our local Amplify project, which has access to the GraphQL client from the Lambda layer at /opt/graphQLClient.js . We only really care right now about implementing delete functionality, so add some code in app.js like so to register an express route at /clothe/:clotheId for the DELETE method:

Don’t freak out about the Clothe import, we’ll do that in just a second. For now, notice how we read in the authorization header from the request. That’s where we’ll find the Bearer XXX with the Cognito JWT token inside! We just need to pop that over to our GraphQL Client and then we can use it to authenticate requests.

Now, let’s implement the actual code for our cascading deletion. Note that I’ve defined the following queries and mutations in /opt/graphql/queries.js and /opt/graphql/mutations.js of the Lambda Layer to facilitate the cascading deletion:

Now, let’s just create the cascading deletion code in a file called clothe.js in our Lambda function’s src directory, on the same level as our app.js :

There’s our Lambda layer’s GraphQL client in action! It runs the list queries and adds the collateral items to be destroyed at each nested level to a set. Once all the collateral items have been found, it goes ahead and loops over all of them and runs the delete mutations. This could probably be optimized by using the BatchWriteItem capability of DynamoDB, but for now this suffices. And once we get the REST API up and running, you’ll see that access to the tables is scoped based on the ownership of the user calling the Lambda function.

All of this code together defines our Lambda layer and the Lambda function for a specific route on a hypothetical REST API, i.e. /clothe/{clotheId} with method DELETE . But we haven’t actually created that REST API yet. So in Part 3, the last installation of the series, we’ll finally build our API with Cognito authorization and create an access point for the Lambda function we created here.

If you have any questions or implementation issues, feel free to shoot me an email at kirkilese@gmail.com!

--

--