How I Built A Serverless RSVP Feature With API Gateway, Lambda, and DynamoDB

I was recently asked to create an RSVP feature for my brother’s wedding website. I was looking forward to working on this since I decided to pursue web development recently, and I’ve only worked on smaller projects thus far. The opportunity to build something that people like our friends and family members would actually use was exciting, not to mention getting the chance to learn new serverless technologies. In this post, I’ll summarize how I approached this project and share a few helpful resources I used.

Why Serverless

The existing website was initially designed and developed by my brother and his fiancée using Gatsby, and is currently hosted on Github Pages. They had a couple requirements for this new RSVP feature.

First, they didn’t want to maintain any infrastructure because it was a static website to begin with. For this reason, I avoided building a monolithic backend where you would normally be concerned with things like server hosting and configuration.

Additionally, since the website is being hosted for free on Github Pages, and Gatsby is known for producing incredibly fast websites, they wanted to stay cost-efficient while keeping performance high. Naturally, all signs pointed to going serverless and we chose to go with Amazon Web Services.

Implementation

RSVP Architecture
  1. First, the user interacts with a React form to add, remove, or edit guests in their party.
  2. Upon submission, a request is sent to the RSVP API created by API Gateway.
  3. The API then triggers a Lambda that puts the RSVP response into a DynamoDB table.
  4. After the request successfully completes, the user receives a confirmation page.

Now let’s look at how I approached the implementation.

Getting started with AWS

A big obstacle I faced was getting started with AWS. I only had a basic understanding of serverless concepts and I knew nothing about AWS. Before jumping into how API Gateway, Lambda, and DynamoDB functioned, I first started with a few tutorials to get a better understanding of how some of AWS’s core services worked. I explored services like S3, EC2, and CloudWatch and played around with them via the AWS web console. Then, I looked into another service, CloudFormation.

CloudFormation essentially treats infrastructure as code by allowing you to setup your infrastructure and resources through configuration templates. I knew this was valuable, but due to time constraints I didn’t get around to learning the ins and outs of it. However, I did learn of the Serverless Framework and how it further simplifies the configuration process. One of the aspects about Serverless that I appreciated immediately was how easy it was to deploy and take down services. This was helpful for quickly generating test environments. A single sls deploy command creates a CloudFormation template from your serverless.yml file, uploads that template to CloudFormation, and creates a CloudFormation stack. And a quick sls remove command takes down the service and all its resources. The Serverless docs go more into what’s going on behind the scenes.

After a few days of research, I felt ready to continue working on the rest of the RSVP feature.

Creating the RSVP API

The first part of the RSVP feature that I worked on was constructing the RSVP API. I had never built an API before this project, so the first thing I had to think about was what endpoints were needed. In the early stages of planning, we intended to have a Guests table and a Responses table stored in Dynamo that needed to be added to and read from. This resulted in having 5 endpoints:

  • POST /responses/accept
  • POST /responses/decline
  • GET /responses
  • GET /guests
  • POST /guests

Initially, we wanted to differentiate between acceptances and rejections, which is why there is a /responses/accept endpoint and a /responses/decline endpoint. However, we tossed that idea later on, and decided to use /responses/accept for all responses, leaving /responses/decline unused. I also realize now that this isn’t strictly RESTful because these endpoints contain verbs. I could fix these issues by simply having one POST /responses endpoint and if we still wanted to distinguish between acceptances and rejections, then that could be determined inside the Lambda.

Additionally, we decided that the /guests endpoints were no longer necessary for this project. Thus, this project makes use of 2 of the 5 endpoints — POST /responses/accept and GET /responses.

With the API designed, it now needed to be created with API Gateway. I was surprised at how easy it was to set it up with Serverless.

#in serverless.yml
functions:
submitResponse:
handler: handler_response.submitResponse
description: Submits response
events:
- http:
path: responses/accept
method: post
cors: true

Then, all that’s needed is to run sls deploy. This creates a Lambda, a new API, the POST resource, and connects the resource to the Lambda in a single command. Without any additional setup, I now have a public facing endpoint.

Next, I needed to write the Lambda functions that connect to DynamoDB.

Creating Lambdas to interact with DynamoDB

Before writing the Lambdas, I had to modify serverless.yml and deploy to create the necessary resources.

