Building A Serverless App 1481055780
Building A Serverless App 1481055780
Lab
Building a
Serverless
Application
End-to-End
Contents Need Help?
Add to S3 7
Using AWS's new server application model, we are going to build out a simple application to serve a webpage
or JSON body. This application will also connect to DynamoDB. Afterwards, we create a CloudFormation
template to deploy any needed components, then deploy the application to AWS.
'use strict';
module.exports = (title, response, count) => '<style>
body {
position: relative;
font-family: 'Open Sans', sans-serif;
margin: 0 auto;
}
h1 {
margin-bottom: 20px;
line-height: 1.1;
font-family: 'Open Sans', sans-serif;
font-weight: 400;
color: White;
}
p {
font-weight: 300;
}
.navbar {
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0,
0, 0, .12);
font-family: 'Open Sans', sans-serif;
}
.navbar-dark .navbar-brand {
color: #fff !important;
}
#home .view {
height: 100vh;
}
.full-bg-img,
.view .content,
.view .mask {
left: 0;
overflow: hidden;
height: 100%;
width: 100%;
top: 0;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
-1-
Building a Serverless Application End-to-End Linux Academy
.full-bg-img {
color: #fff;
}
#home p,
.title {
font-weight: 100;
}
.primary-color {
background-color: #4285F4!important;
}
.navbar-fixed-top {
top: 0;
}
.navbar-fixed-bottom,
.navbar-fixed-top {
position: fixed;
right: 0;
left: 0;
z-index: 1030;
}
.header,
.footer {
display: none;
}
.title {
display: block;
}
</style>
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>${title}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/
css?family=Open+Sans:300,400" rel="stylesheet">
</head>
<body>
<!--Navbar-->
<nav class="navbar navbar-fixed-top navbar-dark primary-color">
<div class="container" style="text-align:center;padding:20px
0;">
<span style="color:White;">${title}</span>
</div>
</nav>
<section id="home">
<div class="view primary-color">
<div class="full-bg-img flex-center">
<ul style="list-style: none;-webkit-padding-start:0;
text-align: center;">
<li>
<h1 class="h1-responsive title">${response}</h1>
</li>
${(typeof count !== 'undefined') ? '<li><p>DynamoDB
-2-
Building a Serverless Application End-to-End Linux Academy
This template takes three parameters, which it then feeds into the HTML code in the defined places. Save
and open index.js.
We want to copy in the next set of code the Javascript function that Lambda will call upon.
'use strict';
const AWS = require('aws-sdk'),
docClient = new AWS.DynamoDB.DocumentClient({
region: 'us-east-1'
}),
generateHtml = require('./template'),
acceptablePaths = ['status.ht ml', 'status.json'],
strings = {
title: "CloudFormation+Serverless Application Model Lab",
notFound: "How'd you get here?",
badRequest: "I don't know what you want.",
success: "You've successfully deployed Lambda/API Gateway/DynamoDB!"
},
requestShould400 = event => typeof event === 'undefined' ||
typeof event.pathParameters === 'undefined' ||
typeof event.pathParameters.request === 'undefined',
requestShould404 = (event, acceptablePaths) => acceptablePaths
.indexOf(event.pathParameters.request) === -1,
badRequest = event => {
const baseResponse = {
headers: {
"Content-Type": 'text/html'
}
};
if (requestShould400(event)) {
return Object.assign({}, baseResponse, {
statusCode: 400,
body: generateHtml(strings.title, strings.badRequest)
});
}
if (requestShould404(event, acceptablePaths)) {
return Object.assign({}, baseResponse, {
statusCode: 404,
body: generateHtml(strings.title, strings.notFound)
});
}
return null;
},
goodRequest = (counter, fileName) => {
const payload = {
-3-
Building a Serverless Application End-to-End Linux Academy
title: strings.title,
response: strings.success,
count: counter
},
baseResponse = {
statusCode: 200
},
fileFormat = fileName.split('.')[1];
switch (fileFormat) {
case 'json':
return Object.assign({}, baseResponse, {
body: JSON.stringify(payload),
headers: {
"Content-Type": 'application/json'
}
});
case 'html':
return Object.assign({}, baseResponse, {
body: generateHtml(payload.title, payload.response, payload.
count),
headers: {
"Content-Type": 'text/html'
}
});
}
};
exports.handler = (event, context, callback) => {
if (badRequest(event)) {
context.succeed(badRequest(event));
} else {
const tableName = process.env.TABLE_NAME;
docClient.get({
Key: {
id: 'requests'
},
TableName: tableName
}).promise().then(data => {
const counter = (!data.Item) ? 1 : data.Item.count + 1;
docClient.put({
Item: {
id: 'requests',
count: counter
},
TableName: tableName
}).promise()
.then(() => context.succeed(goodRequest(counter, event.
pathParameters.request)))
.catch(err => context.fail(err))
})
.catch(err => context.fail(err));
}
}
-4-
Building a Serverless Application End-to-End Linux Academy
The file starts with the required dependencies (const AWS = require('aws-sdk'),), then creates a
new docClient object to work with DynamoDB. The generateHtml = require('./template'),
then calls to the boilerplate template we created, followed by the accepted paths (wherein paths not on this
list return a File not found error) and template strings.
This next segment of code uses logic to determine if a request to the server matches the paths defined in
the initial code segment. If it does not, the request is considered a "bad" request (requestShould400);
otherwise, the request is validated as a "good request."
-5-
Building a Serverless Application End-to-End Linux Academy
Next, we define the response to a bad request using a wrapper function. Here, depending on the response
from the above requestShould400 and requestShould404 functions, the function either generates a
bad request HTML file or a 404 file not found HTML file.
We now have the function called for a "good request." This takes two variables, and generates a payload
based upon the file format. Should the status call for HTML, it generates an HTML payload; if it requires
JSON, the JSON payload is instead generated.
-6-
Building a Serverless Application End-to-End Linux Academy
},
TableName: tableName
}).promise()
.then(() => context.succeed(goodRequest(counter, event.
pathParameters.request)))
.catch(err => context.fail(err))
})
.catch(err => context.fail(err));
}
}
Finally, we have the exports.handler section, which is executed every time the Lambda code is used,
regardless of request. If it is a bad request, it responds to the client as a bad request; otherwise, it specifies
the DynamoDB table and runs a DynamoDB request, retreiving from the table something with the ID of
''requests'', getting the output, data, and assigning it to counter, which will either be 1 if there are no
matches, or the 'data.Item.count + 1' if there are.
Then, should this succeed, it calls to the goodRequest function defined above.
Add to S3
Ensure your index.js file is saved. Then zip both the template.js and index.js files so they can be
uploaded to S3 and used by CloudFormation.
For those on Linux or Mac, you can zip the files using zip; this may need to be installed on Linux systems:
Now, open your AWS Console using the credentials provided on the Live! Lab page and navigate to the S3
dashboard. A bucket has already been created for you it contains your Linux Academy username. Click
on the bucket, then select Upload to upload your code.zip file we just created. Start Upload.
AWSTemplateFormatVersion: '2010-09-09'
Description: Create a Serverless stack with CloudFormation using
Serverless Application Model
Transform: AWS::Serverless-2016-10-31
Resources:
GetFunction:
Type: AWS::Serverless::Function
Description: Use a lambda function to retrieve state from DynamoDB
and return the result in HTML or JSON
-7-
Building a Serverless Application End-to-End Linux Academy
Properties:
Handler: index.handler
Runtime: nodejs4.3
CodeUri: s3://samlab/code.zip
Policies: AmazonDynamoDBFullAccess
Environment:
Variables:
TABLE_NAME: !Ref Table
Events:
GetResource:
Type: Api
Properties:
Path: /{request+}
Method: get
Table:
Type: AWS::Serverless::SimpleTable
The template specifies the format version and description. The Transform: line allows us to use the
AWS serverless application model. We next specify Resources. The initial GetFunction uses a Lambda
function to retrieve a state from DynamoDB and return the result -- the index.js file we created earlier.
Properties calls directly to the handler defined in the index.js code, as well as the CodeUri to
access the code.zip file uploaded to our S3 bucket. You need to update this line to reflect the name of
your assigned bucket. The template also defines the used policy, then, under Variables, references the
environment variable created at DynamoDB creation this is farther defined under the Table: segment
of the code.
Finally, the Events: area sets up the API gateway and allows requests to pass into the function to be
managed within the code itself.
Deploy CloudFormation
Return to the AWS Console, and select CloudFormation. Press Create New Stack. We want to Choose a
template, Upload template to Amazon S3, then select the samlab.yaml file we just created. Next.
Press Next again to bypass the next page, then, under Capabilities, check both I acknowledge that AWS
CloudFormation might create IAM resources and I acknowledge that AWS CloudFormation might create
IAM resources with custom names. Press Create Change Set, under Transform to see what changes may
occur. Execute.
Wait for the stack to spin up (CREATE_COMPLETE, under Status), then navigate to the API Gateway. We
can see our samlab. Select it. From here, we can view the available resources, in particular the request+
resource, with the GET request underneath it. Select the GET request to see the setup, wherein a Lambda
proxy calls the Lambda function. Should you click the Lambda function itself, you can view the code and
-8-
Building a Serverless Application End-to-End Linux Academy
environment variables. Click Test to run the code as a test. You should receive a 400 error.
Return to the samlab API Gateway page, then click on Stages on the left menu. Here is where the prod and
stage stages are visible. Click on the GET request under Stage, and select the Invoke URL URL. This pops
upon the 404 message. Replace the text after Stage/ in the URL with status.html to see the success message.
Should you refresh, you can watch the counter increase.
Alternatively, you can change the status.html to status.json to view the JSON body of the response. Notice
the DynamoDB counter still increases.
Return to the AWS Console, and navigate to the DynamoDB dashboard. Select Tables to view the table
created by the lab.
Should you want to now delete the stack, return to the CloudFormation dashboard and select Delete Stack,
under Actions with the stack selected.
-9-