Serverless Application with AWS

  • AWS DynamoDB as the database
  • AWS Lambda to create functions that will read and write from/to the database
  • AWS API Gateway to create the REST API that the web application will use
  • AWS S3 to host the web application
  • AWS CloudFront to deliver the web application from a location near to the user’s location.

How it works

The API works with two data structures, course and author:

Course:
{
id: "web-components-shadow-dom",
title: "Web Component Fundamentals",
watchHref: "http://www.pluralsight.com/courses/web-components-shadow-dom",
authorId: "cory-house",
length: "5:10",
category: "HTML5"
}
Author:
{
id: 'cory-house',
firstName: 'Cory',
lastName: 'House'
}

Storing data in DynamoDB

DynamoDB is a fully-managed NoSQL database that stores the data in key-value pairs, like a JSON object:

{
"ID": 1,
"Title": "Introduction to Angular 5",
"Category": "web-dev"
}
  • Table name: courses
  • Primary key: id
  • Table name: authors
  • Primary key: id
{
"id": "cory-house",
"firstName": "Cory",
"lastName": "House"
},
{
"id": "samer-buma",
"firstName": "Samer",
"lastName": "Buma"
},
{
"id": "deborah-kurata",
"firstName": "Deborah",
"lastName": "Kurata"
}

Creating lambda functions

AWS Lambda is a service that allows you to run functions upon certain events, for example, when data is inserted in a DynamoDB table or when a file is uploaded to S3.

  • get-all-authors to return all the users in the database
  • get-all-courses to return all the courses in the database
  • get-course to return only one course
  • save-course to create a new course
  • update-course to update a course
  • delete-course to delete a course
  • Name: get-all-authors
  • Runtime: Node.js 6.10 (or a superior version)
  • Role: Create a custom role
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Scan",
"dynamodb:UpdateItem"
],
"Resource": "arn:aws:dynamodb:region:accountId:table/*"
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "dynamodb:Scan",
"Resource": "<YOUR_ARN_FOR_THE_AUTHORS_TABLE>"
}
]
}
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB({
region: "<YOUR_AWS_REGION_CODE>",
apiVersion: "2012-08-10"
});

exports.handler = (event, context, callback) => {
const params = {
TableName: "authors"
};
dynamodb.scan(params, (err, data) => {
if (err) {
console.log(err);
callback(err);
} else {
callback(null, data);
}
});
};
  • event — an object that is used to pass data to the handler.
  • context— an object that provides runtime information about the Lambda function being executed.
  • callback— a function to return information to the caller (if it's called, otherwise the return value is null). The callback takes two parameters:
  • error — an object that provides the result of a failed execution. When a Lambda function succeeds, you can pass null as the first parameter.
  • result — an object that provides the result of a successful execution. The result provided must be JSON.stringify-compatible. If an error is provided, this parameter is ignored.
{
"Items": [
{
"id": {
"S": "cory-house"
},
"firstName": {
"S": "Cory"
},
"lastName": {
"S": "House"
}
},
{
"id": {
"S": "samer-buma"
},
"firstName": {
"S": "Samer"
},
"lastName": {
"S": "Buma"
}
},
{
"id": {
"S": "deborah-kurata"
},
"firstName": {
"S": "Deborah"
},
"lastName": {
"S": "Kurata"
}
}
],
"Count": 3,
"ScannedCount": 3
}
// ...

exports.handler = (event, context, callback) => {
...
dynamodb.scan(params, (err, data) => {
if(err) {
...
} else {
const authors = data.Items.map(item => {
return { id: item.id.S, firstName: item.firstName.S, lastName: item.lastName.S };
});
callback(null, authors);
}
});
};
[
{
id: "cory-house",
firstName: "Cory",
lastName: "House"
},
{
id: "samer-buma",
firstName: "Samer",
lastName: "Buma"
},
{
id: "deborah-kurata",
firstName: "Deborah",
lastName: "Kurata"
}
];
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "dynamodb:PutItem",
"Resource": "<YOUR_ARN_FOR_THE_COURSES_TABLE>"
}
]
}
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB({
region: "<YOUR_AWS_REGION_CODE>",
apiVersion: "2012-08-10"
});

const replaceAll = (str, find, replace) => {
return str.replace(new RegExp(find, "g"), replace);
};

exports.handler = (event, context, callback) => {
const id = replaceAll(event.title, " ", "-").toLowerCase();
const params = {
Item: {
id: {
S: id
},
title: {
S: event.title
},
watchHref: {
S: `http://www.pluralsight.com/courses/${id}`
},
authorId: {
S: event.authorId
},
length: {
S: event.length
},
category: {
S: event.category
}
},
TableName: "courses"
};
dynamodb.putItem(params, (err, data) => {
if (err) {
console.log(err);
callback(err);
} else {
callback(null, {
id: params.Item.id.S,
title: params.Item.title.S,
watchHref: params.Item.watchHref.S,
authorId: params.Item.authorId.S,
length: params.Item.length.S,
category: params.Item.category.S
});
}
});
};
{
"title": "Web Component Fundamentals",
"authorId": "cory-house",
"length": "5:10",
"category": "HTML5"
}
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB({
region: "<YOUR_AWS_REGION_CODE>",
apiVersion: "2012-08-10"
});