#in serverless.yml
resources:
Resources:
ResponsesDynamoDbTable:
Type: 'AWS::DynamoDB::Table'
Properties:
AttributeDefinitions:
-
AttributeName: "id"
AttributeType: "S"
KeySchema:
-
AttributeName: "id"
KeyType: "HASH"
BillingMode: PAY_PER_REQUEST
TableName: ${self:provider.environment.RESPONSES_TABLE}

An important property to note in the configuration is BillingMode. For this feature it’s set to PAY_PER_REQUEST, meaning that charges apply only when the Dynamo table is in use, keeping this project cost efficient.

After the resources were set up, I needed to structure the Dynamo table. Serverless has a helpful tutorial on how to build a REST API using these services, which I used as a reference in creating this feature. We wanted to associate the person who initiated the RSVP with any additional family members or guests they planned to bring. The goal was to collect the name of every single guest attending. As a result, I shaped each response item in the Dynamo table to contain the following attributes:

  • id
  • fullName
  • attendingCeremony
  • attendingBanquet
  • dietaryRestrictions
  • guests
  • timestamp

Next, I needed to create the Lambda functions to save and retrieve responses, so I created submitResponse and listResponses.

Prior to saving the response to Dynamo, submitResponse adds the id and timestamp attributes to the response item. This is done because they aren’t auto generated by Dynamo. Then, I used Dynamo’s put API call to save the response.

In listResponses, I used Dynamo’s scan API call, but I ran into a small issue causing it to fail. Looking at CloudWatch, I learned that timestamp is a reserved word in Dynamo and it turns out that it’s not easy to rename columns. Luckily, scan has an optional parameter that allows you to create an alias for reserved words.

With this, the backend was now complete and I was ready to move on to the React form.

Creating the React form

In researching how to best go about building React forms, I came across this article, which does a good job of explaining how to structure forms using controlled components. This ensures that the form data is being handled by React and not by the DOM. The form was also designed to have 4 views: the form view, the edit view, the summary view, and the result view. As a result, I created 6 components to compose the form:

  • <FormContainer> — Renders form elements, handles all component logic, updates state of form
  • <Input> — Renders the form labels and controls
  • <Guest> — Renders a summary of a single guest’s info
  • <Summary> — Renders a list of Guest components along with the options to edit or add a guest
  • <Result> — Renders the names of everyone on the guest list
  • <FormFooter> — Renders the footer of the form containing different buttons depending on the current view

Let’s look at how each view works.

RSVP Response Flow

Form view
The form view shows a simple form for adding a single guest. When “Continue” is clicked a new guest object is created containing the form info and pushed to a guestList array. An error message is shown if the user does not enter a valid name.

Summary view
The summary view shows all the guests in the guestList array. There are options to modify the guest list or send the RSVP. If “Send RSVP” is clicked, a POST request is sent via Fetch to the /responses/accept endpoint. If the request fails, an error message is shown.

Edit view
The edit view is very similar to the form view, except that the fields are pre-populated with the current guest’s information. When “Remove This Guest” is clicked, the current guest is removed from guestList. When “Continue” is clicked, the same error checking is performed as in the form view and, if it’s valid, a new guest object is created containing the updated information. This new object replaces the current guest object in the guestList array. An important thing to note is that, as mentioned in the Lambda and DynamoDB section, we want to know who initiated the RSVP. Because of this, users will not have the option to remove themselves from the party.

Result view
The result view confirms that the RSVP was successfully received and lists all the names that were in guestList.

Conclusion

Full user flow featuring all possible views and Uncle Ben

TL:DR; I built an RSVP feature using serverless technologies! I learned a lot while working on it, but I know there are still ways it could be improved upon. For instance, I could have written more reusable React components, and I could have used a library like Formik to help build a better, more interactive form.

However, I definitely hit the goals of the original requirements:

Cost-efficient
The total cost for API Gateway, Lambda, and DynamoDB incurred so far was $0. Because AWS’s pricing is based on usage, our request volume doesn’t even register on their pricing scale. For example, our 400 DynamoDB writes so far cost just 0.05¢ — not even a penny.

High performance
The average latency for an API request is around 400ms, making the user experience feel very fast and seamless.

You can check out the Github repos for the RSVP API (https://github.com/enrubio/rsvp-api) and Gatsby website (https://github.com/janrubio/janandolivia) if you’d like to take a closer look. Let me know what you think and if you have any feedback.

Lastly, I’m currently seeking a full stack web developer role. If you have an opening on your team and want to talk, send me an email at emilia.rubio@gmail.com.


Resources

The following is a recap of the resources I used to help me learn about serverless technologies and build this feature: