Building A Serverless App 1481055780

Download as pdf or txt
Download as pdf or txt
You are on page 1of 11

Live!

Lab

Building a
Serverless
Application
End-to-End
Contents Need Help?

Create the Code 1


Linux Academy
Index Code Breakdown 5 Community

Add to S3 7

Create the CoudFormation Template 7 ... and you can


always send in a
Deploy CloudFormation 8 support ticket on
our website to talk
to an instructor!

Lab Connection Information


Labs may take up to five minutes to build
Access to an AWS Console is provided on the Live!
Lab page, along with your login credentials
Ensure you are using the N. Virginia region
Labs will automatically end once the alloted amount
of time finishes
Building a Serverless Application End-to-End Linux Academy

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.

Create the Code


On your workstation, create a source directory, if desired, then create two files: index.js and template.
js. Open the template.js and copy in the sample boilerplate template for generating an HTML webpage:

'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

Request Counter: <span class="">' + count + '</span></p></li>' : ''}


</ul>
</div>
</div>
</section>
</body>
</html>';

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

Index Code Breakdown


'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!"
},

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.

requestShould400 = event => typeof event === 'undefined' ||


typeof event.pathParameters === 'undefined' ||
typeof event.pathParameters.request === 'undefined',
requestShould404 = (event, acceptablePaths) => acceptablePaths
.indexOf(event.pathParameters.request) === -1,

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."

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;
},

-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.

goodRequest = (counter, fileName) => {


const payload = {
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'
}
});
}
};

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.

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

-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:

[user@workstation]~$ zip code.zip index.js template.js

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.

Create the CoudFormation Template


On your workstation, create a file called samlab.ya ml in which to build the CloudFormation template. As
before, we have a pre-made template for you to use:

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.

Save the file.

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.

Give the stack a Stack name. We called ours samlab. 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.

That's all there is to it!

-9-

You might also like