exports.handler = (event, context, callback) => {
const params = {
Item: {
id: {
S: event.id
},
title: {
S: event.title
},
watchHref: {
S: event.watchHref
},
authorId: {
S: event.authorId
},
length: {
S: event.length
},
category: {
S: event.category
}
},
TableName: "courses"
};
dynamodb.putItem(params, (err, data) => {
if (err) {
console.log(err);
callback(err);
} else {
callback(null, {
id: params.Item.id.S,
title: params.Item.title.S,
watchHref: params.Item.watchHref.S,
authorId: params.Item.authorId.S,
length: params.Item.length.S,
category: params.Item.category.S
});
}
});
};
{
"id": "web-component-fundamentals",
"title": "Web Component Fundamentals",
"authorId": "cory-house",
"length": "5:03",
"category": "HTML5",
"watchHref": "http://www.pluralsight.com/courses/web-components-shadow-dom"
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "dynamodb:Scan",
"Resource": "<YOUR_ARN_FOR_THE_COURSES_TABLE>"
}
]
}
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB({
region: "<YOUR_AWS_REGION_CODE>",
apiVersion: "2012-08-10"
});

exports.handler = (event, context, callback) => {
const params = {
TableName: "courses"
};
dynamodb.scan(params, (err, data) => {
if (err) {
console.log(err);
callback(err);
} else {
const courses = data.Items.map(item => {
return {
id: item.id.S,
title: item.title.S,
watchHref: item.watchHref.S,
authorId: item.authorId.S,
length: item.length.S,
category: item.category.S
};
});
callback(null, courses);
}
});
};
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "dynamodb:GetItem",
"Resource": "<YOUR_ARN_FOR_THE_COURSES_TABLE>"
}
]
}
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB({
region: "<YOUR_AWS_REGION_CODE>",
apiVersion: "2012-08-10"
});

exports.handler = (event, context, callback) => {
const params = {
Key: {
id: {
S: event.id
}
},
TableName: "courses"
};
dynamodb.getItem(params, (err, data) => {
if (err) {
console.log(err);
callback(err);
} else {
callback(null, {
id: data.Item.id.S,
title: data.Item.title.S,
watchHref: data.Item.watchHref.S,
authorId: data.Item.authorId.S,
length: data.Item.length.S,
category: data.Item.category.S
});
}
});
};
{
"id": "web-component-fundamentals"
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": "dynamodb:DeleteItem",
"Resource": "<YOUR_ARN_FOR_THE_COURSES_TABLE>"
}
]
}
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB({
region: "<YOUR_AWS_REGION_CODE>",
apiVersion: "2012-08-10"
});

exports.handler = (event, context, callback) => {
const params = {
Key: {
id: {
S: event.id
}
},
TableName: "courses"
};
dynamodb.deleteItem(params, (err, data) => {
if (err) {
console.log(err);
callback(err);
} else {
callback(null, data);
}
});
};
{
"id": "web-component-fundamentals"
}

Setting up API Gateway

API Gateway is a service that allows creating a REST API fully managed by AWS that acts as the front-end for other services.

{
"$schema": "http://json-schema.org/schema#",
"title": "CourseInputModel",
"type": "object",
"properties": {
"title": {"type": "string"},
"authorId": {"type": "string"},
"length": {"type": "string"},
"category": {"type": "string"}
},
"required": ["title", "authorId", "length", "category"]
}
{
"title": "Web Component Fundamentals",
"authorId": "cory-house",
"length": "5:03",
"category": "HTML5"
}
{
"id": $input.params('id'),
"title" : $input.json('$.title'),
"authorId" : $input.json('$.authorId'),
"length" : $input.json('$.length'),
"category" : $input.json('$.category'),
"watchHref" : $input.json('$.watchHref')
}
{
"title": "Web Component Fundamentals",
"authorId": "cory-house",
"length": "5:03",
"category": "HTML5",
"watchHref": "http://www.pluralsight.com/courses/web-components-shadow-dom"
}
{
"id": "$input.params('id')"
}

Cross Origin Resource Sharing (CORS)

Now, we need to add the CORS header to all the methods (the CORS option you chose when creating a resource only adds the CORS headers for the preflight request).

Hosting the app in S3

Download and Install npm

{
"Version":"2012-10-17",
"Statement":[
{
"Sid":"AddPerm",
"Effect":"Allow",
"Principal": "*",
"Action":["s3:GetObject"],
"Resource":["arn:aws:s3:::examplebucket/*"]
}
]
}

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store