Member preview

Learn AWS Serverless by creating a basic email signup list

Most people working in technology are familiar with the tried-and-true server setup that powers much of the internet wherein a cluster of servers are dedicated to running an application or a series of applications. When you navigate to a website, for example, your request will route to the server cluster hosting the application and eventually a single server will process your request and return a response. Prior to the cloud movement these server clusters were actually dedicated hardware machines managed either by the company itself or rented through one of the numerous data centers.

With the advancement of cloud-based computing, the industry began to see an exodus away from traditional physical hardware to cloud-based hardware. This allowed pretty much anyone to spin up a server with a few clicks of a button — all while dramatically reducing costs for businesses that did not actually need powerful, dedicated servers. In this model servers became a service and the nuances of setting up and maintaining them were masked by an easier API interface.

In the last few years we’ve seen a large push to wrap as many other historically painful things as possible in services: databases, logging, machine learning, storage, etc. One of the more recent services to be wrapped under a service is code itself. What this means is that instead of thinking about setting up servers to execute code you are simply providing the code that needs to be executed and letting the cloud provider manage how it’s ran and how it scales. The current frontrunner of this serverless world is Amazon AWS. In the remainder of this article you will create a simple email-list form API that run without the need for a dedicated server.

Lambda

In AWS the service that executes code for you is exposed under a service called Lambda. A typical Lambda create form requires a function name, the language the code will be written in and a role to execute that function under.

A basic Lambda create form

At the time of writing this the languages supported by Lambda are:

  • C# (.NET Core 1.0, 2.0 and 2.1)
  • Go 1.x
  • Java 8
  • NodeJs (4.3, 6.10, 8.10)
  • Python (2.7, 3.6)

Once you fill out the form and hit Create Function your new function will be created and the subsequent screen will let you add code and test it out. There are a few other sections of interest on the edit function page.

Triggers and Resources

Triggers are the primary way in which you will interact with your Lambda functions and they represent actions that will cause your lambda function to execute.

This view shows you the triggers and the resources used by the Lambda

A few examples of triggers:

  • Whenever a file is written to S3 storage bucket
  • Whenever a log is written to CloudWatch
  • Whenever a request to API Gateway is received

As you can see, you can integrate Lambda functions into many of the other cloud-based resources that AWS offers which makes for a compelling ecosystem. If you do not wish to integrate your function with any service you can still execute Lambda functions directly via the AWS SDK. This allows your existing applications to still take advantage of serverless functions without having to rewrite them entirely.

Resources are the other side of the interaction and represent the services your Lambda will interact with. A common example is a Lambda function making requests to a database (RDS/Dynamo) or a data storage utility (S3). Each of these resource integrations will have to have the proper authorization to perform these tasks.

Environment Variables

One other important section of the edit screen is where you will manage environment variables that your function will have access to. These environment variables are a useful way to provide your function with variables that may change or should not be hard-coded with your code. These could be URLs, string values, API keys etc. In this example we will not use environment variables but they are a useful tool when working with Lambdas in general.

Where you would set environment variables

Services our signup form will use

With the introduction of Lambda out of the way let’s dig in and provide an implementation for a common task when dealing with a web page: a signup form. A signup form will take in the user’s email address and add them to a list that you will use at a later date to communicate with them. We will of course be using a Lambda function to perform this task but we will also use four other services.

API Gateway

API Gateway is a service for exposing an API endpoint that your users/applications can interact with. Behind the scenes these requests will ultimately get delivered to a Lambda function where you can perform any task you want. In our case we will be writing to a Dynamo table.

Dynamo

Dynamo is a document storage system that is serverless and very fast. If you are familiar with other NoSQL concepts the idea will be straightforward but if not just know what you will be posting documents (like JSON) to this service and reading data from it later.

CloudWatch

Since working in a serverless world means you are giving up control of a lot of things you will need a way to log events and track them so you know what’s happening in your system. To do that you will setup log events for Lambda which will make it easier to track down issues when and if they occur. CloudWatch is the logging service provided by AWS and each service you run will have its own section of logs. I strongly recommend you setup logging no matter which service you are interacting with.

IAM

IAM is AWSs tool for handling access across services. In this example the Lambda function you create will need to have permission to write to Dynamo. You will be guided through that process when you start creating resources shortly.

Now that the services you will be using have been outlined you can jump right in and begin making your signup form.

Step 1: Setup Dynamo

The first thing you will do is setup the Dynamo table to store your user’s emails. Head to Dynamo and click Create Table. For this example you will use the following settings:

The partition key is similar to a traditional Primary Key in other RDMS systems. This value will be unique across all entries in this table.

The rest of the settings you can leave as defaults for now. Finally click the Create button and your new table will be created. Next up you will create the Lambda function that will put emails into the newly created Dynamo table.

Note: Since this is a document store you do not have to specify other columns you will be using. The Lambda function and the post request itself will determine the columns used in each posted document.

Step 2: Create the Lambda function

Now that the Dynamo table has been created you will now create a new Lambda function that will perform a Put on the table to add new entries into our list. Head to Lambda and hit Create Function.

You will be leaving the setting as Author from Scratch since this guide is an exploration into setting these services up. Use the configuration as seen below:

Lambda Create Function screen

Next hit Create Function and the function will be created, sending you to the edit screen. On this screen scroll down to the Function Code section and input the following code and click Save:

const AWS = require("aws-sdk");
// Create the DynamoDB service object
const client = new AWS.DynamoDB.DocumentClient();
exports.handler = (event, context, callback) => {
// Parse the request body from the API
let body = JSON.parse(event.body);
    // Setup parameters we will store in Dynamo
const doc = {
TableName:"EmailList",
Item:{
"email": body.email,
"ip_address": event.requestContext.identity.sourceIp,
"date": (new Date()).toISOString()
}
};
    // Write to Dynamo
client.put(doc, (err, data) => {
if (err) {
console.error("Unable to write to Dynamo. Error JSON:", JSON.stringify(err, null, 2));
callback(null, {
"statusCode": 500,
"headers": { "Content-Type": "application/json" },
"body": JSON.stringify({"error": "Could not save your email address at this time."})
});
} else {
console.log("Added new entry:", JSON.stringify(doc, null, 2));
callback(null, {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": JSON.stringify({"email": body.email})
});
}
});
};

Code breakdown

Breaking this code down we have a few mainsections. The first at the top is the inclusion of the AWS SDK and the Dynamo document client.

const AWS = require("aws-sdk");
// Create the DynamoDB service object
const client = new AWS.DynamoDB.DocumentClient();

Next comes the main lamba function that will be the starting point of the execution. This code is called automatically by Lambda.

exports.handler = (event, context, callback) => {

Next the POST body is parsed to get the email posted and we setup the payload that will be posted to Dynamo. The payload to Dynamo requires two pieces of information — the table name to post to (TableName — in this case EmailList) and the data to post (Item).

For this example we will be posting the email, the date and the IP address of the user signing up.

// Parse the request body from the API
let body = JSON.parse(event.body);
// Setup parameters we will store in Dynamo
const doc = {
TableName:"EmailList",
Item:{
"email": body.email,
"ip_address": event.requestContext.identity.sourceIp,
"date": (new Date()).toISOString()
}
};

Finally the put request is issued to Dynamo. Both the error and success are logged and in both cases we return a JSON payload: either an error response or the email address saved.

// Write to Dynamo
client.put(doc, (err, data) => {
if (err) {
console.error("Unable to write to Dynamo. Error JSON:", JSON.stringify(err, null, 2));
callback(null, {
"statusCode": 500,
"headers": { "Content-Type": "application/json" },
"body": JSON.stringify({"error": "Could not save your email address at this time."})
});
} else {
console.log("Added new entry:", JSON.stringify(doc, null, 2));
callback(null, {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": JSON.stringify({"email": body.email})
});
}
});

Note: The format of the response here is important. API Gateway will return a 502 if the JSON format is not correct.

Testing a Lambda function

When you save a Lambda function you can test it using the Test button at the top of the screen. Since this example integrates with API Gateway you can create a new Test event using the API Gateway AWS Proxy event template.

Creating a new Lambda test event

The only modification you will need to make for this guide is to change the event.body value from

"body": "{\"test\":\"body\"}",

to

"body": "{\"email\":\"test@test.com\"}",

Once the test event is setup you can simply select the event on the edit screen and hit Test.

Step 3: Setting up permissions

If you tried to test the function as it stands you would have encountered an authorization error since the new Lambda function is not authorized to issue requests to Dynamo. To fix that we must assign a new policy to the role the Lambda function created in Step 2 that gives it permission to make a putItem request to the Dynamo table that was created.

First head to IAM and search for emailList at the top of the screen.

Search for emailList to find the role created previously

Clicking on the Role name will lead you to an overview page where you will be able to attach a new policy to this role.

Click the Add inline policy link. This will open up a policy create page where you will craft the policy for Dynamo access.

First click Choose a service at the top of the page, search for Dynamo and click the matching result. Once you select Dynamo the Actions section will refresh and list which actions you want to allow access to for the Lambda policy. Expand the Write section and select PutItem:

Selecting the actions we want our new policy to grant

After selecting the Actions you will then click the Resources section where it says”

You chose actions that require the table resource type.

This section will expand and give you an Add ARN option. An ARN is a way to identify a specific instance of a service in the AWS ecosystem. Click the Add ARN option and you will be taken to a modal window where you will craft your ARN:

Creating an ARN

Note: To find your Account ID, go to your Account page and you will see your Account ID listed at the top.

Click Add and now that you have set the Action and the Resource you can click Review Policy which will allow you to name your new policy and create it:

Naming your new policy

Once you will out this form you can click Create policy and your policy will now be attached to your Lambda. Finally head back to your Lambda function and click Test which should now successfully load an entry into your Dynamo table. To check, head to the Dynamo section, click Tables on the left, select the EmailList table and click the Items tab.

Step 4: Setup API Gateway

Now that you have a working Lambda function writing to Dynamo you can now setup the API Gateway that will expose this to the outside world. To create a new API head to the API Gateway section of AWS and click the Create API button.

In the settings section of the create page fill out the form to match the screenshot below:

Create the new API

After filling out the form click Create API and you will taken to the main API page where you can setup resources and methods. Resources are specific endpoints in your API (like /emails) while methods are actions you can perform on them (traditional HTTP methods like GET, POST, etc.).

For this API tutorial you will be creating a /emails resource that has a POST method. To set that up first create the /emails resource:

Create a new API resource

Click the Configure as proxy resource checkbox in the next screen which will fill out the rest for you. A proxy resource tells API Gateway that every request to the API will be forwarded to the Lambda with a bunch of other contextual information (like IP address). In more advanced API setups you can do routing and many other things using this setup so you don’t have to setup all the resources yourself.

If you want to call this API from Javascript make sure to check the Enable API Gateway CORS:

Setting up an API proxy resource

Since you selected a proxy resource you will automatically be taken to the integration page. This is where you will select the Lambda function you already created. Simply type emailListApi in the Lambda Function textbox to select your Lambda function:

Setting up a proxy Lambda function

Note: If you see a popup that says you will be granting API Gateway access to call the lambda just press okay

Now that the proxy resource and method is setup your next step is to deploy the API. To do that select Deploy API under the Actions dropdown and name your Stage name list (or whatever you want). The stage name will end up being a part of the URL so keep that in mind:

Setting up a new API Gateway stage

Once deployed you can go to the Stages section on the left and you will see an entry for the stage you setup. Clicking the stage will give you the URL that you can use to test your API:

Your stage will give you the URL you can use to hit your API

Making a POST request to https://xxxxxxx.execute-api-us-east-1.amazonaws.com/list/emails with a body of

{"email": "your@email.com"}

Should now yield a new entry in your Dynamo table list.

Note: Make sure your Content-Type and Accept headers are set to application/json

Congratulations — you now have a serverless API up and running! Now that you have some experience setting up serverless resources you can move onto advanced concepts like API tokens/authorization, custom domain names for APIs and building a utility/API to send emails to your list of email addresses.

Like what you read? Give Derek Woods a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.