Serverless Stack v3.4

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

Serverless Stack is an open source guide for building full-

stack produc6on ready apps using Serverless and React on


AWS. Create a note taking app from scratch using the
Serverless Framework and Create React App. Follow our
step-by-step tutorials with screenshots and code samples.
And use our forums if you have any ques6ons.

IMPORTANT: In some versions of Adobe Reader, copying and pas6ng code


from this PDF might miss some hyphen or dash characters. Please, double
check the code you are copying.

July 22, 2019 - v3.4


Table of Contents

Who is this guide for?


PREFACE What does this guide cover?

How to get help?

Part I - The Basics

What is Serverless?
INTRODUCTION What is AWS Lambda?

Why create Serverless apps?

Create an AWS account

Create an IAM user


SET UP YOUR AWS
What is IAM
ACCOUNT
What is an ARN

Configure the AWS CLI


Create a DynamoDB table

Create an S3 bucket for file uploads

SETTING UP THE Create a Cognito user pool


SERVERLESS BACKEND Create a Cognito test user

Set up the Serverless Framework


Add support for ES6/ES7 JavaScript

Add a create note API

Add a get note API

BUILDING A SERVERLESS Add a list all the notes API


REST API Add an update note API

Add a delete note API

Handle API Gateway CORS errors

Deploy the APIs

DEPLOYING THE Create a Cognito idenUty pool


BACKEND Cognito user pool vs idenUty pool

Test the APIs


Create a new React.js app
Add app favicons

Set up custom fonts

Set up Bootstrap

SETTING UP A REACT APP Handle routes with React Router


Create containers

Adding links in the navbar

Handle 404s

Configure AWS Amplify

Create a login page


Login with AWS Cognito

Add the session to the state

Load the state from the session

Clear the session on logout

Redirect on login and logout

Give feedback while logging in

Create a signup page


Create the signup form

Signup with AWS Cognito


Add the create note page

BUILDING A REACT APP


Call the create API

Upload a file to S3

List all the notes


Call the list API

Display a note
Render the note form

Save changes to a note

Delete a note

Set up secure pages


Create a route that redirects

Use the redirect routes

Redirect on login
Deploy the Frontend
Create an S3 bucket

Deploy to S3

Create a CloudFront distribuUon

DEPLOYING A REACT APP Set up your domain with CloudFront


ON AWS Set up www domain redirect

Set up SSL

Deploy updates
Update the app

Deploy again

Part II - Automation

INTRODUCTION GeZng producUon ready

IniUalize the backend repo


CREATE A NEW BACKEND
Organize the backend repo
What is Infrastructure as Code?
Configure DynamoDB in Serverless

Configure S3 in Serverless

Configure Cognito User Pool in


INFRASTRUCTURE AS Serverless
CODE Configure Cognito IdenUty Pool in
Serverless

Use environment variables in Lambda


funcUons

Deploy your Serverless infrastructure

Working with 3rd party APIs


Setup a Stripe account
ADDING A STRIPE
Add a billing API
BILLING API
Load secrets from env.yml

Test the billing API

ADDING UNIT TESTS Unit tests in Serverless


AutomaUng Serverless deployments
SeZng up your project on Seed

Configure secrets in Seed


AUTOMATING
SERVERLESS Deploying through Seed
DEPLOYMENTS Set custom domains through Seed

Test the configured APIs

Monitoring deployments in Seed

IniUalize the frontend repo


CONNECT TO THE NEW
Manage environments in Create
BACKEND
React App

Create a seZngs page


Add Stripe keys to config
ADDING A BILLING FORM
Create a billing form

Connect the billing form


AutomaUng React Deployments
Create a build script
AUTOMATING REACT APP
SeZng up your project on Netlify
DEPLOYMENTS
Custom Domains in Netlify

Frontend workflow

Wrapping up

Futher reading

TranslaUons
CONCLUSION
Giving back

Changelog

Staying up to date

Extra Credit
Organizing Serverless projects

Cross-stack references in Serverless


DynamoDB as a Serverless service

SERVERLESS S3 as a Serverless service


ARCHITECTURE API Gateway domains across
services

Cognito as a Serverless service

Deploying mulUple services in Serverless


API Gateway and Lambda Logs

Debugging Serverless API Issues

Serverless environment variables

Stages in Serverless Framework

Backups in DynamoDB

Configure mulUple AWS profiles


BACKEND Customize the Serverless IAM Policy

Mapping Cognito IdenUty Id and User


Pool Id

Connect to API Gateway with IAM auth

Serverless Node.js Starter

Package Lambdas with serverless-


bundle

Manage User Accounts in AWS Amplify


Handle Forgot and Reset Password
USER MANAGEMENT
Allow Users to Change Passwords

Allow Users to Change Their Email


Code SpliZng in Create React App

Environments in Create React App


FRONTEND
Facebook Login with Cognito using AWS
Amplify
Who Is This Guide For?
This guide is meant for full-stack developers or developers that would like to build full stack
serverless applica9ons. By providing a step-by-step guide for both the frontend and the
backend we hope that it addresses all the different aspects of building serverless applica9ons.
There are quite a few other tutorials on the web but we think it would be useful to have a
single point of reference for the en9re process. This guide is meant to serve as a resource for
learning about how to build and deploy serverless applica9ons, as opposed to laying out the
best possible way of doing so.

So you might be a backend developer who would like to learn more about the frontend
por9on of building serverless apps or a frontend developer that would like to learn more about
the backend; this guide should have you covered.

We are also catering this solely towards JavaScript developers for now. We might target other
languages and environments in the future. But we think this is a good star9ng point because it
can be really beneficial as a full-stack developer to use a single language (JavaScript) and
environment (Node.js) to build your en9re applica9on.

On a personal note, the serverless approach has been a giant revela9on for us and we wanted
to create a resource where we could share what we’ve learned. You can read more about us
here (/about.html). And check out a sample of what folks have built with Serverless Stack
(/showcase.html).

Let’s start by looking at what we’ll be covering.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/who-is-this-
guide-for/96)
What Does This Guide Cover?
To step through the major concepts involved in building web applica7ons, we are going to be
building a simple note taking app called Scratch (h;ps://demo2.serverless-stack.com).
It is a single page applica7on powered by a serverless API wri;en completely in JavaScript.
Here is the complete source for the backend
(h;ps://github.com/AnomalyInnova7ons/serverless-stack-demo-api) and the frontend
(h;ps://github.com/AnomalyInnova7ons/serverless-stack-demo-client). It is a rela7vely simple
applica7on but we are going to address the following requirements.

Should allow users to signup and login to their accounts


Users should be able to create notes with some content
Each note can also have an uploaded file as an a;achment
Allow users to modify their note and the a;achment
Users can also delete their notes
The app should be able to process credit card payments
App should be served over HTTPS on a custom domain
The backend APIs need to be secure
The app needs to be responsive

We’ll be using the AWS PlaPorm to build it. We might expand further and cover a few other
plaPorms but we figured the AWS PlaPorm would be a good place to start.

Technologies & Services


We’ll be using the following set of technologies and services to build our serverless
applica7on.

Lambda (h;ps://aws.amazon.com/lambda/) & API Gateway (h;ps://aws.amazon.com/api-


gateway/) for our serverless API
DynamoDB (h;ps://aws.amazon.com/dynamodb/) for our database
Cognito (h;ps://aws.amazon.com/cognito/) for user authen7ca7on and securing our APIs
S3 (h;ps://aws.amazon.com/s3/) for hos7ng our app and file uploads
CloudFront (h;ps://aws.amazon.com/cloudfront/) for serving out our app
Route 53 (h;ps://aws.amazon.com/route53/) for our domain
Cer7ficate Manager (h;ps://aws.amazon.com/cer7ficate-manager) for SSL
React.js (h;ps://facebook.github.io/react/) for our single page app
React Router (h;ps://github.com/ReactTraining/react-router) for rou7ng
Bootstrap (h;p://getbootstrap.com) for the UI Kit
Stripe (h;ps://stripe.com) for processing credit card payments
Seed (h;ps://seed.run) for automa7ng Serverless deployments
Netlify (h;ps://netlify.com) for automa7ng React deployments
GitHub (h;ps://github.com) for hos7ng our project repos.

We are going to be using the free *ers for the above services. So you should be able to sign
up for them for free. This of course does not apply to purchasing a new domain to host your
app. Also for AWS, you are required to put in a credit card while crea7ng an account. So if you
happen to be crea7ng resources above and beyond what we cover in this tutorial, you might
end up ge`ng charged.
While the list above might look daun7ng, we are trying to ensure that upon comple7ng the
guide you’ll be ready to build real-world, secure, and fully-func*onal web apps. And don’t
worry we’ll be around to help!

Requirements
You need Node v8.10+ and NPM v5.5+ (h;ps://nodejs.org/en/). You also need to have basic
knowledge of how to use the command line.

How This Guide Is Structured


The guide is split into two separate parts. They are both rela7vely standalone. The first part
covers the basics while the second covers a couple of advanced topics along with a way to
automate the setup. We launched this guide in early 2017 with just the first part. The
Serverless Stack community has grown and many of our readers have used the setup
described in this guide to build apps that power their businesses.

So we decided to extend the guide and add a second part to it. This is targe7ng folks that are
intending to use this setup for their projects. It automates all the manual steps from part 1 and
helps you create a produc7on ready workflow that you can use for all your serverless projects.
Here is what we cover in the two parts.

Part I

Create the notes applica7on and deploy it. We cover all the basics. Each service is created by
hand. Here is what is covered in order.

For the backend:

Configure your AWS account


Create your database using DynamoDB
Set up S3 for file uploads
Set up Cognito User Pools to manage user accounts
Set up Cognito Iden7ty Pool to secure our file uploads
Set up the Serverless Framework to work with Lambda & API Gateway
Write the various backend APIs

For the frontend:


Set up our project with Create React App
Add favicons, fonts, and a UI Kit using Bootstrap
Set up routes using React-Router
Use AWS Cognito SDK to login and signup users
Plugin to the backend APIs to manage our notes
Use the AWS JS SDK to upload files
Create an S3 bucket to upload our app
Configure CloudFront to serve out our app
Point our domain with Route 53 to CloudFront
Set up SSL to serve our app over HTTPS

Part II

Aimed at folks who are looking to use the Serverless Stack for their day-to-day projects. We
automate all the steps from the first part. Here is what is covered in order.

For the backend:

Configure DynamoDB through code


Configure S3 through code
Configure Cognito User Pool through code
Configure Cognito Iden7ty Pool through code
Environment variables in Serverless Framework
Working with the Stripe API
Working with secrets in Serverless Framework
Unit tests in Serverless
Automa7ng deployments using Seed
Configuring custom domains through Seed
Monitoring deployments through Seed

For the frontend

Environments in Create React App


Accep7ng credit card payments in React
Automa7ng deployments using Netlify
Configure custom domains through Netlify

We think this will give you a good founda7on on building full-stack produc7on ready
serverless applica7ons. If there are any other concepts or technologies you’d like us to cover,
feel free to let us know on our forums (h;ps://discourse.serverless-stack.com).

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/what-does-
this-guide-cover/83)
How to Get Help?
In case you find yourself having problems with a certain step, we want to make sure that we
are around to help you fix it and figure it out. There are a few ways to get help.

We use Discourse forum topics (hAps://discourse.serverless-stack.com) as our comments


and we’ve helped resolve quite a few issues in the past. So make sure to check the
comments under each chapter to see if somebody else has run into the same issue as you
have.
Post in the comments for the specific chapter detailing your issue and one of us will
respond.

This enJre guide is hosted on GitHub (hAps://github.com/AnomalyInnovaJons/serverless-


stack-com). So if you find an error you can always:

Open a new issue (hAps://github.com/AnomalyInnovaJons/serverless-stack-


com/issues/new)
Or if you’ve found a typo, edit the page and submit a pull request!

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/how-to-get-
help/95)
What is Serverless?
Tradi&onally, we’ve built and deployed web applica&ons where we have some degree of
control over the HTTP requests that are made to our server. Our applica&on runs on that
server and we are responsible for provisioning and managing the resources for it. There are a
few issues with this.

1. We are charged for keeping the server up even when we are not serving out any requests.

2. We are responsible for up&me and maintenance of the server and all its resources.

3. We are also responsible for applying the appropriate security updates to the server.

4. As our usage scales we need to manage scaling up our server as well. And as a result
manage scaling it down when we don’t have as much usage.

For smaller companies and individual developers this can be a lot to handle. This ends up
distrac&ng from the more important job that we have; building and maintaining the actual
applica&on. At larger organiza&ons this is handled by the infrastructure team and usually it is
not the responsibility of the individual developer. However, the processes necessary to
support this can end up slowing down development &mes. As you cannot just go ahead and
build your applica&on without working with the infrastructure team to help you get up and
running. As developers we’ve been looking for a solu&on to these problems and this is where
serverless comes in.

Serverless Computing
Serverless compu&ng (or serverless for short), is an execu&on model where the cloud provider
(AWS, Azure, or Google Cloud) is responsible for execu&ng a piece of code by dynamically
alloca&ng the resources. And only charging for the amount of resources used to run the code.
The code is typically run inside stateless containers that can be triggered by a variety of events
including hQp requests, database events, queuing services, monitoring alerts, file uploads,
scheduled events (cron jobs), etc. The code that is sent to the cloud provider for execu&on is
usually in the form of a func&on. Hence serverless is some&mes referred to as “Func&ons as a
Service” or “FaaS”. Following are the FaaS offerings of the major cloud providers:
AWS: AWS Lambda (hQps://aws.amazon.com/lambda/)
MicrosoX Azure: Azure Func&ons (hQps://azure.microsoX.com/en-us/services/func&ons/)
Google Cloud: Cloud Func&ons (hQps://cloud.google.com/func&ons/)

While serverless abstracts the underlying infrastructure away from the developer, servers are
s&ll involved in execu&ng our func&ons.

Since your code is going to be executed as individual func&ons, there are a couple of things
that we need to be aware of.

Microservices
The biggest change that we are faced with while transi&oning to a serverless world is that our
applica&on needs to be architectured in the form of func&ons. You might be used to deploying
your applica&on as a single Rails or Express monolith app. But in the serverless world you are
typically required to adopt a more microservice based architecture. You can get around this by
running your en&re applica&on inside a single func&on as a monolith and handling the rou&ng
yourself. But this isn’t recommended since it is beQer to reduce the size of your func&ons.
We’ll talk about this below.

Stateless Functions
Your func&ons are typically run inside secure (almost) stateless containers. This means that
you won’t be able to run code in your applica&on server that executes long aXer an event has
completed or uses a prior execu&on context to serve a request. You have to effec&vely
assume that your func&on is invoked in a new container every single &me.

There are some subtle&es to this and we will discuss in the What is AWS Lambda
(/chapters/what-is-aws-lambda.html) chapter.

Cold Starts
Since your func&ons are run inside a container that is brought up on demand to respond to an
event, there is some latency associated with it. This is referred to as a Cold Start. Your
container might be kept around for a liQle while aXer your func&on has completed execu&on.
If another event is triggered during this &me it responds far more quickly and this is typically
known as a Warm Start.
The dura&on of cold starts depends on the implementa&on of the specific cloud provider. On
AWS Lambda it can range from anywhere between a few hundred milliseconds to a few
seconds. It can depend on the run&me (or language) used, the size of the func&on (as a
package), and of course the cloud provider in ques&on. Cold starts have dras&cally improved
over the years as cloud providers have goQen much beQer at op&mizing for lower latency
&mes.

Aside from op&mizing your func&ons, you can use simple tricks like a separate scheduled
func&on to invoke your func&on every few minutes to keep it warm. Serverless Framework
(hQps://serverless.com) which we are going to be using in this tutorial has a few plugins to
help keep your func&ons warm (hQps://github.com/FidelLimited/serverless-plugin-warmup).

Now that we have a good idea of serverless compu&ng, let’s take a deeper look at what a
Lambda func&on is and how your code will be executed.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/what-is-
serverless/27)
What is AWS Lambda?
AWS Lambda (h,ps://aws.amazon.com/lambda/) (or Lambda for short) is a serverless
compu@ng service provided by AWS. In this chapter we are going to be using Lambda to build
our serverless applica@on. And while we don’t need to deal with the internals of how Lambda
works, it’s important to have a general idea of how your func@ons will be executed.

Lambda Specs
Let’s start by quickly looking at the technical specifica@ons of AWS Lambda. Lambda supports
the following run@mes.

Node.js: v10.15 and v8.10


Java 8
Python: 3.7, 3.6, and 2.7
.NET Core: 1.0.1 and 2.1
Go 1.x
Ruby 2.5
Rust

Each func@on runs inside a container with a 64-bit Amazon Linux AMI. And the execu@on
environment has:

Memory: 128MB - 3008MB, in 64 MB increments


Ephemeral disk space: 512MB
Max execu@on dura@on: 900 seconds
Compressed package size: 50MB
Uncompressed package size: 250MB

You might no@ce that CPU is not men@oned as a part of the container specifica@on. This is
because you cannot control the CPU directly. As you increase the memory, the CPU is
increased as well.

The ephemeral disk space is available in the form of the /tmp directory. You can only use
this space for temporary storage since subsequent invoca@ons will not have access to this.
We’ll talk a bit more on the stateless nature of the Lambda func@ons below.

The execu@on dura@on means that your Lambda func@on can run for a maximum of 900
seconds or 15 minutes. This means that Lambda isn’t meant for long running processes.

The package size refers to all your code necessary to run your func@on. This includes any
dependencies ( node_modules/ directory in case of Node.js) that your func@on might
import. There is a limit of 250MB on the uncompressed package and a 50MB limit once it has
been compressed. We’ll take a look at the packaging process below.

Lambda Function
Finally here is what a Lambda func@on (a Node.js version) looks like.

Here myHandler is the name of our Lambda func@on. The event object contains all the
informa@on about the event that triggered this Lambda. In the case of an HTTP request it’ll be
informa@on about the specific HTTP request. The context object contains info about the
run@me our Lambda func@on is execu@ng in. Ader we do all the work inside our Lambda
func@on, we simply call the callback func@on with the results (or the error) and AWS will
respond to the HTTP request with it.

Packaging Functions
Lambda func@ons need to be packaged and sent to AWS. This is usually a process of
compressing the func@on and all its dependencies and uploading it to a S3 bucket. And leeng
AWS know that you want to use this package when a specific event takes place. To help us
with this process we use the Serverless Framework (h,ps://serverless.com). We’ll go over this
in detail later on in this guide.

Execution Model
The container (and the resources used by it) that runs our func@on is managed completely by
AWS. It is brought up when an event takes place and is turned off if it is not being used. If
addi@onal requests are made while the original event is being served, a new container is
brought up to serve a request. This means that if we are undergoing a usage spike, the cloud
provider simply creates mul@ple instances of the container with our func@on to serve those
requests.

This has some interes@ng implica@ons. Firstly, our func@ons are effec@vely stateless. Secondly,
each request (or event) is served by a single instance of a Lambda func@on. This means that
you are not going to be handling concurrent requests in your code. AWS brings up a container
whenever there is a new request. It does make some op@miza@ons here. It will hang on to the
container for a few minutes (5 - 15mins depending on the load) so it can respond to
subsequent requests without a cold start.

Stateless Functions
The above execu@on model makes Lambda func@ons effec@vely stateless. This means that
every @me your Lambda func@on is triggered by an event it is invoked in a completely new
environment. You don’t have access to the execu@on context of the previous event.

However, due to the op@miza@on noted above, the actual Lambda func@on is invoked only
once per container instan@a@on. Recall that our func@ons are run inside containers. So when a
func@on is first invoked, all the code in our handler func@on gets executed and the handler
func@on gets invoked. If the container is s@ll available for subsequent requests, your func@on
will get invoked and not the code around it.

For example, the createNewDbConnection method below is called once per container
instan@a@on and not every @me the Lambda func@on is invoked. The myHandler func@on
on the other hand is called on every invoca@on.
var dbConnection = createNewDbConnection();

exports.myHandler = function(event, context, callback) {


var result = dbConnection.makeQuery();
callback(null, result);
};

This caching effect of containers also applies to the /tmp directory that we talked about
above. It is available as long as the container is being cached.

Now you can guess that this isn’t a very reliable way to make our Lambda func@ons stateful.
This is because we just don’t control the underlying process by which Lambda is invoked or its
containers are cached.

Pricing
Finally, Lambda func@ons are billed only for the @me it takes to execute your func@on. And it
is calculated from the @me it begins execu@ng @ll when it returns or terminates. It is rounded
up to the nearest 100ms.

Note that while AWS might keep the container with your Lambda func@on around ader it has
completed; you are not going to be charged for this.

Lambda comes with a very generous free @er and it is unlikely that you will go over this while
working on this guide.

The Lambda free @er includes 1M free requests per month and 400,000 GB-seconds of
compute @me per month. Past this, it costs $0.20 per 1 million requests and $0.00001667 for
every GB-seconds. The GB-seconds is based on the memory consump@on of the Lambda
func@on. For further details check out the Lambda pricing page
(h,ps://aws.amazon.com/lambda/pricing/).

In our experience, Lambda is usually the least expensive part of our infrastructure costs.

Next, let’s take a deeper look into the advantages of serverless, including the total cost of
running our demo app.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/what-is-aws-
lambda/308)
Why Create Serverless Apps?
It is important to address why it is worth learning how to create serverless apps. There are a
few reasons why serverless apps are favored over tradi8onal server hosted apps:

1. Low maintenance
2. Low cost
3. Easy to scale

The biggest benefit by far is that you only need to worry about your code and nothing else.
The low maintenance is a result of not having any servers to manage. You don’t need to
ac8vely ensure that your server is running properly, or that you have the right security updates
on it. You deal with your own applica8on code and nothing else.

The main reason it’s cheaper to run serverless applica8ons is that you are effec8vely only
paying per request. So when your applica8on is not being used, you are not being charged for
it. Let’s do a quick breakdown of what it would cost for us to run our note taking applica8on.
We’ll assume that we have 1000 daily ac8ve users making 20 requests per day to our API, and
storing around 10MB of files on S3. Here is a very rough calcula8on of our costs.

Service Rate Cost

Cognito Free[1] $0.00

API Gateway $3.5/M reqs + $0.09/GB transfer $2.20

Lambda Free[2] $0.00

$0.0065/hr 10 write units, $0.0065/hr 50 read


DynamoDB $2.80
units[3]

$0.023/GB storage, $0.005/K PUT, $0.004/10K GET,


S3 $0.24
$0.0025/M objects[4]

CloudFront $0.085/GB transfer + $0.01/10K reqs $0.86


Route53 $0.50 per hosted zone + $0.40/M queries $0.50

Cer8ficate Manager Free $0.00

Total $6.10

[1] Cognito is free for < 50K MAUs and $0.00550/MAU onwards.
[2] Lambda is free for < 1M requests and 400000GB-secs of compute.
[3] DynamoDB gives 25GB of free storage.
[4] S3 gives 1GB of free transfer.

So that comes out to $6.10 per month. Addi8onally, a .com domain would cost us $12 per
year, making that the biggest up front cost for us. But just keep in mind that these are very
rough es8mates. Real-world usage paeerns are going to be very different. However, these
rates should give you a sense of how the cost of running a serverless applica8on is calculated.

Finally, the ease of scaling is thanks in part to DynamoDB which gives us near infinite scale
and Lambda that simply scales up to meet the demand. And of course our front end is a simple
sta8c single page app that is almost guaranteed to always respond instantly thanks to
CloudFront.

Great! Now that you are convinced on why you should build serverless apps; let’s get started.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/why-create-
serverless-apps/87)
Create an AWS Account
Let’s first get started by crea/ng an AWS (Amazon Web Services) account. Of course you can
skip this if you already have one. Head over to the AWS homepage (hDps://aws.amazon.com)
and hit the Create a Free Account and follow the steps to create your account.

Next let’s configure your account so it’s ready to be used for the rest of our guide.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-an-aws-
account/88)
Create an IAM User
Amazon IAM (Iden-ty and Access Management) enables you to manage users and user
permissions in AWS. You can create one or more IAM users in your AWS account. You might
create an IAM user for someone who needs access to your AWS console, or when you have a
new applica-on that needs to make API calls to AWS. This is to add an extra layer of security
to your AWS account.

In this chapter, we are going to create a new IAM user for a couple of the AWS related tools
we are going to be using later.

Create User
First, log in to your AWS Console (hIps://console.aws.amazon.com) and select IAM from the
list of services.
Select Users.

Select Add User.


Enter a User name and check Programma.c access, then select Next: Permissions.

This account will be used by our AWS CLI (hIps://aws.amazon.com/cli/) and Serverless
Framework (hIps://serverless.com). They’ll be connec-ng to the AWS API directly and will not
be using the Management Console.
Select A5ach exis.ng policies directly.
Search for AdministratorAccess and select the policy, then select Next: Review.

We can provide a more fine-grained policy here and we cover this later in the Customize the
Serverless IAM Policy (/chapters/customize-the-serverless-iam-policy.html) chapter. But for
now, let’s con-nue with this.

Select Create user.


Select Show to reveal Secret access key.
Take a note of the Access key ID and Secret access key. We will be needing this later.

The concept of IAM pops up very frequently when working with AWS services. So it is worth
taking a beIer look at what IAM is and how it can help us secure our serverless setup.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-an-iam-
user/92)
What is IAM
In the last chapter, we created an IAM user so that our AWS CLI can operate on our account
without using the AWS Console. But the IAM concept is used very frequently when dealing
with security for AWS services, so it is worth understanding it in a bit more detail.
Unfortunately, IAM is made up of a lot of different parts and it can be very confusing for folks
that first come across it. In this chapter we are going to take a look at IAM and its concepts in
a bit more detail.

Let’s start with the official definiHon of IAM.

AWS Iden)ty and Access Management (IAM) is a web service that helps you securely control
access to AWS resources for your users. You use IAM to control who can use your AWS resources
(authen)ca)on) and what resources they can use and in what ways (authoriza)on).

The first thing to noHce here is that IAM is a service just like all the other services that AWS
has. But in some ways it helps bring them all together in a secure way. IAM is made up of a
few different parts, so let’s start by looking at the first and most basic one.

What is an IAM User


When you first create an AWS account, you are the root user. The email address and
password you used to create the account is called your root account credenHals. You can use
them to sign in to the AWS Management Console. When you do, you have complete,
unrestricted access to all resources in your AWS account, including access to your billing
informaHon and the ability to change your password.
Though it is not a good pracHce to regularly access your account with this level of access, it is
not a problem when you are the only person who works in your account. However, when
another person needs to access and manage your AWS account, you definitely don’t want to
give out your root credenHals. Instead you create an IAM user.

An IAM user consists of a name, a password to sign into the AWS Management Console, and
up to two access keys that can be used with the API or CLI.
By default, users can’t access anything in your account. You grant permissions to a user by
creaHng a policy and aNaching the policy to the user. You can grant one or more of these
policies to restrict what the user can and cannot access.

What is an IAM Policy?


An IAM policy is a rule or set of rules defining the operaHons allowed/denied to be performed
on an AWS resource.

Policies can be granted in a number of ways:

ANaching a managed policy. AWS provides a list of pre-defined policies such as


AmazonS3ReadOnlyAccess.
ANaching an inline policy. An inline policy is a custom policy created by hand.
Adding the user to a group that has appropriate permission policies aNached. We’ll look at
groups in detail below.
Cloning the permission of an exisHng IAM user.
As an example, here is a policy that grants all operaHons to all S3 buckets.

{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
}

And here is a policy that grants more granular access, only allowing retrieval of files prefixed
by the string Bobs- in the bucket called Hello-bucket .

{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::Hello-bucket/*",
"Condition": {"StringEquals": {"s3:prefix": "Bobs-"}}
}

We are using S3 resources in the above examples. But a policy looks similar for any of the
AWS services. It just depends on the resource ARN for Resource property. An ARN is an
idenHfier for a resource in AWS and we’ll look at it in more detail in the next chapter. We also
add the corresponding service acHons and condiHon context keys in Action and
Condition property. You can find all the available AWS Service acHons and condiHon
context keys for use in IAM Policies here
(hNps://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_acHonscondiHons.ht
ml). Aside from aNaching a policy to a user, you can aNach them to a role or a group.

What is an IAM Role


SomeHmes your AWS resources need to access other resources in your account. For example,
you have a Lambda funcHon that queries your DynamoDB to retrieve some data, process it,
and then send Bob an email with the results. In this case, we want Lambda to only be able to
make read queries so it does not change the database by mistake. We also want to restrict
Lambda to be able to email Bob so it does not spam other people. While this could be done by
creaHng an IAM user and pu]ng the user’s credenHals to the Lambda funcHon or embed the
credenHals in the Lambda code, this is just not secure. If somebody was to get hold of these
credenHals, they could make those calls on your behalf. This is where IAM role comes in to
play.

An IAM role is very similar to a user, in that it is an iden)ty with permission policies that
determine what the idenHty can and cannot do in AWS. However, a role does not have any
credenHals (password or access keys) associated with it. Instead of being uniquely associated
with one person, a role can be taken on by anyone who needs it. In this case, the Lambda
funcHon will be assigned with a role to temporarily take on the permission.
Roles can be applied to users as well. In this case, the user is taking on the policy set for the
IAM role. This is useful for cases where a user is wearing mulHple “hats” in the organizaHon.
Roles make this easy since you only need to create these roles once and they can be re-used
for anybody else that wants to take it on.
You can also have a role Hed to the ARN of a user from a different organizaHon. This allows
the external user to assume that role as a part of your organizaHon. This is typically used when
you have a third party service that is acHng on your AWS OrganizaHon. You’ll be asked to
create a Cross-Account IAM Role and add the external user as a Trust Rela)onship. The Trust
Rela)onship is telling AWS that the specified external user can assume this role.
What is an IAM Group
An IAM group is simply a collecHon of IAM users. You can use groups to specify permissions
for a collecHon of users, which can make those permissions easier to manage for those users.
For example, you could have a group called Admins and give that group the types of
permissions that administrators typically need. Any user in that group automaHcally has the
permissions that are assigned to the group. If a new user joins your organizaHon and should
have administrator privileges, you can assign the appropriate permissions by adding the user
to that group. Similarly, if a person changes jobs in your organizaHon, instead of ediHng that
user’s permissions, you can remove him or her from the old groups and add him or her to the
appropriate new groups.
This should give you a quick idea of IAM and some of its concepts. We will be referring to a
few of these in the coming chapters. Next let’s quickly look at another AWS concept; the ARN.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/what-is-
iam/23)
What is an ARN
In the last chapter while we were looking at IAM policies we looked at how you can specify a
resource using its ARN. Let’s take a be>er look at what ARN is.

Here is the official definiBon:

Amazon Resource Names (ARNs) uniquely iden6fy AWS resources. We require an ARN when you
need to specify a resource unambiguously across all of AWS, such as in IAM policies, Amazon
Rela6onal Database Service (Amazon RDS) tags, and API calls.

ARN is really just a globally unique idenBfier for an individual AWS resource. It takes one of
the following formats.

arn:partition:service:region:account-id:resource
arn:partition:service:region:account-id:resourcetype/resource
arn:partition:service:region:account-id:resourcetype:resource

Let’s look at some examples of ARN. Note the different formats used.

<!-- Elastic Beanstalk application version -->


arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My
App/MyEnvironment

<!-- IAM user name -->


arn:aws:iam::123456789012:user/David

<!-- Amazon RDS instance used for tagging -->


arn:aws:rds:eu-west-1:123456789012:db:mysql-db

<!-- Object in an Amazon S3 bucket -->


arn:aws:s3:::my_corporate_bucket/exampleobject.png

Finally, let’s look at the common use cases for ARN.


1. CommunicaBon

ARN is used to reference a specific resource when you orchestrate a system involving
mulBple AWS resources. For example, you have an API Gateway listening for RESTful APIs
and invoking the corresponding Lambda funcBon based on the API path and request
method. The rouBng looks like the following.

GET /hello_world => arn:aws:lambda:us-east-


1:123456789012:function:lambda-hello-world

2. IAM Policy

We had looked at this in detail in the last chapter but here is an example of a policy
definiBon.

{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::Hello-bucket/*"
}

ARN is used to define which resource (S3 bucket in this case) the access is granted for.
The wildcard * character is used here to match all resources inside the Hello-bucket.

Next let’s configure our AWS CLI. We’ll be using the info from the IAM user account we
created previously.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/what-is-an-
arn/34)
Configure the AWS CLI
To make it easier to work with a lot of the AWS services, we are going to use the AWS CLI
(h=ps://aws.amazon.com/cli/).

Install the AWS CLI


AWS CLI needs Python 2 version 2.6.5+ or Python 3 version 3.3+ and Pip
(h=ps://pypi.python.org/pypi/pip). Use the following if you need help installing Python or Pip.

Installing Python (h=ps://www.python.org/downloads/)


Installing Pip (h=ps://pip.pypa.io/en/stable/installing/)

Now using Pip you can install the AWS CLI (on Linux, macOS, or Unix) by running:

$ sudo pip install awscli

Or using Homebrew (h=ps://brew.sh) on macOS:

$ brew install awscli

If you are having some problems installing the AWS CLI or need Windows install instrucRons,
refer to the complete install instrucRons
(h=p://docs.aws.amazon.com/cli/latest/userguide/installing.html).

Add Your Access Key to AWS CLI


We now need to tell the AWS CLI to use your Access Keys from the previous chapter.

It should look something like this:

Access key ID AKIAIOSFODNN7EXAMPLE


Secret access key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Simply run the following with your Secret Key ID and your Access Key.
$ aws configure

You can leave the Default region name and Default output format the way they are.

Next let’s get started with seWng up our backend.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/configure-the-
aws-cli/86)
Create a DynamoDB Table
To build the backend for our notes app, it makes sense that we first start by thinking about
how the data is going to be stored. We are going to use DynamoDB
(h?ps://aws.amazon.com/dynamodb/) to do this.

About DynamoDB
Amazon DynamoDB is a fully managed NoSQL database that provides fast and predictable
performance with seamless scalability. Similar to other databases, DynamoDB stores data in
tables. Each table contains mulKple items, and each item is composed of one or more
a?ributes. We are going to cover some basics in the following chapters. But to get a be?er
feel for it, here is a great guide on DynamoDB (h?ps://www.dynamodbguide.com).

Create Table
First, log in to your AWS Console (h?ps://console.aws.amazon.com) and select DynamoDB
from the list of services.
Select Create table.
Enter the Table name and Primary key info as shown below. Just make sure that userId and
noteId are in camel case.

Each DynamoDB table has a primary key, which cannot be changed once set. The primary key
uniquely idenKfies each item in the table, so that no two items can have the same key.
DynamoDB supports two different kinds of primary keys:

ParKKon key
ParKKon key and sort key (composite)

We are going to use the composite primary key which gives us addiKonal flexibility when
querying the data. For example, if you provide only the value for userId , DynamoDB would
retrieve all of the notes by that user. Or you could provide a value for userId and a value for
noteId , to retrieve a parKcular note.

To further your understanding of how indexes work in DynamoDB, you can read more here:
DynamoDB Core Components
(h?p://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreCo
mponents.html)
Next scroll down and deselect Use default se8ngs.

Scroll down further and On-demand instead of Provisioned.


On-Demand Capacity (h?ps://aws.amazon.com/dynamodb/pricing/on-demand/) is
DynamoDB’s pay per request mode. For workloads that are not predictable or if you are just
starKng out, this ends up being a lot cheaper than the Provisioned Capacity
(h?ps://aws.amazon.com/dynamodb/pricing/provisioned/) mode.

Finally, scroll down and hit Create.


The notes table has now been created. If you find yourself stuck with the Table is being
created message; refresh the page manually.
It is also a good idea to set up backups for your DynamoDB table, especially if you are
planning to use it in producKon. We cover this in an extra-credit chapter, Backups in
DynamoDB (/chapters/backups-in-dynamodb.html).

Next we’ll set up an S3 bucket to handle file uploads.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
dynamodb-table/139)
Create an S3 Bucket for File Uploads
Now that we have our database table ready; let’s get things set up for handling file uploads.
We need to handle file uploads because each note can have an uploaded file as an
a<achment.

Amazon S3 (h<ps://aws.amazon.com/s3/) (Simple Storage Service) provides storage service


through web services interfaces like REST. You can store any object in S3 including images,
videos, files, etc. Objects are organized into buckets, and idenNfied within each bucket by a
unique, user-assigned key.

In this chapter, we are going to create an S3 bucket which will be used to store user uploaded
files from our notes app.

Create Bucket
First, log in to your AWS Console (h<ps://console.aws.amazon.com) and select S3 from the list
of services.
Select Create bucket.
Pick a name of the bucket and select a region. Then select Create.

Bucket names are globally unique, which means you cannot pick the same name as this
tutorial.
Region is the physical geographical region where the files are stored. We will use US East
(N. Virginia) for this guide.

Make a note of the name and region as we’ll be using it later in the guide.

Step through the next steps and leave the defaults by clicking Next, and then click Create
bucket on the last step.
Enable CORS
In the notes app we’ll be building, users will be uploading files to the bucket we just created.
And since our app will be served through our custom domain, it’ll be communicaNng across
domains while it does the uploads. By default, S3 does not allow its resources to be accessed
from a different domain. However, cross-origin resource sharing (CORS) defines a way for
client web applicaNons that are loaded in one domain to interact with resources in a different
domain. Let’s enable CORS for our S3 bucket.

Select the bucket we just created.


Select the Permissions tab, then select CORS configura@on.
Add the following CORS configuraNon into the editor, then hit Save.

<CORSConfiguration>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Note that you can edit this configuraNon to use your own domain or a list of domains when
you use this in producNon.

Now that our S3 bucket is ready, let’s get set up to handle user authenNcaNon.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-an-s3-
bucket-for-file-uploads/150)
Create a Cognito User Pool
Our notes app needs to handle user accounts and authen0ca0on in a secure and reliable way.
To do this we are going to use Amazon Cognito (h=ps://aws.amazon.com/cognito/).

Amazon Cognito User Pool makes it easy for developers to add sign-up and sign-in
func0onality to web and mobile applica0ons. It serves as your own iden0ty provider to
maintain a user directory. It supports user registra0on and sign-in, as well as provisioning
iden0ty tokens for signed-in users.

In this chapter, we are going to create a User Pool for our notes app.

Create User Pool


From your AWS Console (h=ps://console.aws.amazon.com), select Cognito from the list of
services.
Select Manage your User Pools.

Select Create a User Pool.


Enter Pool name and select Review defaults.
Select Choose username a9ributes….

And select Email address or phone numbers and Allow email addresses. This is telling Cognito
User Pool that we want our users to be able to sign up and login with their email as their
username.
Scroll down and select Next step.
Hit Review in the side panel and make sure that the Username a9ributes is set to email.

Now hit Create pool at the bo=om of the page.


Your User Pool has been created. Take a note of the Pool Id and Pool ARN which will be
required later. Also, note the region that your User Pool is created in – in our case it’s us-
east-1 .
Create App Client
Select App clients from the leS panel.
Select Add an app client.
Enter App client name, un-select Generate client secret, select Enable sign-in API for server-
based authenEcaEon, then select Create app client.

Generate client secret: user pool apps with a client secret are not supported by the
JavaScript SDK. We need to un-select the op0on.
Enable sign-in API for server-based authenEcaEon: required by AWS CLI when managing
the pool users via command line interface. We will be crea0ng a test user through the
command line interface in the next chapter.

Your app client has been created. Take note of the App client id which will be required in the
later chapters.
Create Domain Name
Finally, select Domain name from the leS panel. Enter your unique domain name and select
Save changes. In our case we are using notes-app .
Now our Cognito User Pool is ready. It will maintain a user directory for our notes app. It will
also be used to authen0cate access to our API. Next let’s set up a test user within the pool.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
cognito-user-pool/148)
Create a Cognito Test User
In this chapter, we are going to create a test user for our Cognito User Pool. We are going to
need this user to test the authen:ca:on por:on of our app later.

Create User
First, we will use AWS CLI to sign up a user with their email and password.

In your terminal, run.

$ aws cognito-idp sign-up \


--region YOUR_COGNITO_REGION \
--client-id YOUR_COGNITO_APP_CLIENT_ID \
--username [email protected] \
--password Passw0rd!

Now, the user is created in Cognito User Pool. However, before the user can authen:cate with
the User Pool, the account needs to be verified. Let’s quickly verify the user using an
administrator command.

In your terminal, run.

$ aws cognito-idp admin-confirm-sign-up \


--region YOUR_COGNITO_REGION \
--user-pool-id YOUR_COGNITO_USER_POOL_ID \
--username [email protected]

Now our test user is ready. Next, let’s set up the Serverless Framework to create our backend
APIs.

For help and discussion


! Comments on this chapter
(https://discourse.serverless-stack.com/t/create-a-
cognito-test-user/126)
Set up the Serverless Framework
We are going to be using AWS Lambda (h5ps://aws.amazon.com/lambda/) and Amazon API
Gateway (h5ps://aws.amazon.com/api-gateway/) to create our backend. AWS Lambda is a
compute service that lets you run code without provisioning or managing servers. You pay
only for the compute Hme you consume - there is no charge when your code is not running.
And API Gateway makes it easy for developers to create, publish, maintain, monitor, and
secure APIs. Working directly with AWS Lambda and configuring API Gateway can be a bit
cumbersome; so we are going to use the Serverless Framework (h5ps://serverless.com) to help
us with it.

The Serverless Framework enables developers to deploy backend applicaHons as independent


funcHons that will be deployed to AWS Lambda. It also configures AWS Lambda to run your
code in response to HTTP requests using Amazon API Gateway.

In this chapter, we are going to set up the Serverless Framework on our local development
environment.

Install Serverless
Install Serverless globally.

$ npm install serverless -g

The above command needs NPM (h5ps://www.npmjs.com), a package manager for JavaScript.
Follow this (h5ps://docs.npmjs.com/geTng-started/installing-node) if you need help installing
NPM.

In your working directory; create a project using a Node.js starter. We’ll go over
some of the details of this starter project in the next chapter.

$ serverless install --url


https://github.com/AnomalyInnovations/serverless-nodejs-starter --name
notes-app-api
Go into the directory for our backend api project.

$ cd notes-app-api

Now the directory should contain a few files including, the handler.js and serverless.yml.

handler.js file contains actual code for the services/funcHons that will be deployed to
AWS Lambda.
serverless.yml file contains the configuraHon on what AWS services Serverless will
provision and how to configure them.

We also have a tests/ directory where we can add our unit tests.

Install Node.js packages


The starter project relies on a few dependencies that are listed in the package.json .

At the root of the project, run.

$ npm install

Next, we’ll install a couple of other packages specifically for our backend.

$ npm install aws-sdk --save-dev


$ npm install uuid --save

aws-sdk allows us to talk to the various AWS services.


uuid generates unique ids. We need this for storing things to DynamoDB.

The starter project that we are using allows us to use the version of JavaScript that we’ll be
using in our frontend app later. Let’s look at exactly how it does this.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/set-up-the-
serverless-framework/145)
Add Support for ES6/ES7 JavaScript
AWS Lambda recently added support for Node.js v8.10 and v10.x. The supported syntax is a
liAle different when compared to the frontend React app we’ll be working on a liAle later. It
makes sense to use similar ES features across both parts of the project – specifically, we’ll be
relying on ES imports/exports in our handler funcNons. To do this we will be transpiling our
code using Babel (hAps://babeljs.io) and Webpack 4 (hAps://webpack.github.io). Also,
Webpack allows us to generate opNmized packages for our Lambda funcNons by only
including the code that is used in our funcNon. This helps keep our packages small and
reduces cold start Nmes. Serverless Framework supports plugins to do this automaNcally. We
are going to use an extension of the popular serverless-webpack
(hAps://github.com/serverless-heaven/serverless-webpack) plugin, serverless-bundle
(hAps://github.com/AnomalyInnovaNons/serverless-bundle).

All this has been added in the previous chapter using the serverless-nodejs-starter
(/chapters/serverless-nodejs-starter.html). We created this starter for a couple of reasons:

Generate opNmized packages for our Lambda funcNons


Use a similar version of JavaScript in the frontend and backend
Ensure transpiled code sNll has the right line numbers for error messages
Lint our code and add support for unit tests
Allow you to run your backend API locally
Not have to manage any Webpack or Babel configs

If you recall we installed this starter using the serverless install --url
https://github.com/AnomalyInnovations/serverless-nodejs-starter --name
my-project command. This is telling Serverless Framework to use the starter
(hAps://github.com/AnomalyInnovaNons/serverless-nodejs-starter) as a template to create our
project.

In this chapter, let’s quickly go over how it’s doing this so you’ll be able to make changes in the
future if you need to.

Serverless Webpack
The transpiling process of converNng our ES code to Node v8.10 JavaScript is done by the
serverless-bundle plugin. This plugin was added in our serverless.yml .

Open serverless.yml and replace the default with the following.

service: notes-app-api

# Create an optimized package for our functions


package:
individually: true

plugins:
- serverless-bundle # Package our functions with Webpack
- serverless-offline

provider:
name: aws
runtime: nodejs8.10
stage: prod
region: us-east-1

The service opNon is preAy important. We are calling our service the notes-app-api .
Serverless Framework creates your stack on AWS using this as the name. This means that if
you change the name and deploy your project, it will create a completely new project.

You’ll noNce the plugins serverless-bundle and serverless-offline that we have


included. The first plugin we talked about above, while the serverless-offline
(hAps://github.com/dherault/serverless-offline) is helpful for local development.

We are also using this opNon:

# Create an optimized package for our functions


package:
individually: true

By default, Serverless Framework creates one large package for all the Lambda funcNons in
your app. Large Lambda funcNon packages can cause longer cold starts. By se_ng
individually: true , we are telling Serverless Framework to create a single package per
Lambda funcNon. This in combinaNon with serverless-bundle (and Webpack) will generate
opNmized packages. Note that, this’ll slow down our builds but the performance benefit is well
worth it.

And now we are ready to build our backend.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-support-
for-es6-es7-javascript/128)
Add a Create Note API
Let’s get started on our backend by first adding an API to create a note. This API will take the
note object as the input and store it in the database with a new id. The note object will
contain the content field (the content of the note) and an attachment field (the URL to
the uploaded file).

Add the Function


Let’s add our first funcCon.

Create a new file called create.js in our project root with the following.

import uuid from "uuid";


import AWS from "aws-sdk";

const dynamoDb = new AWS.DynamoDB.DocumentClient();

export function main(event, context, callback) {


// Request body is passed in as a JSON encoded string in
'event.body'
const data = JSON.parse(event.body);

const params = {
TableName: "notes",
// 'Item' contains the attributes of the item to be created
// - 'userId': user identities are federated through the
// Cognito Identity Pool, we will use the identity id
// as the user id of the authenticated user
// - 'noteId': a unique uuid
// - 'content': parsed from request body
// - 'attachment': parsed from request body
// - 'createdAt': current Unix timestamp
Item: {
userId: event.requestContext.identity.cognitoIdentityId,
noteId: uuid.v1(),
content: data.content,
attachment: data.attachment,
createdAt: Date.now()
}
};

dynamoDb.put(params, (error, data) => {


// Set response headers to enable CORS (Cross-Origin Resource
Sharing)
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
};

// Return status code 500 on error


if (error) {
const response = {
statusCode: 500,
headers: headers,
body: JSON.stringify({ status: false })
};
callback(null, response);
return;
}

// Return status code 200 and the newly created item


const response = {
statusCode: 200,
headers: headers,
body: JSON.stringify(params.Item)
};
callback(null, response);
});
}

There are some helpful comments in the code but we are doing a few simple things here.
The AWS JS SDK assumes the region based on the current region of the Lambda funcCon.
So if your DynamoDB table is in a different region, make sure to set it by calling
AWS.config.update({ region: "my-region" }); before iniClizing the DynamoDB
client.
Parse the input from the event.body . This represents the HTTP request parameters.
The userId is a Federated IdenCty id that comes in as a part of the request. This is set
aRer our user has been authenCcated via the User Pool. We are going to expand more on
this in the coming chapters when we set up our Cognito IdenCty Pool. However, if you
want to use the user’s User Pool user Id; take a look at the Mapping Cognito IdenCty Id
and User Pool Id (/chapters/mapping-cognito-idenCty-id-and-user-pool-id.html) chapter.
Make a call to DynamoDB to put a new object with a generated noteId and the current
date as the createdAt .
Upon success, return the newly created note object with the HTTP status code 200 and
response headers to enable CORS (Cross-Origin Resource Sharing).
And if the DynamoDB call fails then return an error with the HTTP status code 500 .

Configure the API Endpoint


Now let’s define the API endpoint for our funcCon.

Open the serverless.yml file and replace it with the following.

service: notes-app-api

# Create an optimized package for our functions


package:
individually: true

plugins:
- serverless-bundle # Package our functions with Webpack
- serverless-offline

provider:
name: aws
runtime: nodejs8.10
stage: prod
region: us-east-1
# 'iamRoleStatements' defines the permission policy for the Lambda
function.
# In this case Lambda functions are granted with permissions to
access DynamoDB.
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:us-east-1:*:*"

functions:
# Defines an HTTP API endpoint that calls the main function in
create.js
# - path: url path is /notes
# - method: POST request
# - cors: enabled CORS (Cross-Origin Resource Sharing) for browser
cross
# domain api call
# - authorizer: authenticate using the AWS IAM role
create:
handler: create.main
events:
- http:
path: notes
method: post
cors: true
authorizer: aws_iam

Here we are adding our newly added create funcCon to the configuraCon. We specify that it
handles post requests at the /notes endpoint. This pa[ern of using a single Lambda
funcCon to respond to a single HTTP event is very much like the Microservices architecture
(h[ps://en.wikipedia.org/wiki/Microservices). We discuss this and a few other pa[erns in the
chapter on organizing Serverless Framework projects (/chapters/organizing-serverless-
projects.html). We set CORS support to true. This is because our frontend is going to be
served from a different domain. As the authorizer we are going to restrict access to our API
based on the user’s IAM credenCals. We will touch on this and how our User Pool works with
this, in the Cognito IdenCty Pool chapter.

The iamRoleStatements secCon is telling AWS which resources our Lambda funcCons
have access to. In this case we are saying that our Lambda funcCons can carry out the above
listed acCons on DynamoDB. We specify DynamoDB using arn:aws:dynamodb:us-east-
1:*:* . This is roughly poinCng to every DynamoDB table in the us-east-1 region. We can
be more specific here by specifying the table name but we’ll leave this as an exercise for the
reader. Just make sure to use the region that the DynamoDB table was created in, as this can
be a common source of issues later on. For us the region is us-east-1 .

Test
Now we are ready to test our new API. To be able to test it on our local we are going to mock
the input parameters.

In our project root, create a mocks/ directory.

$ mkdir mocks

Create a mocks/create-event.json file and add the following.

{
"body": "{\"content\":\"hello
world\",\"attachment\":\"hello.jpg\"}",
"requestContext": {
"identity": {
"cognitoIdentityId": "USER-SUB-1234"
}
}
}

You might have noCced that the body and requestContext fields are the ones we used in
our create funcCon. In this case the cognitoIdentityId field is just a string we are going
to use as our userId . We can use any string here; just make sure to use the same one when
we test our other funcCons.

And to invoke our funcCon we run the following in the root directory.

$ serverless invoke local --function create --path mocks/create-


event.json

If you have mulCple profiles for your AWS SDK credenCals, you will need to explicitly pick
one. Use the following command instead:

$ AWS_PROFILE=myProfile serverless invoke local --function create --


path mocks/create-event.json

Where myProfile is the name of the AWS profile you want to use. If you need more info on
how to work with AWS profiles in Serverless, refer to our Configure mulCple AWS profiles
(/chapters/configure-mulCple-aws-profiles.html) chapter.

The response should look similar to this.

{
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: '{"userId":"USER-SUB-1234","noteId":"578eb840-f70f-11e6-9d1a-
1359b3b22944","content":"hello
world","attachment":"hello.jpg","createdAt":1487800950620}'
}

Make a note of the noteId in the response. We are going to use this newly created note in
the next chapter.

Refactor Our Code


Before we move on to the next chapter, let’s quickly refactor the code since we are going to
be doing much of the same for all of our APIs.

In our project root, create a libs/ directory.

$ mkdir libs
$ cd libs

And create a libs/response-lib.js file.

export function success(body) {


return buildResponse(200, body);
}

export function failure(body) {


return buildResponse(500, body);
}

function buildResponse(statusCode, body) {


return {
statusCode: statusCode,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify(body)
};
}

This will manage building the response objects for both success and failure cases with the
proper HTTP status code and headers.

Again inside libs/ , create a dynamodb-lib.js file.

import AWS from "aws-sdk";

export function call(action, params) {


const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
}

Here we are using the promise form of the DynamoDB methods. Promises are a method for
managing asynchronous code that serve as an alternaCve to the standard callback funcCon
syntax. It will make our code a lot easier to read.

Now, we’ll go back to our create.js and use the helper funcCons we created.
Replace our create.js with the following.

import uuid from "uuid";


import * as dynamoDbLib from "./libs/dynamodb-lib";
import { success, failure } from "./libs/response-lib";

export async function main(event, context) {


const data = JSON.parse(event.body);
const params = {
TableName: "notes",
Item: {
userId: event.requestContext.identity.cognitoIdentityId,
noteId: uuid.v1(),
content: data.content,
attachment: data.attachment,
createdAt: Date.now()
}
};

try {
await dynamoDbLib.call("put", params);
return success(params.Item);
} catch (e) {
return failure({ status: false });
}
}

We are also using the async/await pa[ern here to refactor our Lambda funcCon. This
allows us to return once we are done processing; instead of using the callback funcCon.
Next, we are going to write the API to get a note given its id.

Common Issues

Response statusCode: 500

If you see a statusCode: 500 response when you invoke your funcCon, here is how to
debug it. The error is generated by our code in the catch block. Adding a
console.log like so, should give you a clue about what the issue is.

catch(e) {
console.log(e);
return failure({status: false});
}

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-a-create-
note-api/125)

For reference, here is the code we are using

" Backend Source :add-a-create-note-api


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/add-a-create-note-api)
Add a Get Note API
Now that we created a note and saved it to our database. Let’s add an API to retrieve a note
given its id.

Add the Function


Create a new file get.js and paste the following code

import * as dynamoDbLib from "./libs/dynamodb-lib";


import { success, failure } from "./libs/response-lib";

export async function main(event, context) {


const params = {
TableName: "notes",
// 'Key' defines the partition key and sort key of the item to be
retrieved
// - 'userId': Identity Pool identity id of the authenticated user
// - 'noteId': path parameter
Key: {
userId: event.requestContext.identity.cognitoIdentityId,
noteId: event.pathParameters.id
}
};

try {
const result = await dynamoDbLib.call("get", params);
if (result.Item) {
// Return the retrieved item
return success(result.Item);
} else {
return failure({ status: false, error: "Item not found." });
}
} catch (e) {
return failure({ status: false });
}
}

This follows exactly the same structure as our previous create.js funcBon. The major
difference here is that we are doing a dynamoDbLib.call('get', params) to get a note
object given the noteId and userId that is passed in through the request.

Configure the API Endpoint


Open the serverless.yml file and append the following to it.

get:
# Defines an HTTP API endpoint that calls the main function in
get.js
# - path: url path is /notes/{id}
# - method: GET request
handler: get.main
events:
- http:
path: notes/{id}
method: get
cors: true
authorizer: aws_iam

Make sure that this block is indented exactly the same way as the preceding create block.

This defines our get note API. It adds a GET request handler with the endpoint
/notes/{id} .

Test
To test our get note API we need to mock passing in the noteId parameter. We are going to
use the noteId of the note we created in the previous chapter and add in a
pathParameters block to our mock. So it should look similar to the one below. Replace the
value of id with the id you received when you invoked the previous create.js funcBon.
Create a mocks/get-event.json file and add the following.

{
"pathParameters": {
"id": "578eb840-f70f-11e6-9d1a-1359b3b22944"
},
"requestContext": {
"identity": {
"cognitoIdentityId": "USER-SUB-1234"
}
}
}

And we invoke our newly created funcBon.

$ serverless invoke local --function get --path mocks/get-event.json

The response should look similar to this.

{
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: '{"attachment":"hello.jpg","content":"hello
world","createdAt":1487800950620,"noteId":"578eb840-f70f-11e6-9d1a-
1359b3b22944","userId":"USER-SUB-1234"}'
}

Next, let’s create an API to list all the notes a user has.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-a-get-
note-api/132)

For reference, here is the code we are using

" Backend Source :add-a-get-note-api


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/add-a-get-note-api)
Add a List All the Notes API
Now we are going to add an API that returns a list of all the notes a user has.

Add the Function


Create a new file called list.js with the following.

import * as dynamoDbLib from "./libs/dynamodb-lib";


import { success, failure } from "./libs/response-lib";

export async function main(event, context) {


const params = {
TableName: "notes",
// 'KeyConditionExpression' defines the condition for the query
// - 'userId = :userId': only return items with matching 'userId'
// partition key
// 'ExpressionAttributeValues' defines the value in the condition
// - ':userId': defines 'userId' to be Identity Pool identity id
// of the authenticated user
KeyConditionExpression: "userId = :userId",
ExpressionAttributeValues: {
":userId": event.requestContext.identity.cognitoIdentityId
}
};

try {
const result = await dynamoDbLib.call("query", params);
// Return the matching list of items in response body
return success(result.Items);
} catch (e) {
return failure({ status: false });
}
}
This is pre;y much the same as our get.js except we only pass in the userId in the
DynamoDB query call.

Configure the API Endpoint


Open the serverless.yml file and append the following.

list:
# Defines an HTTP API endpoint that calls the main function in
list.js
# - path: url path is /notes
# - method: GET request
handler: list.main
events:
- http:
path: notes
method: get
cors: true
authorizer: aws_iam

This defines the /notes endpoint that takes a GET request.

Test
Create a mocks/list-event.json file and add the following.

{
"requestContext": {
"identity": {
"cognitoIdentityId": "USER-SUB-1234"
}
}
}

And invoke our funcGon from the root directory of the project.
$ serverless invoke local --function list --path mocks/list-event.json

The response should look similar to this.

{
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: '[{"attachment":"hello.jpg","content":"hello
world","createdAt":1487800950620,"noteId":"578eb840-f70f-11e6-9d1a-
1359b3b22944","userId":"USER-SUB-1234"}]'
}

Note that this API returns an array of note objects as opposed to the get.js funcGon that
returns just a single note object.

Next we are going to add an API to update a note.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-a-list-
all-the-notes-api/147)

For reference, here is the code we are using

" Backend Source :add-a-list-all-the-notes-api


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/add-a-list-all-the-notes-api)
Add an Update Note API
Now let’s create an API that allows a user to update a note with a new note object given its id.

Add the Function


Create a new file update.js and paste the following code

import * as dynamoDbLib from "./libs/dynamodb-lib";


import { success, failure } from "./libs/response-lib";

export async function main(event, context) {


const data = JSON.parse(event.body);
const params = {
TableName: "notes",
// 'Key' defines the partition key and sort key of the item to be
updated
// - 'userId': Identity Pool identity id of the authenticated user
// - 'noteId': path parameter
Key: {
userId: event.requestContext.identity.cognitoIdentityId,
noteId: event.pathParameters.id
},
// 'UpdateExpression' defines the attributes to be updated
// 'ExpressionAttributeValues' defines the value in the update
expression
UpdateExpression: "SET content = :content, attachment =
:attachment",
ExpressionAttributeValues: {
":attachment": data.attachment || null,
":content": data.content || null
},
// 'ReturnValues' specifies if and how to return the item's
attributes,
// where ALL_NEW returns all attributes of the item after the
update; you
// can inspect 'result' below to see how it works with different
settings
ReturnValues: "ALL_NEW"
};

try {
await dynamoDbLib.call("update", params);
return success({ status: true });
} catch (e) {
return failure({ status: false });
}
}

This should look similar to the create.js funcAon. Here we make an update DynamoDB
call with the new content and attachment values in the params .

Configure the API Endpoint


Open the serverless.yml file and append the following to it.

update:
# Defines an HTTP API endpoint that calls the main function in
update.js
# - path: url path is /notes/{id}
# - method: PUT request
handler: update.main
events:
- http:
path: notes/{id}
method: put
cors: true
authorizer: aws_iam

Here we are adding a handler for the PUT request to the /notes/{id} endpoint.
Test
Create a mocks/update-event.json file and add the following.

Also, don’t forget to use the noteId of the note we have been using in place of the id in
the pathParameters block.

{
"body": "{\"content\":\"new world\",\"attachment\":\"new.jpg\"}",
"pathParameters": {
"id": "578eb840-f70f-11e6-9d1a-1359b3b22944"
},
"requestContext": {
"identity": {
"cognitoIdentityId": "USER-SUB-1234"
}
}
}

And we invoke our newly created funcAon from the root directory.

$ serverless invoke local --function update --path mocks/update-


event.json

The response should look similar to this.

{
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: '{"status":true}'
}

Next we are going to add an API to delete a note given its id.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-an-update-
note-api/144)

For reference, here is the code we are using

" Backend Source :add-an-update-note-api


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/add-an-update-note-api)
Add a Delete Note API
Finally, we are going to create an API that allows a user to delete a given note.

Add the Function


Create a new file delete.js and paste the following code

import * as dynamoDbLib from "./libs/dynamodb-lib";


import { success, failure } from "./libs/response-lib";

export async function main(event, context) {


const params = {
TableName: "notes",
// 'Key' defines the partition key and sort key of the item to be
removed
// - 'userId': Identity Pool identity id of the authenticated user
// - 'noteId': path parameter
Key: {
userId: event.requestContext.identity.cognitoIdentityId,
noteId: event.pathParameters.id
}
};

try {
await dynamoDbLib.call("delete", params);
return success({ status: true });
} catch (e) {
return failure({ status: false });
}
}

This makes a DynamoDB delete call with the userId & noteId key to delete the note.
Configure the API Endpoint
Open the serverless.yml file and append the following to it.

delete:
# Defines an HTTP API endpoint that calls the main function in
delete.js
# - path: url path is /notes/{id}
# - method: DELETE request
handler: delete.main
events:
- http:
path: notes/{id}
method: delete
cors: true
authorizer: aws_iam

This adds a DELETE request handler to the /notes/{id} endpoint.

Test
Create a mocks/delete-event.json file and add the following.

Just like before we’ll use the noteId of our note in place of the id in the
pathParameters block.

{
"pathParameters": {
"id": "578eb840-f70f-11e6-9d1a-1359b3b22944"
},
"requestContext": {
"identity": {
"cognitoIdentityId": "USER-SUB-1234"
}
}
}
Invoke our newly created funcJon from the root directory.

$ serverless invoke local --function delete --path mocks/delete-


event.json

And the response should look similar to this.

{
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
},
body: '{"status":true}'
}

Now that our APIs are complete; we are almost ready to deploy them.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-a-delete-
note-api/153)

For reference, here is the code we are using

" Backend Source :add-a-delete-note-api


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/add-a-delete-note-api)
Handle API Gateway CORS Errors
Before we deploy our APIs we need to do one last thing to set them up. We need to add
CORS headers to API Gateway errors. You might recall that back in the Add a create note API
(/chapters/add-a-create-note-api.html) chapter, we added the CORS headers to our Lambda
funcIons. However when we make an API request, API Gateway gets invoked before our
Lambda funcIons. This means that if there is an error at the API Gateway level, the CORS
headers won’t be set.

Consequently, debugging such errors can be really hard. Our client won’t be able to see the
error message and instead will be presented with something like this:

No 'Access-Control-Allow-Origin' header is present on the requested


resource

These CORS related errors are one of the most common Serverless API errors. In this chapter,
we are going to configure API Gateway to set the CORS headers in the case there is an HTTP
error. We won’t be able to test this right away, but it will really help when we work on our
frontend client.

Create a Resource
To configure API Gateway errors we are going to add a few things to our serverless.yml .
By default, Serverless Framework (hRps://serverless.com) supports CloudFormaIon
(hRps://aws.amazon.com/cloudformaIon/) to help us configure our API Gateway instance
through code.

Let’s create a directory to add our resources. We’ll be adding to this later in the
guide.

$ mkdir resources/

And add the following to resources/api-gateway-errors.yml .


Resources:
GatewayResponseDefault4XX:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
ResponseType: DEFAULT_4XX
RestApiId:
Ref: 'ApiGatewayRestApi'
GatewayResponseDefault5XX:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
ResponseType: DEFAULT_5XX
RestApiId:
Ref: 'ApiGatewayRestApi'

The above might look a liRle inImidaIng. It’s a CloudFormaIon resource and its syntax tends
to be fairly verbose. But the details here aren’t too important. We are adding the CORS
headers to the ApiGatewayRestApi resource in our app. The
GatewayResponseDefault4XX is for 4xx errors, while GatewayResponseDefault5XX is
for 5xx errors.

Include the Resource


Now let’s include the above CloudFormaIon resource in our serverless.yml .

Add the following to the boRom of our serverless.yml .

# Create our resources with separate CloudFormation templates


resources:
# API Gateway Errors
- ${file(resources/api-gateway-errors.yml)}
And that’s it. We are ready to deploy our APIs.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/handle-api-
gateway-cors-errors/780)

For reference, here is the code we are using

" Backend Source :handle-api-gateway-cors-errors


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/handle-api-gateway-cors-errors)
Deploy the APIs
Now that our APIs are complete, let’s deploy them.

Run the following in your working directory.

$ serverless deploy

If you have mul?ple profiles for your AWS SDK creden?als, you will need to explicitly pick
one. Use the following command instead:

$ serverless deploy --aws-profile myProfile

Where myProfile is the name of the AWS profile you want to use. If you need more info on
how to work with AWS profiles in Serverless, refer to our Configure mul?ple AWS profiles
(/chapters/configure-mul?ple-aws-profiles.html) chapter.

Near the boNom of the output for this command, you will find the Service Informa.on.

Service Information
service: notes-app-api
stage: prod
region: us-east-1
api keys:
None
endpoints:
POST - https://ly55wbovq4.execute-api.us-east-
1.amazonaws.com/prod/notes
GET - https://ly55wbovq4.execute-api.us-east-
1.amazonaws.com/prod/notes/{id}
GET - https://ly55wbovq4.execute-api.us-east-
1.amazonaws.com/prod/notes
PUT - https://ly55wbovq4.execute-api.us-east-
1.amazonaws.com/prod/notes/{id}
DELETE - https://ly55wbovq4.execute-api.us-east-
1.amazonaws.com/prod/notes/{id}
functions:
notes-app-api-prod-create
notes-app-api-prod-get
notes-app-api-prod-list
notes-app-api-prod-update
notes-app-api-prod-delete

This has a list of the API endpoints that were created. Make a note of these endpoints as we
are going to use them later while crea?ng our frontend. Also make a note of the region and
the id in these endpoints, we are going to use them in the coming chapters. In our case, us-
east-1 is our API Gateway Region and ly55wbovq4 is our API Gateway ID.

If you are running into some issues while deploying your app, we have a compila?on of some
of the most common Serverless errors (hNps://seed.run/docs/serverless-errors/) over on Seed
(hNps://seed.run).

Deploy a Single Function


There are going to be cases where you might want to deploy just a single API endpoint as
opposed to all of them. The serverless deploy function command deploys an
individual func?on without going through the en?re deployment cycle. This is a much faster
way of deploying the changes we make.

For example, to deploy the list func?on again, we can run the following.

$ serverless deploy function -f list

Now before we test our APIs we have one final thing to set up. We need to ensure that our
users can securely access the AWS resources we have created so far. Let’s look at seUng up a
Cognito Iden?ty Pool.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploy-the-
apis/121)
Create a Cognito Identity Pool
Now that we have deployed our backend API; we almost have all the pieces we need for our
backend. We have the User Pool that is going to store all of our users and help sign in and sign
them up. We also have an S3 bucket that we will use to help our users upload files as
aCachments for their notes. The final piece that Ees all these services together in a secure way
is called Amazon Cognito Federated IdenEEes.

Amazon Cognito Federated IdenEEes enables developers to create unique idenEEes for your
users and authenEcate them with federated idenEty providers. With a federated idenEty, you
can obtain temporary, limited-privilege AWS credenEals to securely access other AWS
services such as Amazon DynamoDB, Amazon S3, and Amazon API Gateway.

In this chapter, we are going to create a federated Cognito IdenEty Pool. We will be using our
User Pool as the idenEty provider. We could also use Facebook, Google, or our own custom
idenEty provider. Once a user is authenEcated via our User Pool, the IdenEty Pool will aCach
an IAM Role to the user. We will define a policy for this IAM Role to grant access to the S3
bucket and our API. This is the Amazon way of securing your resources.

Let’s get started.

Create Pool
From your AWS Console (hCps://console.aws.amazon.com) and select Cognito from the list of
services.
Select Manage Federated Iden//es.
Enter an Iden/ty pool name. If you have any exisEng IdenEty Pools, you’ll need to click the
Create new iden/ty pool buCon.

Select Authen/ca/on providers. Under Cognito tab, enter User Pool ID and App Client ID of
the User Pool created in the Create a Cognito user pool (/chapters/create-a-cognito-user-
pool.html) chapter. Select Create Pool.
Now we need to specify what AWS resources are accessible for users with temporary
credenEals obtained from the Cognito IdenEty Pool.

Select View Details. Two Role Summary secEons are expanded. The top secEon summarizes
the permission policy for authenEcated users, and the boCom secEon summarizes that for
unauthenEcated users.

Select View Policy Document in the top secEon. Then select Edit.
It will warn you to read the documentaEon. Select Ok to edit.
Add the following policy into the editor. Replace
YOUR_S3_UPLOADS_BUCKET_NAME with the bucket name from the Create an S3 bucket for
file uploads (/chapters/create-an-s3-bucket-for-file-uploads.html) chapter. And replace the
YOUR_API_GATEWAY_REGION and YOUR_API_GATEWAY_ID with the ones that you get
aZer you deployed your API in the last chapter.

In our case YOUR_S3_UPLOADS_BUCKET_NAME is notes-app-uploads ,


YOUR_API_GATEWAY_ID is ly55wbovq4 , and YOUR_API_GATEWAY_REGION is us-east-
1 .

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"mobileanalytics:PutEvents",
"cognito-sync:*",
"cognito-identity:*"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::YOUR_S3_UPLOADS_BUCKET_NAME/private/${cognito-
identity.amazonaws.com:sub}/*"
]
},
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-
api:YOUR_API_GATEWAY_REGION:*:YOUR_API_GATEWAY_ID/*/*/*"
]
}
]
}

A quick note on the block that relates to the S3 Bucket. In the above policy we are granEng
our logged in users access to the path private/${cognito-
identity.amazonaws.com:sub/}/ . Where cognito-identity.amazonaws.com:sub is
the authenEcated user’s federated idenEty ID (their user id). So a user has access to only their
folder within the bucket. This is how we are securing the uploads for each user.

So in summary we are telling AWS that an authenEcated user has access to two resources.

1. Files in the S3 bucket that are inside a folder with their federated idenEty id as the name
of the folder.
2. And, the APIs we deployed using API Gateway.

One other thing to note is that the federated idenEty id is a UUID that is assigned by our
IdenEty Pool. This is the id ( event.requestContext.identity.cognitoIdentityId )
that we were using as our user id back when we were creaEng our APIs.

Select Allow.
Our Cognito IdenEty Pool should now be created. Let’s find out the IdenEty Pool ID.

Select Dashboard from the leZ panel, then select Edit iden/ty pool.
Take a note of the Iden/ty pool ID which will be required in the later chapters.
Now before we test our serverless API let’s take a quick look at the Cognito User Pool and
Cognito IdenEty Pool and make sure we’ve got a good idea of the two concepts and the
differences between them.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
cognito-identity-pool/135)
Cognito User Pool vs Identity Pool
We o%en get ques,ons about the differences between the Cognito User Pool and the Iden,ty
Pool, so it is worth covering in detail. The two can seem a bit similar in func,on and it is not
en,rely clear what they are for. Let’s first start with the official defini,ons.

Here is what AWS defines the Cognito User Pool as:

Amazon Cognito User Pool makes it easy for developers to add sign-up and sign-in func;onality to
web and mobile applica;ons. It serves as your own iden;ty provider to maintain a user directory. It
supports user registra;on and sign-in, as well as provisioning iden;ty tokens for signed-in users.

And the Cognito Federated Iden,,es or Iden,ty Pool is defined as:

Amazon Cognito Federated Iden;;es enables developers to create unique iden;;es for your users
and authen;cate them with federated iden;ty providers. With a federated iden;ty, you can obtain
temporary, limited-privilege AWS creden;als to securely access other AWS services such as
Amazon DynamoDB, Amazon S3, and Amazon API Gateway.

Unfortunately they are both a bit vague and confusingly similar. Here is a more prac,cal
descrip,on of what they are.

User Pool
Say you were crea,ng a new web or mobile app and you were thinking about how to handle
user registra,on, authen,ca,on, and account recovery. This is where Cognito User Pools
would come in. Cognito User Pool handles all of this and as a developer you just need to use
the SDK to retrieve user related informa,on.

Identity Pool
Cognito Iden,ty Pool (or Cognito Federated Iden,,es) on the other hand is a way to authorize
your users to use the various AWS services. Say you wanted to allow a user to have access to
your S3 bucket so that they could upload a file; you could specify that while crea,ng an
Iden,ty Pool. And to create these levels of access, the Iden,ty Pool has its own concept of an
iden,ty (or user). The source of these iden,,es (or users) could be a Cognito User Pool or
even Facebook or Google.

User Pool vs Identity Pool


To clarify this a bit more, let’s put these two services in context of each other. Here is how
they play together.

No,ce how we could use the User Pool, social networks, or even our own custom
authen,ca,on system as the iden,ty provider for the Cognito Iden,ty Pool. The Cognito
Iden,ty Pool simply takes all your iden,ty providers and puts them together (federates them).
And with all of this it can now give your users secure access to your AWS services, regardless
of where they come from.

So in summary; the Cognito User Pool stores all your users which then plugs into your Cognito
Iden,ty Pool which can give your users access to your AWS services.

Now that we have a good understanding of how our users will be handled, let’s finish up our
backend by tes,ng our APIs.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/cognito-user-
pool-vs-identity-pool/146)
Test the APIs
Now that we have our backend completely set up and secured, let’s test the API we just
deployed.

To be able to hit our API endpoints securely, we need to follow these steps.

1. AuthenAcate against our User Pool and acquire a user token.


2. With the user token get temporary IAM credenAals from our IdenAty Pool.
3. Use the IAM credenAals to sign our API request with Signature Version 4
(hMp://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).

These steps can be a bit tricky to do by hand. So we created a simple tool called AWS API
Gateway Test CLI (hMps://github.com/AnomalyInnovaAons/aws-api-gateway-cli-test).

You can run it using.

$ npx aws-api-gateway-cli-test

The npx command is just a convenient way of running a NPM module without installing it
globally.

We need to pass in quite a bit of our info to complete the above steps.

Use the username and password of the user created in the Create a Cognito test user
(/chapters/create-a-cognito-test-user.html) chapter.
Replace YOUR_COGNITO_USER_POOL_ID, YOUR_COGNITO_APP_CLIENT_ID, and
YOUR_COGNITO_REGION with the values from the Create a Cognito user pool
(/chapters/create-a-cognito-user-pool.html) chapter. In our case the region is us-east-
1 .
Replace YOUR_IDENTITY_POOL_ID with the one from the Create a Cognito idenAty
pool (/chapters/create-a-cognito-idenAty-pool.html) chapter.
Use the YOUR_API_GATEWAY_URL and YOUR_API_GATEWAY_REGION with the ones
from the Deploy the APIs (/chapters/deploy-the-apis.html) chapter. In our case the URL is
https://ly55wbovq4.execute-api.us-east-1.amazonaws.com/prod and the
region is us-east-1 .

And run the following.

$ npx aws-api-gateway-cli-test \
--username='[email protected]' \
--password='Passw0rd!' \
--user-pool-id='YOUR_COGNITO_USER_POOL_ID' \
--app-client-id='YOUR_COGNITO_APP_CLIENT_ID' \
--cognito-region='YOUR_COGNITO_REGION' \
--identity-pool-id='YOUR_IDENTITY_POOL_ID' \
--invoke-url='YOUR_API_GATEWAY_URL' \
--api-gateway-region='YOUR_API_GATEWAY_REGION' \
--path-template='/notes' \
--method='POST' \
--body='{"content":"hello world","attachment":"hello.jpg"}'

While this might look inAmidaAng, just keep in mind that behind the scenes all we are doing is
generaAng some security headers before making a basic HTTP request. You’ll see more of this
process when we connect our React.js app to our API backend.

If you are on Windows, use the command below. The space between each opAon is very
important.

$ npx aws-api-gateway-cli-test --username [email protected] --password


Passw0rd! --user-pool-id YOUR_COGNITO_USER_POOL_ID --app-client-id
YOUR_COGNITO_APP_CLIENT_ID --cognito-region YOUR_COGNITO_REGION --
identity-pool-id YOUR_IDENTITY_POOL_ID --invoke-url
YOUR_API_GATEWAY_URL --api-gateway-region YOUR_API_GATEWAY_REGION --
path-template /notes --method POST --body "{\"content\":\"hello
world\",\"attachment\":\"hello.jpg\"}"

If the command is successful, the response will look similar to this.

Authenticating with User Pool


Getting temporary credentials
Making API request
{ status: 200,
statusText: 'OK',
data:
{ userId: 'us-east-1:9bdc031d-ee9e-4ffa-9a2d-123456789',
noteId: '8f7da030-650b-11e7-a661-123456789',
content: 'hello world',
attachment: 'hello.jpg',
createdAt: 1499648598452 } }

And that’s it for the backend! Next we are going to move on to creaAng the frontend of our
app.

Common Issues

Response {status: 403}

This is the most common issue we come across and it is a bit crypAc and can be hard to
debug. Here are a few things to check before you start debugging:

Ensure the --path-template opAon in the apig-test command is poinAng to


/notes and not notes . The format maMers for securely signing our request.

There are no trailing slashes for YOUR_API_GATEWAY_URL . In our case, the URL is
https://ly55wbovq4.execute-api.us-east-1.amazonaws.com/prod . NoAce
that it does not end with a / .

If you’re on Windows and are using Git Bash, try adding a trailing slash to
YOUR_API_GATEWAY_URL while removing the leading slash from --path-
template . In our case, it would result in --invoke-url
https://ly55wbovq4.execute-api.us-east-1.amazonaws.com/prod/ --
path-template notes . You can follow the discussion on this here
(hMps://github.com/AnomalyInnovaAons/serverless-stack-
com/issues/112#issuecomment-345996566).

There is a good chance that this error is happening even before our Lambda funcAons are
invoked. So we can start by making sure our IAM Roles are configured properly for our
IdenAty Pool. Follow the steps as detailed in our Debugging Serverless API Issues
(/chapters/debugging-serverless-api-issues.html#missing-iam-policy) chapter to ensure
that your IAM Roles have the right set of permissions.

Next, you can enable API Gateway logs (/chapters/api-gateway-and-lambda-


logs.html#enable-api-gateway-cloudwatch-logs) and follow these instrucAons
(/chapters/api-gateway-and-lambda-logs.html#viewing-api-gateway-cloudwatch-logs) to
read the requests that are being logged. This should give you a beMer idea of what is
going on.

Finally, make sure to look at the comment thread below. We’ve helped quite a few people
with similar issues and it’s very likely that somebody has run into a similar issue as you.

Response {status: false}

If instead your command fails with the {status: false} response; we can do a few
things to debug this. This response is generated by our Lambda funcAons when there is
an error. Add a console.log like so in your handler funcAon.

catch(e) {
console.log(e);
callback(null, failure({status: false}));
}

And deploy it using serverless deploy function -f create . But we can’t see this
output when we make an HTTP request to it, since the console logs are not sent in our
HTTP responses. We need to check the logs to see this. We have a detailed chapter
(/chapters/api-gateway-and-lambda-logs.html#viewing-lambda-cloudwatch-logs) on
working with API Gateway and Lambda logs and you can read about how to check your
debug messages here (/chapters/api-gateway-and-lambda-logs.html#viewing-lambda-
cloudwatch-logs).

A common source of errors here is an improperly indented serverless.yml . Make sure


to double-check the indenAng in your serverless.yml by comparing it to the one
from this chapter (hMps://github.com/AnomalyInnovaAons/serverless-stack-demo-
api/blob/master/serverless.yml).

‘User: arn:aws:... is not authorized to perform: dynamodb:PutItem on


resource: arn:aws:dynamodb:...’
This error is basically saying that our Lambda funcAon does not have the right permissions
to make a DynamoDB request. Recall that, the IAM role that allows your Lambda funcAon
to make requests to DynamoDB are set in the serverless.yml . And a common source
of this error is when the iamRoleStatements: are improperly indented. Make sure to
compare it to the one in the repo (hMps://github.com/AnomalyInnovaAons/serverless-
stack-demo-api).

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
test-the-apis/122)

For reference, here is the code for Part I

" Backend Part I Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/handle-api-gateway-cors-errors)
Create a New React.js App
Let’s get started with our frontend. We are going to create a single page app using React.js
(h:ps://facebook.github.io/react/). We’ll use the Create React App
(h:ps://github.com/facebookincubator/create-react-app) project to set everything up. It is
officially supported by the React team and conveniently packages all the dependencies for a
React.js project.

Move out of the directory that we were working in for the backend.

$ cd ../

Create a New React App


Run the following command to create the client for our notes app.

$ npx create-react-app notes-app-client

This should take a second to run, and it will create your new project and your new working
directory.

Now let’s go into our working directory and run our project.

$ cd notes-app-client
$ npm start

This should fire up the newly created app in your browser.


Change the Title
Let’s quickly change the Ntle of our note taking app. Open up
public/index.html and edit the title tag to the following:

<title>Scratch - A simple note taking app</title>

Create React App comes pre-loaded with a pre:y convenient yet minimal development
environment. It includes live reloading, a tesNng framework, ES6 support, and much more
(h:ps://github.com/facebookincubator/create-react-app#why-use-this).

Next, we are going to create our app icon and update the favicons.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-new-
react-js-app/68)
Add App Favicons
Create React App generates a simple favicon for our app and places it in
public/favicon.ico . However, ge:ng the favicon to work on all browsers and mobile
pla>orms requires a li@le more work. There are quite a few different requirements and
dimensions. And this gives us a good opportunity to learn how to include files in the
public/ directory of our app.

For our example, we are going to start with a simple image and generate the various versions
from it.

Right-click to download the following image.

To ensure that our icon works for most of our targeted pla>orms we’ll use a service called the
Favicon Generator (h@p://realfavicongenerator.net).

Click Select your Favicon picture to upload our icon.


Once you upload your icon, it’ll show you a preview of your icon on various pla>orms. Scroll
down the page and hit the Generate your Favicons and HTML code bu@on.
This should generate your favicon package and the accompanying code.

Click Favicon package to download the generated favicons. And copy all the files
over to your public/ directory.
Then replace the contents of public/manifest.json with the following:

{
"short_name": "Scratch",
"name": "Scratch Note Taking App",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#ffffff",
"background_color": "#ffffff"
}

To include a file from the public/ directory in your HTML, Create React App needs the
%PUBLIC_URL% prefix.

Add this to your public/index.html .

<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-


touch-icon.png">
<link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon-
32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon-
16x16.png" sizes="16x16">
<link rel="mask-icon" href="%PUBLIC_URL%/safari-pinned-tab.svg"
color="#5bbad5">
<meta name="theme-color" content="#ffffff">

And remove the following lines that reference the original favicon and theme color.

<meta name="theme-color" content="#000000">


<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

Finally head over to your browser and try the /favicon-32x32.png path to ensure that the
files were added correctly.

Next we are going to look into se:ng up custom fonts in our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-app-
favicons/155)
Set up Custom Fonts
Custom Fonts are now an almost standard part of modern web applica5ons. We’ll be se9ng it
up for our note taking app using Google Fonts (h?ps://fonts.google.com).

This also gives us a chance to explore the structure of our newly created React.js app.

Include Google Fonts


For our project we’ll be using the combina5on of a Serif (PT Serif
(h?ps://fonts.google.com/specimen/PT+Serif)) and Sans-Serif (Open Sans
(h?ps://fonts.google.com/specimen/Open+Sans)) typeface. They will be served out through
Google Fonts and can be used directly without having to host them on our end.

Let’s first include them in the HTML. Our React.js app is using a single HTML file.

Go ahead and edit public/index.html and add the following line in the
<head> sec5on of the HTML to include the two typefaces.

<link rel="stylesheet" type="text/css"


href="https://fonts.googleapis.com/css?
family=PT+Serif|Open+Sans:300,400,600,700,800">

Here we are referencing all the 5 different weights (300, 400, 600, 700, and 800) of the Open
Sans typeface.

Add the Fonts to the Styles


Now we are ready to add our newly added fonts to our stylesheets. Create React App helps
separate the styles for our individual components and has a master stylesheet for the project
located in src/index.css .

Let’s change the current font in src/index.css for the body tag to the
following.
body {
margin: 0;
padding: 0;
font-family: "Open Sans", sans-serif;
font-size: 16px;
color: #333;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

And let’s change the fonts for the header tags to our new Serif font by adding this
block to the css file.

h1, h2, h3, h4, h5, h6 {


font-family: "PT Serif", serif;
}

Now if you just flip over to your browser with our new app, you should see the new fonts
update automa5cally; thanks to the live reloading.
We’ll stay on the theme of adding styles and set up our project with Bootstrap to ensure that
we have a consistent UI Kit to work with while building our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/set-up-custom-
fonts/81)
Set up Bootstrap
A big part of wri-ng web applica-ons is having a UI Kit to help create the interface of the
applica-on. We are going to use Bootstrap (h=p://getbootstrap.com) for our note taking app.
While Bootstrap can be used directly with React; the preferred way is to use it with the React-
Bootstrap (h=ps://react-bootstrap.github.io) package. This makes our markup a lot simpler to
implement and understand.

Installing React Bootstrap


Run the following command in your working directory.

$ npm install [email protected] --save

This installs the NPM package and adds the dependency to your package.json .

Add Bootstrap Styles


React Bootstrap uses the standard Bootstrap v3 styles; so just add the following
styles to your public/index.html .

<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"

We’ll also tweak the styles of the form fields so that the mobile browser does not zoom in on
them on focus. We just need them to have a minimum font size of 16px to prevent the
zoom.

To do that, let’s add the following to our src/index.css .

select.form-control,
textarea.form-control,
input.form-control {
font-size: 16px;
}
input[type=file] {
width: 100%;
}

We are also seRng the width of the input type file to prevent the page on mobile from
overflowing and adding a scrollbar.

Now if you head over to your browser, you might no-ce that the styles have shiTed a bit. This
is because Bootstrap includes Normalize.css (h=p://necolas.github.io/normalize.css/) to have a
more consistent styles across browsers.

Next, we are going to create a few routes for our applica-on and set up the React Router.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/set-up-
bootstrap/118)
Handle Routes with React Router
Create React App sets a lot of things up by default but it does not come with a built-in way to
handle routes. And since we are building a single page app, we are going to use React Router
(h=ps://reac=raining.com/react-router/) to handle them for us.

Let’s start by installing React Router. We are going to be using the React Router v4, the
newest version of React Router. React Router v4 can be used on the web and in naFve. So
let’s install the one for the web.

Installing React Router v4


Run the following command in your working directory.

$ npm install [email protected] --save

This installs the NPM package and adds the dependency to your package.json .

Setting up React Router


Even though we don’t have any routes set up in our app, we can get the basic structure up and
running. Our app currently runs from the App component in src/App.js . We are going to
be using this component as the container for our enFre app. To do that we’ll encapsulate our
App component within a Router .

Replace the following code in src/index.js :

ReactDOM.render(<App />, document.getElementById('root'));

With this:

ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById("root")
);

And import this in the header of src/index.js .

import { BrowserRouter as Router } from "react-router-dom";

We’ve made two small changes here.

1. Use BrowserRouter as our router. This uses the browser’s History


(h=ps://developer.mozilla.org/en-US/docs/Web/API/History) API to create real URLs.
2. Use the Router to render our App component. This will allow us to create the routes
we need inside our App component.

Now if you head over to your browser, your app should load just like before. The only
difference being that we are using React Router to serve out our pages.

Next we are going to look into how to organize the different pages of our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/handle-routes-
with-react-router/116)
Create Containers
Currently, our app has a single component that renders our content. For crea7ng our note
taking app, we need to create a few different pages to load/edit/create notes. Before we can
do that we will put the outer chrome of our app inside a component and render all the top
level components inside them. These top level components that represent the various pages
will be called containers.

Add a Navbar
Let’s start by crea7ng the outer chrome of our applica7on by first adding a naviga7on bar to it.
We are going to use the Navbar (hGps://react-bootstrap.github.io/components/navbar/)
React-Bootstrap component.

To start, you can go remove the src/logo.svg that is placed there by Create
React App.

$ rm src/logo.svg

And go ahead and remove the code inside src/App.js and replace it with the
following.

import React, { Component } from "react";


import { Link } from "react-router-dom";
import { Navbar } from "react-bootstrap";
import "./App.css";

class App extends Component {


render() {
return (
<div className="App container">
<Navbar fluid collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Scratch</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
</Navbar>
</div>
);
}
}

export default App;

We are doing a few things here:

1. Crea7ng a fixed width container using Bootstrap in div.container .


2. Adding a Navbar inside the container that fits to its container’s width using the aGribute
fluid .
3. Using Link component from the React-Router to handle the link to our app’s homepage
(without forcing the page to refresh).

Let’s also add a couple of line of styles to space things out a bit more.

Remove all the code inside src/App.css and replace it with the following:

.App {
margin-top: 15px;
}

.App .navbar-brand {
font-weight: bold;
}

Add the Home container


Now that we have the outer chrome of our applica7on ready, let’s add the container for the
homepage of our app. It’ll respond to the / route.

Create a src/containers/ directory by running the following in your working


directory.

$ mkdir src/containers/

We’ll be storing all of our top level components here. These are components that will respond
to our routes and make requests to our API. We will be calling them containers through the
rest of this tutorial.

Create a new container and add the following to src/containers/Home.js .

import React, { Component } from "react";


import "./Home.css";

export default class Home extends Component {


render() {
return (
<div className="Home">
<div className="lander">
<h1>Scratch</h1>
<p>A simple note taking app</p>
</div>
</div>
);
}
}

This simply renders our homepage given that the user is not currently signed in.

Now let’s add a few lines to style this.

Add the following into src/containers/Home.css .

.Home .lander {
padding: 80px 0;
text-align: center;
}

.Home .lander h1 {
font-family: "Open Sans", sans-serif;
font-weight: 600;
}

.Home .lander p {
color: #999;
}

Set up the Routes


Now we’ll set up the routes so that we can have this container respond to the / route.

Create src/Routes.js and add the following into it.

import React from "react";


import { Route, Switch } from "react-router-dom";
import Home from "./containers/Home";

export default () =>


<Switch>
<Route path="/" exact component={Home} />
</Switch>;

This component uses this Switch component from React-Router that renders the first
matching route that is defined within it. For now we only have a single route, it looks for /
and renders the Home component when matched. We are also using the exact prop to
ensure that it matches the / route exactly. This is because the path / will also match any
route that starts with a / .

Render the Routes


Now let’s render the routes into our App component.

Add the following to the header of your src/App.js .

import Routes from "./Routes";


And add the following line below our Navbar component inside the render of
src/App.js .

<Routes />

So the render method of our src/App.js should now look like this.

render() {
return (
<div className="App container">
<Navbar fluid collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Scratch</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
</Navbar>
<Routes />
</div>
);
}

This ensures that as we navigate to different routes in our app, the por7on below the navbar
will change to reflect that.

Finally, head over to your browser and your app should show the brand new homepage of
your app.
Next we are going to add login and signup links to our navbar.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-
containers/62)
Adding Links in the Navbar
Now that we have our first route set up, let’s add a couple of links to the navbar of our app.
These will direct users to login or signup for our app when they first visit it.

Replace the render method in src/App.js with the following.

render() {
return (
<div className="App container">
<Navbar fluid collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Scratch</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
<NavItem href="/signup">Signup</NavItem>
<NavItem href="/login">Login</NavItem>
</Nav>
</Navbar.Collapse>
</Navbar>
<Routes />
</div>
);
}

This adds two links to our navbar using the NavItem Bootstrap component. The
Navbar.Collapse component ensures that on mobile devices the two links will be
collapsed.

And let’s include the necessary components in the header.


Replace the react-router-dom and react-bootstrap import in
src/App.js with this.

import { Link } from "react-router-dom";


import { Nav, Navbar, NavItem } from "react-bootstrap";

Now if you flip over to your browser, you should see the two links in our navbar.

Unfortunately, when you click on them they refresh your browser while redirecCng to the link.
We need it to route it to the new link without refreshing the page since we are building a
single page app.

To fix this we need a component that works with React Router and React Bootstrap called
React Router Bootstrap (hGps://github.com/react-bootstrap/react-router-bootstrap). It can
wrap around your Navbar links and use the React Router to route your app to the required
link without refreshing the browser.

Run the following command in your working directory.

$ npm install react-router-bootstrap --save


And include it at the top of your src/App.js .

import { LinkContainer } from "react-router-bootstrap";

We will now wrap our links with the LinkContainer . Replace the render
method in your src/App.js with this.

render() {
return (
<div className="App container">
<Navbar fluid collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Scratch</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
<LinkContainer to="/signup">
<NavItem>Signup</NavItem>
</LinkContainer>
<LinkContainer to="/login">
<NavItem>Login</NavItem>
</LinkContainer>
</Nav>
</Navbar.Collapse>
</Navbar>
<Routes />
</div>
);
}

And that’s it! Now if you flip over to your browser and click on the login link, you should see
the link highlighted in the navbar. Also, it doesn’t refresh the page while redirecCng.
You’ll noCce that we are not rendering anything on the page because we don’t have a login
page currently. We should handle the case when a requested page is not found.

Next let’s look at how to tackle handling 404s with our router.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/adding-links-
in-the-navbar/141)
Handle 404s
Now that we know how to handle the basic routes; let’s look at handling 404s with the React
Router.

Create a Component
Let’s start by crea<ng a component that will handle this for us.

Create a new component at src/containers/NotFound.js and add the


following.

import React from "react";


import "./NotFound.css";

export default () =>


<div className="NotFound">
<h3>Sorry, page not found!</h3>
</div>;

All this component does is print out a simple message for us.

Let’s add a couple of styles for it in src/containers/NotFound.css .

.NotFound {
padding-top: 100px;
text-align: center;
}

Add a Catch All Route


Now we just need to add this component to our routes to handle our 404s.

Find the <Switch> block in src/Routes.js and add it as the last line in that
sec<on.

{ /* Finally, catch all unmatched routes */ }


<Route component={NotFound} />

This needs to always be the last line in the <Route> block. You can think of it as the route
that handles requests in case all the other routes before it have failed.

And include the NotFound component in the header by adding the following:

import NotFound from "./containers/NotFound";

And that’s it! Now if you were to switch over to your browser and try clicking on the Login or
Signup buKons in the Nav you should see the 404 message that we have.

Next up, we are going to configure our app with the info of our backend resources.

For help and discussion


! Comments on this chapter
(https://discourse.serverless-stack.com/t/handle-
404s/75)
Configure AWS Amplify
To allow our React app to talk to the AWS resources that we created (in the backend sec9on
of the tutorial), we’ll be using a library called AWS Amplify (hAps://github.com/aws/aws-
amplify).

AWS Amplify provides a few simple modules (Auth, API, and Storage) to help us easily connect
to our backend. Let’s get started.

Install AWS Amplify


Run the following command in your working directory.

$ npm install aws-amplify --save

This installs the NPM package and adds the dependency to your package.json .

Create a Config
Let’s first create a configura9on file for our app that’ll reference all the resources we have
created.

Create a file at src/config.js and add the following.

export default {
s3: {
REGION: "YOUR_S3_UPLOADS_BUCKET_REGION",
BUCKET: "YOUR_S3_UPLOADS_BUCKET_NAME"
},
apiGateway: {
REGION: "YOUR_API_GATEWAY_REGION",
URL: "YOUR_API_GATEWAY_URL"
},
cognito: {
REGION: "YOUR_COGNITO_REGION",
USER_POOL_ID: "YOUR_COGNITO_USER_POOL_ID",
APP_CLIENT_ID: "YOUR_COGNITO_APP_CLIENT_ID",
IDENTITY_POOL_ID: "YOUR_IDENTITY_POOL_ID"
}
};

Here you need to replace the following:

1. YOUR_S3_UPLOADS_BUCKET_NAME and YOUR_S3_UPLOADS_BUCKET_REGION with the


your S3 Bucket name and region from the Create an S3 bucket for file uploads
(/chapters/create-an-s3-bucket-for-file-uploads.html) chapter. In our case it is notes-
app-uploads and us-east-1 .

2. YOUR_API_GATEWAY_URL and YOUR_API_GATEWAY_REGION with the ones from the


Deploy the APIs (/chapters/deploy-the-apis.html) chapter. In our case the URL is
https://ly55wbovq4.execute-api.us-east-1.amazonaws.com/prod and the
region is us-east-1 .

3. YOUR_COGNITO_USER_POOL_ID , YOUR_COGNITO_APP_CLIENT_ID , and


YOUR_COGNITO_REGION with the Cognito Pool Id, App Client id, and region from the
Create a Cognito user pool (/chapters/create-a-cognito-user-pool.html) chapter.

4. YOUR_IDENTITY_POOL_ID with your Iden.ty pool ID from the Create a Cognito


iden9ty pool (/chapters/create-a-cognito-iden9ty-pool.html) chapter.

Add AWS Amplify


Next we’ll set up AWS Amplify.

Import it by adding the following to the header of your src/index.js .

import Amplify from "aws-amplify";

And import the config we created above.

Add the following, also to the header of your src/index.js .


import config from "./config";

And to ini9alize AWS Amplify; add the following above the ReactDOM.render
line in src/index.js .

Amplify.configure({
Auth: {
mandatorySignIn: true,
region: config.cognito.REGION,
userPoolId: config.cognito.USER_POOL_ID,
identityPoolId: config.cognito.IDENTITY_POOL_ID,
userPoolWebClientId: config.cognito.APP_CLIENT_ID
},
Storage: {
region: config.s3.REGION,
bucket: config.s3.BUCKET,
identityPoolId: config.cognito.IDENTITY_POOL_ID
},
API: {
endpoints: [
{
name: "notes",
endpoint: config.apiGateway.URL,
region: config.apiGateway.REGION
},
]
}
});

A couple of notes here.

Amplify refers to Cognito as Auth , S3 as Storage , and API Gateway as API .

The mandatorySignIn flag for Auth is set to true because we want our users to be
signed in before they can interact with our app.

The name: "notes" is basically telling Amplify that we want to name our API. Amplify
allows you to add mul9ple APIs that your app is going to work with. In our case our en9re
backend is just one single API.

The Amplify.configure() is just se\ng the various AWS resources that we want to
interact with. It isn’t doing anything else special here beside configura9on. So while this
might look in9mida9ng, just remember this is only se\ng things up.

Next up, we are going to work on crea9ng our login and sign up forms.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/configure-aws-
amplify/151)
Create a Login Page
Let’s create a page where the users of our app can login with their creden5als. When we
created our User Pool we asked it to allow a user to sign in and sign up with their email as
their username. We’ll be touching on this further when we create the signup form.

So let’s start by crea5ng the basic form that’ll take the user’s email (as their username) and
password.

Add the Container


Create a new file src/containers/Login.js and add the following.

import React, { Component } from "react";


import { Button, FormGroup, FormControl, ControlLabel } from "react-
bootstrap";
import "./Login.css";

export default class Login extends Component {


constructor(props) {
super(props);

this.state = {
email: "",
password: ""
};
}

validateForm() {
return this.state.email.length > 0 && this.state.password.length >
0;
}

handleChange = event => {


this.setState({
[event.target.id]: event.target.value
});
}

handleSubmit = event => {


event.preventDefault();
}

render() {
return (
<div className="Login">
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="email" bsSize="large">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={this.state.email}
onChange={this.handleChange}
/>
</FormGroup>
<FormGroup controlId="password" bsSize="large">
<ControlLabel>Password</ControlLabel>
<FormControl
value={this.state.password}
onChange={this.handleChange}
type="password"
/>
</FormGroup>
<Button
block
bsSize="large"
disabled={!this.validateForm()}
type="submit"
>
Login
</Button>
</form>
</div>
);
}
}

We are introducing a couple of new concepts in this.

1. In the constructor of our component we create a state object. This will be where we’ll
store what the user enters in the form.

2. We then connect the state to our two fields in the form by seHng this.state.email
and this.state.password as the value in our input fields. This means that when
the state changes, React will re-render these components with the updated value.

3. But to update the state when the user types something into these fields, we’ll call a
handle func5on named handleChange . This func5on grabs the id (set as
controlId for the <FormGroup> ) of the field being changed and updates its state
with the value the user is typing in. Also, to have access to the this keyword inside
handleChange we store the reference to an anonymous func5on like so:
handleChange = (event) => { } .

4. We are seHng the autoFocus flag for our email field, so that when our form loads, it
sets focus to this field.

5. We also link up our submit buTon with our state by using a validate func5on called
validateForm . This simply checks if our fields are non-empty, but can easily do
something more complicated.

6. Finally, we trigger our callback handleSubmit when the form is submiTed. For now we
are simply suppressing the browsers default behavior on submit but we’ll do more here
later.

Let’s add a couple of styles to this in the file src/containers/Login.css .

@media all and (min-width: 480px) {


.Login {
padding: 60px 0;
}
.Login form {
margin: 0 auto;
max-width: 320px;
}
}

These styles roughly target any non-mobile screen sizes.

Add the Route


Now we link this container up with the rest of our app by adding the following line
to src/Routes.js below our home <Route> .

<Route path="/login" exact component={Login} />

And include our component in the header.

import Login from "./containers/Login";

Now if we switch to our browser and navigate to the login page we should see our newly
created form.
Next, let’s connect our login form to our AWS Cognito set up.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
login-page/71)
Login with AWS Cognito
We are going to use AWS Amplify to login to our Amazon Cognito setup. Let’s start by
impor:ng it.

Import Auth from AWS Amplify


Add the following to the header of our Login container in
src/containers/Login.js .

import { Auth } from "aws-amplify";

Login to Amazon Cognito


The login code itself is rela:vely simple.

Simply replace our placeholder handleSubmit method in


src/containers/Login.js with the following.

handleSubmit = async event => {


event.preventDefault();

try {
await Auth.signIn(this.state.email, this.state.password);
alert("Logged in");
} catch (e) {
alert(e.message);
}
}

We are doing two things of note here.

1. We grab the email and password from this.state and call Amplify’s
Auth.signIn() method with it. This method returns a promise since it will be logging
the user asynchronously.

2. We use the await keyword to invoke the Auth.signIn() method that returns a
promise. And we need to label our handleSubmit method as async .

Now if you try to login using the [email protected] user (that we created in the Create a
Cognito Test User (/chapters/create-a-cognito-test-user.html) chapter), you should see the
browser alert that tells you that the login was successful.

Next, we’ll take a look at storing the login state in our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/login-with-
aws-cognito/129)
Add the Session to the State
To complete the login process we would need to update the app state with the session to
reflect that the user has logged in.

Update the App State


First we’ll start by upda:ng the applica:on state by se;ng that the user is logged in. We
might be tempted to store this in the Login container, but since we are going to use this in a
lot of other places, it makes sense to li@ up the state. The most logical place to do this will be
in our App component.

Add the following to src/App.js right below the class App extends
Component { line.

constructor(props) {
super(props);

this.state = {
isAuthenticated: false
};
}

userHasAuthenticated = authenticated => {


this.setState({ isAuthenticated: authenticated });
}

This ini:alizes the isAuthenticated flag in the App’s state. And calling
userHasAuthenticated updates it. But for the Login container to call this method we
need to pass a reference of this method to it.

Pass the Session State to the Routes


We can do this by passing in a couple of props to the child component of the routes that the
App component creates.

Add the following right below the render() { line in src/App.js .

const childProps = {
isAuthenticated: this.state.isAuthenticated,
userHasAuthenticated: this.userHasAuthenticated
};

And pass them into our Routes component by replacing the following line in the
render method of src/App.js .

<Routes />

With this.

<Routes childProps={childProps} />

Currently, our Routes component does not do anything with the passed in childProps .
We need it to apply these props to the child component it is going to render. In this case we
need it to apply them to our Login component.

To do this we are going to create a new component.

Create a src/components/ directory by running this command in your working


directory.

$ mkdir src/components/

Here we’ll be storing all our React components that are not dealing directly with our API or
responding to routes.

Create a new component in src/components/AppliedRoute.js and add the


following.

import React from "react";


import { Route } from "react-router-dom";
export default ({ component: C, props: cProps, ...rest }) =>
<Route {...rest} render={props => <C {...props} {...cProps} />} />;

This simple component creates a Route where the child component that it renders contains
the passed in props. Let’s take a quick look at how this being done.

The Route component takes a prop called component that represents the component
that will be rendered when a matching route is found. We want our childProps to be
sent to this component.

The Route component can also take a render method in place of the component .
This allows us to control what is passed in to our component.

Based on this we can create a component that returns a Route and takes a
component and childProps prop. This allows us to pass in the component we want
rendered and the props that we want applied.

Finally, we take component (set as C ) and props (set as cProps ) and render inside
our Route using the inline func:on; props => <C {...props} {...cProps} /> .
Note, the props variable in this case is what the Route component passes us. Whereas,
the cProps is the childProps that we want to set.

Now to use this component, we are going to include it in the routes where we need to have
the childProps passed in.

Replace the export default () => ( method in src/Routes.js with the


following.

export default ({ childProps }) =>


<Switch>
<AppliedRoute path="/" exact component={Home} props={childProps}
/>
<AppliedRoute path="/login" exact component={Login} props=
{childProps} />
{ /* Finally, catch all unmatched routes */ }
<Route component={NotFound} />
</Switch>;
And import the new component in the header of src/Routes.js .

import AppliedRoute from "./components/AppliedRoute";

Now in the Login container we’ll call the userHasAuthenticated method.

Replace the alert('Logged in'); line with the following in


src/containers/Login.js .

this.props.userHasAuthenticated(true);

Create a Logout Button


We can now use this to display a Logout buPon once the user logs in. Find the following in our
src/App.js .

<LinkContainer to="/signup">
<NavItem>Signup</NavItem>
</LinkContainer>
<LinkContainer to="/login">
<NavItem>Login</NavItem>
</LinkContainer>

And replace it with this:

{this.state.isAuthenticated
? <NavItem onClick={this.handleLogout}>Logout</NavItem>
: <Fragment>
<LinkContainer to="/signup">
<NavItem>Signup</NavItem>
</LinkContainer>
<LinkContainer to="/login">
<NavItem>Login</NavItem>
</LinkContainer>
</Fragment>
}
Also, import the Fragment in the header.

Replace the import React line in the header of src/App.js with the
following.

import React, { Component, Fragment } from "react";

The Fragment component can be thought of as a placeholder component. We need this


because in the case the user is not logged in, we want to render two links. To do this we would
need to wrap it inside a single component, like a div . But by using the Fragment
component it tells React that the two links are inside this component but we don’t want to
render any extra HTML.

And add this handleLogout method to src/App.js above the render() {


line as well.

handleLogout = event => {


this.userHasAuthenticated(false);
}

Now head over to your browser and try logging in with the admin creden:als we created in
the Create a Cognito Test User (/chapters/create-a-cognito-test-user.html) chapter. You
should see the Logout buPon appear right away.
Now if you refresh your page you should be logged out again. This is because we are not
ini:alizing the state from the browser session. Let’s look at how to do that next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-the-
session-to-the-state/136)
Load the State from the Session
To make our login informa/on persist we need to store and load it from the browser session.
There are a few different ways we can do this, using Cookies or Local Storage. Thankfully the
AWS Amplify does this for us automa/cally and we just need to read from it and load it into
our applica/on state.

Amplify gives us a way to get the current user session using the Auth.currentSession()
method. It returns a promise that resolves to the session object (if there is one).

Load User Session


Let’s load this when our app loads. We are going to do this in componentDidMount . Since
Auth.currentSession() returns a promise, it means that we need to ensure that the rest
of our app is only ready to go aGer this has been loaded.

To do this, let’s add a flag to our src/App.js state called isAuthenticating .


The ini/al state in our constructor should look like the following.

this.state = {
isAuthenticated: false,
isAuthenticating: true
};

Let’s include the Auth module by adding the following to the header of
src/App.js .

import { Auth } from "aws-amplify";

Now to load the user session we’ll add the following to our src/App.js below
our constructor method.

async componentDidMount() {
try {
await Auth.currentSession();
this.userHasAuthenticated(true);
}
catch(e) {
if (e !== 'No current user') {
alert(e);
}
}

this.setState({ isAuthenticating: false });


}

All this does is load the current session. If it loads, then it updates the isAuthenticating
flag once the process is complete. The Auth.currentSession() method throws an error
No current user if nobody is currently logged in. We don’t want to show this error to
users when they load up our app and are not signed in.

Render When the State Is Ready


Since loading the user session is an asynchronous process, we want to ensure that our app
does not change states when it first loads. To do this we’ll hold off rendering our app /ll
isAuthenticating is false .

We’ll condi/onally render our app based on the isAuthenticating flag.

Our render method in src/App.js should be as follows.

render() {
const childProps = {
isAuthenticated: this.state.isAuthenticated,
userHasAuthenticated: this.userHasAuthenticated
};

return (
!this.state.isAuthenticating &&
<div className="App container">
<Navbar fluid collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Scratch</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
{this.state.isAuthenticated
? <NavItem onClick={this.handleLogout}>Logout</NavItem>
: <Fragment>
<LinkContainer to="/signup">
<NavItem>Signup</NavItem>
</LinkContainer>
<LinkContainer to="/login">
<NavItem>Login</NavItem>
</LinkContainer>
</Fragment>
}
</Nav>
</Navbar.Collapse>
</Navbar>
<Routes childProps={childProps} />
</div>
);
}

Now if you head over to your browser and refresh the page, you should see that a user is
logged in.
Unfortunately, when we hit Logout and refresh the page; we are s/ll logged in. To fix this we
are going to clear the session on logout next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/load-the-
state-from-the-session/157)
Clear the Session on Logout
Currently we are only removing the user session from our app’s state. But when we refresh
the page, we load the user session from the browser Local Storage (using Amplify), in effect
logging them back in.

AWS Amplify has a Auth.signOut() method that helps clear it out.

Let’s replace the handleLogout method in our src/App.js with this:

handleLogout = async event => {


await Auth.signOut();

this.userHasAuthenticated(false);
}

Now if you head over to your browser, logout and then refresh the page; you should be logged
out completely.

If you try out the enHre login flow from the beginning you’ll noHce that, we conHnue to stay
on the login page through out the enHre process. Next, we’ll look at redirecHng the page aKer
we login and logout to make the flow make more sense.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/clear-the-
session-on-logout/70)
Redirect on Login and Logout
To complete the login flow we are going to need to do two more things.

1. Redirect the user to the homepage a8er they login.


2. And redirect them back to the login page a8er they logout.

We are going to use the history.push method that comes with React Router v4.

Redirect to Home on Login


Since our Login component is rendered using a Route , it adds the router props to it. So
we can redirect using the this.props.history.push method.

this.props.history.push("/");

Update the handleSubmit method in src/containers/Login.js to look like


this:

handleSubmit = async event => {


event.preventDefault();

try {
await Auth.signIn(this.state.email, this.state.password);
this.props.userHasAuthenticated(true);
this.props.history.push("/");
} catch (e) {
alert(e.message);
}
}

Now if you head over to your browser and try logging in, you should be redirected to the
homepage a8er you’ve been logged in.
Redirect to Login After Logout
Now we’ll do something very similar for the logout process. However, the App component
does not have access to the router props directly since it is not rendered inside a Route
component. To be able to use the router props in our App component we will need to use
the withRouter Higher-Order Component (hMps://facebook.github.io/react/docs/higher-
order-components.html) (or HOC). You can read more about the withRouter HOC here
(hMps://reacMraining.com/react-router/web/api/withRouter).

To use this HOC, we’ll change the way we export our App component.

Replace the following line in src/App.js .

export default App;

With this.

export default withRouter(App);


And import withRouter by replacing the import { Link } line in the header
of src/App.js with this:

import { Link, withRouter } from "react-router-dom";

Add the following to the boMom of the handleLogout method in our


src/App.js .

this.props.history.push("/login");

So our handleLogout method should now look like this.

handleLogout = async event => {


await Auth.signOut();

this.userHasAuthenticated(false);

this.props.history.push("/login");
}

This redirects us back to the login page once the user logs out.

Now if you switch over to your browser and try logging out, you should be redirected to the
login page.

You might have noRced while tesRng this flow that since the login call has a bit of a delay, we
might need to give some feedback to the user that the login call is in progress. Let’s do that
next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/redirect-on-
login-and-logout/154)
Give Feedback While Logging In
It’s important that we give the user some feedback while we are logging them in. So they get
the sense that the app is s<ll working, as opposed to being unresponsive.

Use an isLoading Flag


To do this we are going to add an isLoading flag to the state of our
src/containers/Login.js . So the ini<al state in the constructor looks like the
following.

this.state = {
isLoading: false,
email: "",
password: ""
};

And we’ll update it while we are logging in. So our handleSubmit method now
looks like so:

handleSubmit = async event => {


event.preventDefault();

this.setState({ isLoading: true });

try {
await Auth.signIn(this.state.email, this.state.password);
this.props.userHasAuthenticated(true);
this.props.history.push("/");
} catch (e) {
alert(e.message);
this.setState({ isLoading: false });
}
}

Create a Loader Button


Now to reflect the state change in our buCon we are going to render it differently based on
the isLoading flag. But we are going to need this piece of code in a lot of different places.
So it makes sense that we create a reusable component out of it.

Create a new file and add the following in src/components/LoaderButton.js .

import React from "react";


import { Button, Glyphicon } from "react-bootstrap";
import "./LoaderButton.css";

export default ({
isLoading,
text,
loadingText,
className = "",
disabled = false,
...props
}) =>
<Button
className={`LoaderButton ${className}`}
disabled={disabled || isLoading}
{...props}
>
{isLoading && <Glyphicon glyph="refresh" className="spinning" />}
{!isLoading ? text : loadingText}
</Button>;

This is a really simple component that takes an isLoading flag and the text that the buCon
displays in the two states (the default state and the loading state). The disabled prop is a
result of what we have currently in our Login buCon. And we ensure that the buCon is
disabled when isLoading is true . This makes it so that the user can’t click it while we are
in the process of logging them in.
And let’s add a couple of styles to animate our loading icon.

Add the following to src/components/LoaderButton.css .

.LoaderButton .spinning.glyphicon {
margin-right: 7px;
top: 2px;
animation: spin 1s infinite linear;
}
@keyframes spin {
from { transform: scale(1) rotate(0deg); }
to { transform: scale(1) rotate(360deg); }
}

This spins the refresh Glyphicon infinitely with each spin taking a second. And by adding these
styles as a part of the LoaderButton we keep them self contained within the component.

Render Using the isLoading Flag


Now we can use our new component in our Login container.

In src/containers/Login.js find the <Button> component in the render


method.

<Button
block
bsSize="large"
disabled={!this.validateForm()}
type="submit"
>
Login
</Button>

And replace it with this.

<LoaderButton
block
bsSize="large"
disabled={!this.validateForm()}
type="submit"
isLoading={this.state.isLoading}
text="Login"
loadingText="Logging in…"
/>

Also, import the LoaderButton in the header. And remove the reference to the
Button component.

import { FormGroup, FormControl, ControlLabel } from "react-


bootstrap";
import LoaderButton from "../components/LoaderButton";

And now when we switch over to the browser and try logging in, you should see the
intermediate state before the login completes.

If you would like to add Forgot Password func<onality for your users, you can refer to our Extra
Credit series of chapters on user management (/chapters/manage-user-accounts-in-aws-
amplify.html).
Next let’s implement the sign up process for our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/give-feedback-
while-logging-in/46)
Create a Signup Page
The signup page is quite similar to the login page that we just created. But it has a couple of
key differences. When we sign the user up, AWS Cognito sends them a confirmaBon code via
email. We also need to authenBcate the new user once they’ve confirmed their account.

So the signup flow will look something like this:

1. The user types in their email, password, and confirms their password.

2. We sign them up with Amazon Cognito using the AWS Amplify library and get a user
object in return.

3. We then render a form to accept the confirmaBon code that AWS Cognito has emailed to
them.

4. We confirm the sign up by sending the confirmaBon code to AWS Cognito.

5. We authenBcate the newly created user.

6. Finally, we update the app state with the session.

So let’s get started by creaBng the basic sign up form first.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
signup-page/65)
Create the Signup Form
Let’s start by crea,ng the signup form that’ll get the user’s email and password.

Add the Container


Create a new container at src/containers/Signup.js with the following.

import React, { Component } from "react";


import {
HelpBlock,
FormGroup,
FormControl,
ControlLabel
} from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./Signup.css";

export default class Signup extends Component {


constructor(props) {
super(props);

this.state = {
isLoading: false,
email: "",
password: "",
confirmPassword: "",
confirmationCode: "",
newUser: null
};
}

validateForm() {
return (
this.state.email.length > 0 &&
this.state.password.length > 0 &&
this.state.password === this.state.confirmPassword
);
}

validateConfirmationForm() {
return this.state.confirmationCode.length > 0;
}

handleChange = event => {


this.setState({
[event.target.id]: event.target.value
});
}

handleSubmit = async event => {


event.preventDefault();

this.setState({ isLoading: true });

this.setState({ newUser: "test" });

this.setState({ isLoading: false });


}

handleConfirmationSubmit = async event => {


event.preventDefault();

this.setState({ isLoading: true });


}

renderConfirmationForm() {
return (
<form onSubmit={this.handleConfirmationSubmit}>
<FormGroup controlId="confirmationCode" bsSize="large">
<ControlLabel>Confirmation Code</ControlLabel>
<FormControl
autoFocus
type="tel"
value={this.state.confirmationCode}
onChange={this.handleChange}
/>
<HelpBlock>Please check your email for the code.</HelpBlock>
</FormGroup>
<LoaderButton
block
bsSize="large"
disabled={!this.validateConfirmationForm()}
type="submit"
isLoading={this.state.isLoading}
text="Verify"
loadingText="Verifying…"
/>
</form>
);
}

renderForm() {
return (
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="email" bsSize="large">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={this.state.email}
onChange={this.handleChange}
/>
</FormGroup>
<FormGroup controlId="password" bsSize="large">
<ControlLabel>Password</ControlLabel>
<FormControl
value={this.state.password}
onChange={this.handleChange}
type="password"
/>
</FormGroup>
<FormGroup controlId="confirmPassword" bsSize="large">
<ControlLabel>Confirm Password</ControlLabel>
<FormControl
value={this.state.confirmPassword}
onChange={this.handleChange}
type="password"
/>
</FormGroup>
<LoaderButton
block
bsSize="large"
disabled={!this.validateForm()}
type="submit"
isLoading={this.state.isLoading}
text="Signup"
loadingText="Signing up…"
/>
</form>
);
}

render() {
return (
<div className="Signup">
{this.state.newUser === null
? this.renderForm()
: this.renderConfirmationForm()}
</div>
);
}
}

Most of the things we are doing here are fairly straigh<orward but let’s go over them quickly.

1. Since we need to show the user a form to enter the confirma,on code, we are
condi,onally rendering two forms based on if we have a user object or not.
2. We are using the LoaderButton component that we created earlier for our submit
buGons.

3. Since we have two forms we have two valida,on methods called validateForm and
validateConfirmationForm .

4. We are seJng the autoFocus flags on the email and the confirma,on code fields.

5. For now our handleSubmit and handleConfirmationSubmit don’t do a whole lot


besides seJng the isLoading state and a dummy value for the newUser state.

Also, let’s add a couple of styles in src/containers/Signup.css .

@media all and (min-width: 480px) {


.Signup {
padding: 60px 0;
}

.Signup form {
margin: 0 auto;
max-width: 320px;
}
}

.Signup form span.help-block {


font-size: 14px;
padding-bottom: 10px;
color: #999;
}

Add the Route


Finally, add our container as a route in src/Routes.js below our login route. We
are using the AppliedRoute component that we created in the Add the session to the state
(/chapters/add-the-session-to-the-state.html) chapter.

<AppliedRoute path="/signup" exact component={Signup} props=


{childProps} />
And include our component in the header.

import Signup from "./containers/Signup";

Now if we switch to our browser and navigate to the signup page we should see our newly
created form. Our form doesn’t do anything when we enter in our info but you can s,ll try to
fill in an email address, password, and the confirma,on code. It’ll give you an idea of how the
form will behave once we connect it to Cognito.

Next, let’s connect our signup form to Amazon Cognito.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-the-
signup-form/52)
Signup with AWS Cognito
Now let’s go ahead and implement the handleSubmit and handleConfirmationSubmit
methods and connect it up with our AWS Cognito setup.

Replace our handleSubmit and handleConfirmationSubmit methods in


src/containers/Signup.js with the following.

handleSubmit = async event => {


event.preventDefault();

this.setState({ isLoading: true });

try {
const newUser = await Auth.signUp({
username: this.state.email,
password: this.state.password
});
this.setState({
newUser
});
} catch (e) {
alert(e.message);
}

this.setState({ isLoading: false });


}

handleConfirmationSubmit = async event => {


event.preventDefault();

this.setState({ isLoading: true });

try {
await Auth.confirmSignUp(this.state.email,
this.state.confirmationCode);
await Auth.signIn(this.state.email, this.state.password);

this.props.userHasAuthenticated(true);
this.props.history.push("/");
} catch (e) {
alert(e.message);
this.setState({ isLoading: false });
}
}

Also, include the Amplify Auth in our header.

import { Auth } from "aws-amplify";

The flow here is pre@y simple:

1. In handleSubmit we make a call to signup a user. This creates a new user object.

2. Save that user object to the state as newUser .

3. In handleConfirmationSubmit use the confirmaKon code to confirm the user.

4. With the user now confirmed, Cognito now knows that we have a new user that can login
to our app.

5. Use the email and password to authenKcate exactly the same way we did in the login
page.

6. Update the App’s state using the userHasAuthenticated method.

7. Finally, redirect to the homepage.

Now if you were to switch over to your browser and try signing up for a new account it should
redirect you to the homepage aSer sign up successfully completes.
A quick note on the signup flow here. If the user refreshes their page at the confirm step, they
won’t be able to get back and confirm that account. It forces them to create a new account
instead. We are keeping things intenKonally simple but here are a couple of hints on how to fix
it.

1. Check for the UsernameExistsException in the handleSubmit method’s catch


block.

2. Use the Auth.resendSignUp() method to resend the code if the user has not been
previously confirmed. Here is a link to the Amplify API docs (h@ps://aws.github.io/aws-
amplify/api/classes/authclass.html#resendsignup).

3. Confirm the code just as we did before.

Give this a try and post in the comments if you have any quesKons.

Now while developing you might run into cases where you need to manually confirm an
unauthenKcated user. You can do that with the AWS CLI using the following command.

aws cognito-idp admin-confirm-sign-up \


--region YOUR_COGNITO_REGION \
--user-pool-id YOUR_COGNITO_USER_POOL_ID \
--username YOUR_USER_EMAIL

Just be sure to use your Cognito User Pool Id and the email you used to create the account.

If you would like to allow your users to change their email or password, you can refer to our
Extra Credit series of chapters on user management (/chapters/manage-user-accounts-in-
aws-amplify.html).

Next up, we are going to create our first note.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/signup-with-
aws-cognito/130)
Add the Create Note Page
Now that we can signup users and also log them in. Let’s get started with the most important
part of our note taking app; the crea:on of a note.

First we are going to create the form for a note. It’ll take some content and a file as an
a>achment.

Add the Container


Create a new file src/containers/NewNote.js and add the following.

import React, { Component } from "react";


import { FormGroup, FormControl, ControlLabel } from "react-
bootstrap";
import LoaderButton from "../components/LoaderButton";
import config from "../config";
import "./NewNote.css";

export default class NewNote extends Component {


constructor(props) {
super(props);

this.file = null;

this.state = {
isLoading: null,
content: ""
};
}

validateForm() {
return this.state.content.length > 0;
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}

handleFileChange = event => {


this.file = event.target.files[0];
}

handleSubmit = async event => {


event.preventDefault();

if (this.file && this.file.size > config.MAX_ATTACHMENT_SIZE) {


alert(`Please pick a file smaller than
${config.MAX_ATTACHMENT_SIZE/1000000} MB.`);
return;
}

this.setState({ isLoading: true });


}

render() {
return (
<div className="NewNote">
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="content">
<FormControl
onChange={this.handleChange}
value={this.state.content}
componentClass="textarea"
/>
</FormGroup>
<FormGroup controlId="file">
<ControlLabel>Attachment</ControlLabel>
<FormControl onChange={this.handleFileChange} type="file"
/>
</FormGroup>
<LoaderButton
block
bsStyle="primary"
bsSize="large"
disabled={!this.validateForm()}
type="submit"
isLoading={this.state.isLoading}
text="Create"
loadingText="Creating…"
/>
</form>
</div>
);
}
}

Everything is fairly standard here, except for the file input. Our form elements so far have
been controlled components (h>ps://facebook.github.io/react/docs/forms.html), as in their
value is directly controlled by the state of the component. The file input simply calls a different
onChange handler ( handleFileChange ) that saves the file object as a class property. We
use a class property instead of saving it in the state because the file object we save does not
change or drive the rendering of our component.

Currently, our handleSubmit does not do a whole lot other than limi:ng the file size of our
a>achment. We are going to define this in our config.

So add the following to our src/config.js below the export default {


line.

MAX_ATTACHMENT_SIZE: 5000000,

Let’s also add the styles for our form in src/containers/NewNote.css .

.NewNote form {
padding-bottom: 15px;
}
.NewNote form textarea {
height: 300px;
font-size: 24px;
}

Add the Route


Finally, add our container as a route in src/Routes.js below our signup route.
We are using the AppliedRoute component that we created in the Add the session to the
state (/chapters/add-the-session-to-the-state.html) chapter.

<AppliedRoute path="/notes/new" exact component={NewNote} props=


{childProps} />

And include our component in the header.

import NewNote from "./containers/NewNote";

Now if we switch to our browser and navigate http://localhost:3000/notes/new we


should see our newly created form. Try adding some content, uploading a file, and hiSng
submit to see it in ac:on.
Next, let’s get into connec:ng this form to our API.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-the-
create-note-page/107)
Call the Create API
Now that we have our basic create note form working, let’s connect it to our API. We’ll do the
upload to S3 a liAle bit later. Our APIs are secured using AWS IAM and Cognito User Pool is
our authenFcaFon provider. Thankfully, Amplify takes care of this for us by using the logged in
user’s session.

We just need to use the API module that AWS Amplify has.

Let’s include the API module by adding the following to the header of
src/containers/NewNote.js .

import { API } from "aws-amplify";

And replace our handleSubmit funcFon with the following.

handleSubmit = async event => {


event.preventDefault();

if (this.file && this.file.size > config.MAX_ATTACHMENT_SIZE) {


alert(`Please pick a file smaller than
${config.MAX_ATTACHMENT_SIZE/1000000} MB.`);
return;
}

this.setState({ isLoading: true });

try {
await this.createNote({
content: this.state.content
});
this.props.history.push("/");
} catch (e) {
alert(e);
this.setState({ isLoading: false });
}
}

createNote(note) {
return API.post("notes", "/notes", {
body: note
});
}

This does a couple of simple things.

1. We make our create call in createNote by making a POST request to /notes and
passing in our note object. NoFce that the first two arguments to the API.post()
method are notes and /notes . This is because back in the Configure AWS Amplify
(/chapters/configure-aws-amplify.html) chapter we called these set of APIs by the name
notes .

2. For now the note object is simply the content of the note. We are creaFng these notes
without an aAachment for now.

3. Finally, aTer the note is created we redirect to our homepage.

And that’s it; if you switch over to your browser and try submiVng your form, it should
successfully navigate over to our homepage.
Next let’s upload our file to S3 and add an aAachment to our note.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/call-the-
create-api/124)
Upload a File to S3
Let’s now add an a,achment to our note. The flow we are using here is very simple.

1. The user selects a file to upload.


2. The file is uploaded to S3 under the user’s folder and we get a key back.
3. Create a note with the file key as the a,achment.

We are going to use the Storage module that AWS Amplify has. If you recall, that back in the
Create a Cognito idenHty pool (/chapters/create-a-cognito-idenHty-pool.html) chapter we
allow a logged in user access to a folder inside our S3 Bucket. AWS Amplify stores directly to
this folder if we want to privately store a file.

Also, just looking ahead a bit; we will be uploading files when a note is created and when a
note is edited. So let’s create a simple convenience method to help with that.

Upload to S3
Create a src/libs/ directory for this.

$ mkdir src/libs/

Add the following to src/libs/awsLib.js .

import { Storage } from "aws-amplify";

export async function s3Upload(file) {


const filename = `${Date.now()}-${file.name}`;

const stored = await Storage.vault.put(filename, file, {


contentType: file.type
});

return stored.key;
}

The above method does a couple of things.

1. It takes a file object as a parameter.

2. Generates a unique file name using the current Hmestamp ( Date.now() ). Of course, if
your app is being used heavily this might not be the best way to create a unique filename.
But this should be fine for now.

3. Upload the file to the user’s folder in S3 using the Storage.vault.put() object.
AlternaHvely, if we were uploading publicly you can use the Storage.put() method.

4. And return the stored object’s key.

Upload Before Creating a Note


Now that we have our upload methods ready, let’s call them from the create note method.

Replace the handleSubmit method in src/containers/NewNote.js with the


following.

handleSubmit = async event => {


event.preventDefault();

if (this.file && this.file.size > config.MAX_ATTACHMENT_SIZE) {


alert(`Please pick a file smaller than
${config.MAX_ATTACHMENT_SIZE/1000000} MB.`);
return;
}

this.setState({ isLoading: true });

try {
const attachment = this.file
? await s3Upload(this.file)
: null;

await this.createNote({
attachment,
content: this.state.content
});
this.props.history.push("/");
} catch (e) {
alert(e);
this.setState({ isLoading: false });
}
}

And make sure to include s3Upload by adding the following to the header of
src/containers/NewNote.js .

import { s3Upload } from "../libs/awsLib";

The change we’ve made in the handleSubmit is that:

1. We upload the file using the s3Upload method.

2. Use the returned key and add that to the note object when we create the note.

Now when we switch over to our browser and submit the form with an uploaded file we
should see the note being created successfully. And the app being redirected to the
homepage.

Next up we are going to allow users to see a list of the notes they’ve created.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
upload-a-file-to-s3/123)
List All the Notes
Now that we are able to create a new note, let’s create a page where we can see a list of all
the notes a user has created. It makes sense that this would be the homepage (even though
we use the / route for the landing page). So we just need to condi@onally render the landing
page or the homepage depending on the user session.

Currently, our Home container is very simple. Let’s add the condi@onal rendering in there.

Replace our src/containers/Home.js with the following.

import React, { Component } from "react";


import { PageHeader, ListGroup } from "react-bootstrap";
import "./Home.css";

export default class Home extends Component {


constructor(props) {
super(props);

this.state = {
isLoading: true,
notes: []
};
}

renderNotesList(notes) {
return null;
}

renderLander() {
return (
<div className="lander">
<h1>Scratch</h1>
<p>A simple note taking app</p>
</div>
);
}

renderNotes() {
return (
<div className="notes">
<PageHeader>Your Notes</PageHeader>
<ListGroup>
{!this.state.isLoading &&
this.renderNotesList(this.state.notes)}
</ListGroup>
</div>
);
}

render() {
return (
<div className="Home">
{this.props.isAuthenticated ? this.renderNotes() :
this.renderLander()}
</div>
);
}
}

We are doing a few things of note here:

1. Rendering the lander or the list of notes based on this.props.isAuthenticated .

2. Store our notes in the state. Currently, it’s empty but we’ll be calling our API for it.

3. Once we fetch our list we’ll use the renderNotesList method to render the items in
the list.

And that’s our basic setup! Head over to the browser and the homepage of our app should
render out an empty list.
Next we are going to fill it up with our API.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/list-all-the-
notes/156)
Call the List API
Now that we have our basic homepage set up, let’s make the API call to render our list of
notes.

Make the Request


Add the following below the constructor block in
src/containers/Home.js .

async componentDidMount() {
if (!this.props.isAuthenticated) {
return;
}

try {
const notes = await this.notes();
this.setState({ notes });
} catch (e) {
alert(e);
}

this.setState({ isLoading: false });


}

notes() {
return API.get("notes", "/notes");
}

And include our Amplify API module in the header.

import { API } from "aws-amplify";


All this does, is make a GET request to /notes on componentDidMount and puts the
results in the notes object in the state.

Now let’s render the results.

Render the List


Replace our renderNotesList placeholder method with the following.

renderNotesList(notes) {
return [{}].concat(notes).map(
(note, i) =>
i !== 0
? <LinkContainer
key={note.noteId}
to={`/notes/${note.noteId}`}
>
<ListGroupItem header={note.content.trim().split("\n")
[0]}>
{"Created: " + new
Date(note.createdAt).toLocaleString()}
</ListGroupItem>
</LinkContainer>
: <LinkContainer
key="new"
to="/notes/new"
>
<ListGroupItem>
<h4>
<b>{"\uFF0B"}</b> Create a new note
</h4>
</ListGroupItem>
</LinkContainer>
);
}

And include the ListGroupItem in the header so that our react-bootstrap


import looks like so.
import { PageHeader, ListGroup, ListGroupItem } from "react-
bootstrap";

Also include the LinkContainer from react-router-bootstrap .

import { LinkContainer } from "react-router-bootstrap";

The code above does a few things.

1. It always renders a Create a new note buFon as the first item in the list (even if the list is
empty). We do this by concatenaKng an array with an empty object with our notes
array.

2. We render the first line of each note as the ListGroupItem header by doing
note.content.trim().split('\n')[0] .

3. And the LinkContainer component directs our app to each of the items.

Let’s also add a couple of styles to our src/containers/Home.css .

.Home .notes h4 {
font-family: "Open Sans", sans-serif;
font-weight: 600;
overflow: hidden;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
}
.Home .notes p {
color: #666;
}

Now head over to your browser and you should see your list displayed.
And if you click on the links they should take you to their respecKve pages.

Next up we are going to allow users to view and edit their notes.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/call-the-list-
api/127)
Display a Note
Now that we have a lis-ng of all the notes, let’s create a page that displays a note and let’s the
user edit it.

The first thing we are going to need to do is load the note when our container loads. Just like
what we did in the Home container. So let’s get started.

Add the Route


Let’s add a route for the note page that we are going to create.

Add the following line to src/Routes.js below our /notes/new route. We


are using the AppliedRoute component that we created in the Add the session to the state
(/chapters/add-the-session-to-the-state.html) chapter.

<AppliedRoute path="/notes/:id" exact component={Notes} props=


{childProps} />

This is important because we are going to be paHern matching to extract our note id from the
URL.

By using the route path /notes/:id we are telling the router to send all matching routes to
our component Notes . This will also end up matching the route /notes/new with an id
of new . To ensure that doesn’t happen, we put our /notes/new route before the paHern
matching one.

And include our component in the header.

import Notes from "./containers/Notes";

Of course this component doesn’t exist yet and we are going to create it now.

Add the Container


Create a new file src/containers/Notes.js and add the following.

import React, { Component } from "react";


import { API, Storage } from "aws-amplify";

export default class Notes extends Component {


constructor(props) {
super(props);

this.file = null;

this.state = {
note: null,
content: "",
attachmentURL: null
};
}

async componentDidMount() {
try {
let attachmentURL;
const note = await this.getNote();
const { content, attachment } = note;

if (attachment) {
attachmentURL = await Storage.vault.get(attachment);
}

this.setState({
note,
content,
attachmentURL
});
} catch (e) {
alert(e);
}
}
getNote() {
return API.get("notes", `/notes/${this.props.match.params.id}`);
}

render() {
return <div className="Notes"></div>;
}
}

We are doing a couple of things here.

1. Load the note on componentDidMount and save it to the state. We get the id of our
note from the URL using the props automa-cally passed to us by React-Router in
this.props.match.params.id . The keyword id is a part of the paHern matching in
our route ( /notes/:id ).

2. If there is an aHachment, we use the key to get a secure link to the file we uploaded to S3.
We then store this to the component’s state as attachmentURL .

3. The reason why we have the note object in the state along with the content and the
attachmentURL is because we will be using this later when the user edits the note.

Now if you switch over to your browser and navigate to a note that we previously created,
you’ll no-ce that the page renders an empty container.
Next up, we are going to render the note we just loaded.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/display-a-
note/112)
Render the Note Form
Now that our container loads a note on componentDidMount , let’s go ahead and render the
form that we’ll use to edit it.

Replace our placeholder render method in src/containers/Notes.js with


the following.

validateForm() {
return this.state.content.length > 0;
}

formatFilename(str) {
return str.replace(/^\w+-/, "");
}

handleChange = event => {


this.setState({
[event.target.id]: event.target.value
});
}

handleFileChange = event => {


this.file = event.target.files[0];
}

handleSubmit = async event => {


event.preventDefault();

if (this.file && this.file.size > config.MAX_ATTACHMENT_SIZE) {


alert(`Please pick a file smaller than
${config.MAX_ATTACHMENT_SIZE/1000000} MB.`);
return;
}
this.setState({ isLoading: true });
}

handleDelete = async event => {


event.preventDefault();

const confirmed = window.confirm(


"Are you sure you want to delete this note?"
);

if (!confirmed) {
return;
}

this.setState({ isDeleting: true });


}

render() {
return (
<div className="Notes">
{this.state.note &&
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="content">
<FormControl
onChange={this.handleChange}
value={this.state.content}
componentClass="textarea"
/>
</FormGroup>
{this.state.note.attachment &&
<FormGroup>
<ControlLabel>Attachment</ControlLabel>
<FormControl.Static>
<a
target="_blank"
rel="noopener noreferrer"
href={this.state.attachmentURL}
>
{this.formatFilename(this.state.note.attachment)}
</a>
</FormControl.Static>
</FormGroup>}
<FormGroup controlId="file">
{!this.state.note.attachment &&
<ControlLabel>Attachment</ControlLabel>}
<FormControl onChange={this.handleFileChange} type="file"
/>
</FormGroup>
<LoaderButton
block
bsStyle="primary"
bsSize="large"
disabled={!this.validateForm()}
type="submit"
isLoading={this.state.isLoading}
text="Save"
loadingText="Saving…"
/>
<LoaderButton
block
bsStyle="danger"
bsSize="large"
isLoading={this.state.isDeleting}
onClick={this.handleDelete}
text="Delete"
loadingText="Deleting…"
/>
</form>}
</div>
);
}

We are doing a few things here:

1. We render our form only when this.state.note is available.


2. Inside the form we condiAonally render the part where we display the aBachment by
using this.state.note.attachment .

3. We format the aBachment URL using formatFilename by stripping the Amestamp we


had added to the filename while uploading it.

4. We also added a delete buBon to allow users to delete the note. And just like the submit
buBon it too needs a flag that signals that the call is in progress. We call it isDeleting .

5. We handle aBachments with a file input exactly like we did in the NewNote component.

6. Our delete buBon also confirms with the user if they want to delete the note using the
browser’s confirm dialog.

To complete this code, let’s add isLoading and isDeleting to the state.

So our new iniAal state in the constructor looks like so.

this.state = {
isLoading: null,
isDeleting: null,
note: null,
content: "",
attachmentURL: null
};

Let’s also add some styles by adding the following to


src/containers/Notes.css .

.Notes form {
padding-bottom: 15px;
}

.Notes form textarea {


height: 300px;
font-size: 24px;
}

Also, let’s include the React-Bootstrap components that we are using here by
adding the following to our header. And our styles, the LoaderButton , and the config .

import { FormGroup, FormControl, ControlLabel } from "react-


bootstrap";
import LoaderButton from "../components/LoaderButton";
import config from "../config";
import "./Notes.css";

And that’s it. If you switch over to your browser, you should see the note loaded.

Next, we’ll look at saving the changes we make to our note.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/render-the-
note-form/140)
Save Changes to a Note
Now that our note loads into our form, let’s work on saving the changes we make to that note.

Replace the handleSubmit method in src/containers/Notes.js with the


following.

saveNote(note) {
return API.put("notes", `/notes/${this.props.match.params.id}`, {
body: note
});
}

handleSubmit = async event => {


let attachment;

event.preventDefault();

if (this.file && this.file.size > config.MAX_ATTACHMENT_SIZE) {


alert(`Please pick a file smaller than
${config.MAX_ATTACHMENT_SIZE/1000000} MB.`);
return;
}

this.setState({ isLoading: true });

try {
if (this.file) {
attachment = await s3Upload(this.file);
}

await this.saveNote({
content: this.state.content,
attachment: attachment || this.state.note.attachment
});
this.props.history.push("/");
} catch (e) {
alert(e);
this.setState({ isLoading: false });
}
}

And include our s3Upload helper method in the header:

import { s3Upload } from "../libs/awsLib";

The code above is doing a couple of things that should be very similar to what we did in the
NewNote container.

1. If there is a file to upload we call s3Upload to upload it and save the key we get from
S3.

2. We save the note by making a PUT request with the note object to /notes/:id where
we get the id from this.props.match.params.id . We use the API.put()
method from AWS Amplify.

3. And on success we redirect the user to the homepage.

Let’s switch over to our browser and give it a try by saving some changes.
You might have noKced that we are not deleKng the old aLachment when we upload a new
one. To keep things simple, we are leaving that bit of detail up to you. It should be preLy
straighMorward. Check the AWS Amplify API Docs (hLps://aws.github.io/aws-
amplify/api/classes/storageclass.html#remove) on how to a delete file from S3.

Next up, let’s allow users to delete their note.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/save-changes-
to-a-note/131)
Delete a Note
The last thing we need to do on the note page is allowing users to delete their note. We have
the bu6on all set up already. All that needs to be done is to hook it up with the API.

Replace our handleDelete method in src/containers/Notes.js .

deleteNote() {
return API.del("notes", `/notes/${this.props.match.params.id}`);
}

handleDelete = async event => {


event.preventDefault();

const confirmed = window.confirm(


"Are you sure you want to delete this note?"
);

if (!confirmed) {
return;
}

this.setState({ isDeleting: true });

try {
await this.deleteNote();
this.props.history.push("/");
} catch (e) {
alert(e);
this.setState({ isDeleting: false });
}
}

We are simply making a DELETE request to /notes/:id where we get the id from
this.props.match.params.id . We use the API.del method from AWS Amplify to do
so. This calls our delete API and we redirect to the homepage on success.

Now if you switch over to your browser and try deleCng a note you should see it confirm your
acCon and then delete the note.

Again, you might have noCced that we are not deleCng the a6achment when we are deleCng
a note. We are leaving that up to you to keep things simple. Check the AWS Amplify API Docs
(h6ps://aws.github.io/aws-amplify/api/classes/storageclass.html#remove) on how to a delete
file from S3.

Now with our app nearly complete, we’ll look at securing some the pages of our app that
require a login. Currently if you visit a note page while you are logged out, it throws an ugly
error.
Instead, we would like it to redirect us to the login page and then redirect us back aPer we
login. Let’s look at how to do that next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
delete-a-note/137)
Set up Secure Pages
We are almost done pu/ng together our app. All the pages are done but there are a few
pages that should not be accessible if a user is not logged in. For example, a page with the
note should not load if a user is not logged in. Currently, we get an error when we do this. This
is because the page loads and since there is no user in the session, the call to our API fails.

We also have a couple of pages that need to behave in sort of the same way. We want the
user to be redirected to the homepage if they type in the login ( /login ) or signup
( /signup ) URL. Currently, the login and sign up page end up loading even though the user is
already logged in.

There are many ways to solve the above problems. The simplest would be to just check the
condiIons in our containers and redirect. But since we have a few containers that need the
same logic we can create a special route (like the AppliedRoute from the Add the session
to the state (/chapters/add-the-session-to-the-state.html) chapter) for it.

We are going to create two different route components to fix the problem we have.

1. A route called the AuthenIcatedRoute, that checks if the user is authenIcated before
rouIng.

2. And a component called the UnauthenIcatedRoute, that ensures the user is not
authenIcated.

Let’s create these components next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/set-up-secure-
pages/42)
Create a Route That Redirects
Let’s first create a route that will check if the user is logged in before rou7ng.

Add the following to src/components/AuthenticatedRoute.js .

import React from "react";


import { Route, Redirect } from "react-router-dom";

export default ({ component: C, props: cProps, ...rest }) =>


<Route
{...rest}
render={props =>
cProps.isAuthenticated
? <C {...props} {...cProps} />
: <Redirect
to={`/login?
redirect=${props.location.pathname}${props.location
.search}`}
/>}
/>;

This component is similar to the AppliedRoute component that we created in the Add the
session to the state (/chapters/add-the-session-to-the-state.html) chapter. The main
difference being that we look at the props that are passed in to check if a user is
authen7cated. If the user is authen7cated, then we simply render the passed in component.
And if the user is not authen7cated, then we use the Redirect React Router v4 component
to redirect the user to the login page. We also pass in the current path to the login page
( redirect in the querystring). We will use this later to redirect us back aJer the user logs in.

We’ll do something similar to ensure that the user is not authen7cated.

Add the following to src/components/UnauthenticatedRoute.js .


import React from "react";
import { Route, Redirect } from "react-router-dom";

export default ({ component: C, props: cProps, ...rest }) =>


<Route
{...rest}
render={props =>
!cProps.isAuthenticated
? <C {...props} {...cProps} />
: <Redirect to="/" />}
/>;

Here we are checking to ensure that the user is not authen7cated before we render the
component that is passed in. And in the case where the user is authen7cated, we use the
Redirect component to simply send the user to the homepage.

Next, let’s use these components in our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
route-that-redirects/47)
Use the Redirect Routes
Now that we created the AuthenticatedRoute and UnauthenticatedRoute in the last
chapter, let’s use them on the containers we want to secure.

First import them in the header of src/Routes.js .

import AuthenticatedRoute from "./components/AuthenticatedRoute";


import UnauthenticatedRoute from "./components/UnauthenticatedRoute";

Next, we simply switch to our new redirect routes.

So the following routes in src/Routes.js would be affected.

<AppliedRoute path="/login" exact component={Login} props={childProps}


/>
<AppliedRoute path="/signup" exact component={Signup} props=
{childProps} />
<AppliedRoute path="/notes/new" exact component={NewNote} props=
{childProps} />
<AppliedRoute path="/notes/:id" exact component={Notes} props=
{childProps} />

They should now look like so:

<UnauthenticatedRoute path="/login" exact component={Login} props=


{childProps} />
<UnauthenticatedRoute path="/signup" exact component={Signup} props=
{childProps} />
<AuthenticatedRoute path="/notes/new" exact component={NewNote} props=
{childProps} />
<AuthenticatedRoute path="/notes/:id" exact component={Notes} props=
{childProps} />
And now if we tried to load a note page while not logged in, we would be redirected to the
login page with a reference to the note page.

Next, we are going to use the reference to redirect to the note page aBer we login.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/use-the-
redirect-routes/152)
Redirect on Login
Our secured pages redirect to the login page when the user is not logged in, with a referral to
the origina5ng page. To redirect back a:er they login, we need to do a couple of more things.
Currently, our Login component does the redirec5ng a:er the user logs in. We are going to
move this to the newly created UnauthenticatedRoute component.

Let’s start by adding a method to read the redirect URL from the querystring.

Add the following method to your


src/components/UnauthenticatedRoute.js below the imports.

function querystring(name, url = window.location.href) {


name = name.replace(/[[]]/g, "\\$&");

const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)", "i");


const results = regex.exec(url);

if (!results) {
return null;
}
if (!results[2]) {
return "";
}

return decodeURIComponent(results[2].replace(/\+/g, " "));


}

This method takes the querystring param we want to read and returns it.

Now let’s update our component to use this parameter when it redirects.

Replace our current export default ({ component: C, props: cProps,


...rest }) => { method with the following.
export default ({ component: C, props: cProps, ...rest }) => {
const redirect = querystring("redirect");
return (
<Route
{...rest}
render={props =>
!cProps.isAuthenticated
? <C {...props} {...cProps} />
: <Redirect
to={redirect === "" || redirect === null ? "/" :
redirect}
/>}
/>
);
};

And remove the following from the handleSubmit method in


src/containers/Login.js .

this.props.history.push("/");

Now our login page should redirect a:er we login.

And that’s it! Our app is ready to go live. Let’s look at how we are going to deploy it using our
serverless setup.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/redirect-on-
login/24)
Deploy the Frontend
Now that we have our setup working in our local environment, let’s do our first deploy and
look into what we need to do to host our serverless applica:on.

The basic setup we are going to be using will look something like this:

1. Upload the assets of our app


2. Use a CDN to serve out our assets
3. Point our domain to the CDN distribu:on
4. Switch to HTTPS with a SSL cer:ficate

AWS provides quite a few services that can help us do the above. We are going to use S3
(hOps://aws.amazon.com/s3/) to host our assets, CloudFront
(hOps://aws.amazon.com/cloudfront/) to serve it, Route 53
(hOps://aws.amazon.com/route53/) to manage our domain, and Cer:ficate Manager
(hOps://aws.amazon.com/cer:ficate-manager/) to handle our SSL cer:ficate.

So let’s get started by first configuring our S3 bucket to upload the assets of our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploy-the-
frontend/39)
Create an S3 Bucket
To be able to host our note taking app, we need to upload the assets that are going to be
served out sta6cally on S3. S3 has a concept of buckets (or folders) to separate different types
of files.

A bucket can also be configured to host the assets in it as a sta6c website and is automa6cally
assigned a publicly accessible URL. So let’s get started.

Create the Bucket


First, log in to your AWS Console (hJps://console.aws.amazon.com) and select S3 from the list
of services.

Select Create Bucket and pick a name for your applica6on and select the US East (N. Virginia)
Region Region. Since our applica6on is being served out using a CDN, the region should not
maJer to us.

Click Next through the configure op6ons step.


In the permissions step, make sure to uncheck Block new public bucket policies and Block
public and cross-account access if bucket has public policies. Making buckets public is a
common security error, but in our case we’ll be serving our app from the bucket, so want it to
be public.
Click Create bucket on the review page to create the bucket.
Now click on your newly created bucket from the list and navigate to its permissions panel by
clicking Permissions.

Add Permissions
Buckets by default are not publicly accessible, so we need to change the S3 Bucket
Permission. Select the Bucket Policy from the permissions panel.
Add the following bucket policy into the editor. Where notes-app-client is the
name of our S3 bucket. Make sure to use the name of your bucket here.

{
"Version":"2012-10-17",
"Statement":[{
"Sid":"PublicReadForGetBucketObjects",
"Effect":"Allow",
"Principal": "*",
"Action":["s3:GetObject"],
"Resource":["arn:aws:s3:::notes-app-client/*"]
}
]
}
And hit Save.

Enable Static Web Hosting


And finally we need to turn our bucket into a sta6c website. Select the ProperFes tab from
the top panel.
Select StaFc website hosFng.
Now select Use this bucket to host a website and add our index.html as the Index
Document and the Error Document. Since we are leTng React handle 404s, we can simply
redirect our errors to our index.html as well. Hit Save once you are done.

This panel also shows us where our app will be accessible. AWS assigns us a URL for our sta6c
website. In this case the URL assigned to me is notes-app-client.s3-website-us-east-
1.amazonaws.com .

Now that our bucket is all set up and ready, let’s go ahead and upload our assets to it.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-an-s3-
bucket/48)
Deploy to S3
Now that our S3 Bucket is created we are ready to upload the assets of our app.

Build Our App


Create React App comes with a convenient way to package and prepare our app for
deployment. From our working directory simply run the following command.

$ npm run build

This packages all of our assets and places them in the build/ directory.

Upload to S3
Now to deploy simply run the following command; where YOUR_S3_DEPLOY_BUCKET_NAME
is the name of the S3 Bucket we created in the Create an S3 bucket (/chapters/create-an-s3-
bucket.html) chapter.

$ aws s3 sync build/ s3://YOUR_S3_DEPLOY_BUCKET_NAME

All this command does is that it syncs the build/ directory with our bucket on S3. Just as a
sanity check, go into the S3 secIon in your AWS Console
(hKps://console.aws.amazon.com/console/home) and check if your bucket has the files we just
uploaded.
And our app should be live on S3! If you head over to the URL assigned to you (in my case it is
hKp://notes-app-client.s3-website-us-east-1.amazonaws.com (hKp://notes-app-client.s3-
website-us-east-1.amazonaws.com)), you should see it live.
Next we’ll configure CloudFront to serve our app out globally.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploy-to-
s3/134)
Create a CloudFront Distribution
Now that we have our app up and running on S3, let’s serve it out globally through
CloudFront. To do this we need to create an AWS CloudFront DistribuAon.

Select CloudFront from the list of services in your AWS Console


(hEps://console.aws.amazon.com).

Then select Create Distribu,on.


And then in the Web secAon select Get Started.
In the Create DistribuAon form we need to start by specifying the Origin Domain Name for
our Web CloudFront DistribuAon. This field is pre-filled with a few opAons including the S3
bucket we created. But we are not going to select on the opAons in the dropdown. This is
because the opAons here are the REST API endpoints for the S3 bucket instead of the one
that is set up as a staAc website.

You can grab the S3 website endpoint from the Sta,c website hos,ng panel for your S3
bucket. We had configured this in the previous chapter. Copy the URL in the Endpoint field.

And paste that URL in the Origin Domain Name field. In my case it is, http://notes-app-
client.s3-website-us-east-1.amazonaws.com .
And now scroll down the form and switch Compress Objects Automa,cally to Yes. This will
automaAcally Gzip compress the files that can be compressed and speed up the delivery of
our app.
Next, scroll down a bit further to set the Default Root Object to index.html .
And finally, hit Create Distribu,on.

It takes AWS a liEle while to create a distribuAon. But once it is complete you can find your
CloudFront DistribuAon by clicking on your newly created distribuAon from the list and
looking up its domain name.
And if you navigate over to that in your browser, you should see your app live.
Now before we move on there is one last thing we need to do. Currently, our staAc website
returns our index.html as the error page. We set this up back in the chapter where we
created our S3 bucket. However, it returns a HTTP status code of 404 when it does so. We
want to return the index.html but since the rouAng is handled by React Router; it does not
make sense that we return the 404 HTTP status code. One of the issues with this is that
certain corporate firewalls and proxies tend to block 4xx and 5xx responses.

Custom Error Responses


So we are going to create a custom error response and return a 200 status code instead. The
downside of this approach is that we are going to be returning 200 even for cases where we
don’t have a route in our React Router. Unfortunately, there isn’t a way around this. This is
because CloudFront or S3 are not aware of the routes in our React Router.

To set up a custom error response, head over to the Error Pages tab in our DistribuAon.

And select Create Custom Error Response.


Pick 404 for the HTTP Error Code and select Customize Error Response. Enter
/index.html for the Response Page Path and 200 for the HTTP Response Code.
And hit Create. This is basically telling CloudFront to respond to any 404 responses from our
S3 bucket with the index.html and a 200 status code. CreaAng a custom error response
should take a couple of minutes to complete.

Next up, let’s point our domain to our CloudFront DistribuAon.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
cloudfront-distribution/104)
Set up Your Domain with CloudFront
Now that we have our CloudFront distribu4on live, let’s set up our domain with it. You can
purchase a domain right from the AWS Console (hBps://console.aws.amazon.com) by heading
to the Route 53 sec4on in the list of services.

Purchase a Domain with Route 53


Type in your domain in the Register domain sec4on and click Check.
AMer checking its availability, click Add to cart.
And hit Con3nue at the boBom of the page.

Fill in your contact details and hit Con3nue once again.


Finally, review your details and confirm the purchase by hiOng Complete Purchase.
Next, we’ll add an alternate domain name for our CloudFront Distribu4on.

Add Alternate Domain for CloudFront Distribution


Head over to the details of your CloudFront Distribu4on and hit Edit.

And type in your new domain name in the Alternate Domain Names (CNAMEs) field.
Scroll down and hit Yes, Edit to save the changes.
Next, let’s point our domain to the CloudFront Distribu4on.

Point Domain to CloudFront Distribution


Head back into Route 53 and hit the Hosted Zones buBon. If you don’t have an exis4ng
Hosted Zone, you’ll need to create one by adding the Domain Name and selec4ng Public
Hosted Zone as the Type.

Select your domain from the list and hit Create Record Set in the details screen.
Leave the Name field empty since we are going to point our bare domain (without the www.)
to our CloudFront Distribu4on.
And select Alias as Yes since we are going to simply point this to our CloudFront domain.
In the Alias Target dropdown, select your CloudFront Distribu4on.

Finally, hit Create to add your new record set.


Add IPv6 Support
CloudFront Distribu4ons have IPv6 enabled by default and this means that we need to create
an AAAA record as well. It is set up exactly the same way as the Alias record.

Create a new Record Set with the exact seOngs as before, except make sure to pick AAAA -
IPv6 address as the Type.
And hit Create to add your AAAA record set.

It can take around an hour to update the DNS records but once it’s done, you should be able
to access your app through your domain.
Next up, we’ll take a quick look at ensuring that our www. domain also directs to our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/set-up-your-
domain-with-cloudfront/149)
Set up WWW Domain Redirect
There’s plenty of debate over the www vs non-www domains and while both sides have merit;
we’ll go over how to set up another domain (in this case the www) and redirect it to our
original. The reason we do a redirect is to tell the search engines that we only want one
version of our domain to appear in the search results. If you prefer having the www domain as
the default simply swap this step with the last one where we created a bare domain (non-
www).

To create a www version of our domain and have it redirect we are going to create a new S3
Bucket and a new CloudFront DistribuFon. This new S3 Bucket will simply respond with a
redirect to our main domain using the redirecFon feature that S3 Buckets have.

So let’s start by creaFng a new S3 redirect Bucket for this.

Create S3 Redirect Bucket


Create a new S3 Bucket through the AWS Console (hIps://console.aws.amazon.com). The
name doesn’t really maIer but it pick something that helps us disFnguish between the two.
Again, remember that we need a separate S3 Bucket for this step and we cannot use the
original one we had previously created.
Next just follow through the steps and leave the defaults intact.
Now go into the Proper0es of the new bucket and click on the Sta0c website hos0ng.

But unlike last Fme we are going to select the Redirect requests opFon and fill in the domain
we are going to be redirecFng towards. This is the domain that we set up in our last chapter.

Also, make sure to copy the Endpoint as we’ll be needing this later.
And hit Save to make the changes. Next we’ll create a CloudFront DistribuFon to point to this
S3 redirect Bucket.

Create a CloudFront Distribution


Create a new CloudFront Distribu0on. And copy the S3 Endpoint from the step above as the
Origin Domain Name. Make sure to not use the one from the dropdown. In my case, it is
http://www-notes-app-client.s3-website-us-east-1.amazonaws.com .
Scroll down to the Alternate Domain Names (CNAMEs) and use the www version of our
domain name here.
And hit Create Distribu0on.
Finally, we’ll point our www domain to this CloudFront DistribuFon.

Point WWW Domain to CloudFront Distribution


Head over to your domain in Route 53 and hit Create Record Set.

This Fme fill in www as the Name and select Alias as Yes. And pick your new CloudFront
DistribuFon from the Alias Target dropdown.
Add IPv6 Support
Just as before, we need to add an AAAA record to support IPv6.

Create a new Record Set with the exact same seYngs as before, except make sure to pick
AAAA - IPv6 address as the Type.
And that’s it! Just give it some Fme for the DNS to propagate and if you visit your www
version of your domain, it should redirect you to your non-www version.

Next, we’ll set up SSL and add HTTPS support for our domains.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/set-up-www-
domain-redirect/142)
Set up SSL
Now that our app is being served through our domain, let’s add a layer of security to it by
switching to HTTPS. AWS makes this fairly easy to do, thanks to CerCficate Manager.

Request a Certificate
Select Cer$ficate Manager from the list of services in your AWS Console
(hGps://console.aws.amazon.com). Ensure that you are in the US East (N. Virginia) region. This
is because a cerCficate needs to be from this region for it to work with CloudFront
(hGp://docs.aws.amazon.com/acm/latest/userguide/acm-regions.html).

If this is your first cerCficate, you’ll need to hit Get started. If not then hit Request a cer$ficate
from the top.
And type in the name of our domain. Hit Add another name to this cer$ficate and add our
www version of our domain as well. Hit Review and request once you are done.
Now to confirm that we control the domain, select the DNS valida$on method and hit
Review.
On the validaCon screen expand the two domains we are trying to validate.
Since we control the domain through Route 53, we can directly create the DNS record
through here by hiVng Create record in Route 53.

And confirm that you want the record to be created by hiVng Create.
Also, make sure to do this for the other domain.

The process of creaCng a DNS record and validaCng it can take around 30 minutes.

Next, we’ll associate this cerCficate with our CloudFront DistribuCons.

Update CloudFront Distributions with Certificate


Open up our first CloudFront DistribuCon from our list of distribuCons and hit the Edit buGon.
Now switch the SSL Cer$ficate to Custom SSL Cer$ficate and select the cerCficate we just
created from the drop down. And scroll down to the boGom and hit Yes, Edit.
Next, head over to the Behaviors tab from the top.
And select the only one we have and hit Edit.

Then switch the Viewer Protocol Policy to Redirect HTTP to HTTPS. And scroll down to the
boGom and hit Yes, Edit.
Now let’s do the same for our other CloudFront DistribuCon.
But leave the Viewer Protocol Policy as HTTP and HTTPS. This is because we want our users
to go straight to the HTTPS version of our non-www domain. As opposed to redirecCng to the
HTTPS version of our www domain before redirecCng again.

Update S3 Redirect Bucket


The S3 Redirect Bucket that we created in the last chapter is redirecCng to the HTTP version
of our non-www domain. We should switch this to the HTTPS version to prevent the extra
redirect.

Open up the S3 Redirect Bucket we created in the last chapter. Head over to the Proper$es
tab and select Sta$c website hos$ng.
Change the Protocol to hPps and hit Save.
And that’s it. Our app should be served out on our domain through HTTPS.

Next up, let’s look at the process of deploying updates to our app.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
set-up-ssl/133)
Deploy Updates
Now let’s look at how we make changes and update our app. The process is very similar to
how we deployed our code to S3 but with a few changes. Here is what it looks like.

1. Build our app with the changes


2. Deploy to the main S3 Bucket
3. Invalidate the cache in both our CloudFront DistribuFons

We need to do the last step since CloudFront caches our objects in its edge locaFons. So to
make sure that our users see the latest version, we need to tell CloudFront to invalidate it’s
cache in the edge locaFons.

Let’s start by making a couple of changes to our app and go through the process of deploying
them.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploy-
updates/16)
Update the App
Let’s make a couple of quick changes to test the process of deploying updates to our app.

We are going to add a Login and Signup bu<on to our lander to give users a clear call to
ac>on.

To do this update our renderLander method in src/containers/Home.js .

renderLander() {
return (
<div className="lander">
<h1>Scratch</h1>
<p>A simple note taking app</p>
<div>
<Link to="/login" className="btn btn-info btn-lg">
Login
</Link>
<Link to="/signup" className="btn btn-success btn-lg">
Signup
</Link>
</div>
</div>
);
}

And import the Link component from React-Router in the header.

import { Link } from "react-router-dom";

Also, add a couple of styles to src/containers/Home.css .

.Home .lander div {


padding-top: 20px;
}
.Home .lander div a:first-child {
margin-right: 20px;
}

And our lander should look something like this.

Next, let’s deploy these updates.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
update-the-app/43)

For reference, here is the code for Part I

" Frontend Part I Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-client/tree/part-1)
Deploy Again
Now that we’ve made some changes to our app, let’s deploy the updates. This is the process
we are going to repeat every :me we need to deploy any updates.

Build Our App


First let’s prepare our app for produc:on by building it. Run the following in your working
directory.

$ npm run build

Now that our app is built and ready in the build/ directory, let’s deploy to S3.

Upload to S3
Run the following from our working directory to upload our app to our main S3 Bucket. Make
sure to replace YOUR_S3_DEPLOY_BUCKET_NAME with the S3 Bucket we created in the
Create an S3 bucket (/chapters/create-an-s3-bucket.html) chapter.

$ aws s3 sync build/ s3://YOUR_S3_DEPLOY_BUCKET_NAME --delete

Note the --delete flag here; this is telling S3 to delete all the files that are in the bucket
that we aren’t uploading this :me around. Create React App generates unique bundles when
we build it and without this flag we’ll end up retaining all the files from the previous builds.

Our changes should be live on S3.


Now to ensure that CloudFront is serving out the updated version of our app, let’s invalidate
the CloudFront cache.

Invalidate the CloudFront Cache


CloudFront allows you to invalidate objects in the distribu:on by passing in the path of the
object. But it also allows you to use a wildcard ( /* ) to invalidate the en:re distribu:on in a
single command. This is recommended when we are deploying a new version of our app.

To do this we’ll need the Distribu(on ID of both of our CloudFront Distribu:ons. You can get
it by clicking on the distribu:on from the list of CloudFront Distribu:ons.
Now we can use the AWS CLI to invalidate the cache of the two distribu:ons. Make sure to
replace YOUR_CF_DISTRIBUTION_ID and YOUR_WWW_CF_DISTRIBUTION_ID with the
ones from above.

$ aws cloudfront create-invalidation --distribution-id


YOUR_CF_DISTRIBUTION_ID --paths "/*"
$ aws cloudfront create-invalidation --distribution-id
YOUR_WWW_CF_DISTRIBUTION_ID --paths "/*"

This invalidates our distribu:on for both the www and non-www versions of our domain. If
you click on the Invalida(ons tab, you should see your invalida:on request being processed.
It can take a few minutes to complete. But once it is done, the updated version of our app
should be live.
And that’s it! We now have a set of commands we can run to deploy our updates. Let’s quickly
put them together so we can do it with one command.

Add a Deploy Command


NPM allows us to add a deploy command in our package.json .

Add the following in the scripts block above eject in the package.json .

"predeploy": "npm run build",


"deploy": "aws s3 sync build/ s3://YOUR_S3_DEPLOY_BUCKET_NAME --
delete",
"postdeploy": "aws cloudfront create-invalidation --distribution-id
YOUR_CF_DISTRIBUTION_ID --paths '/*' && aws cloudfront create-
invalidation --distribution-id YOUR_WWW_CF_DISTRIBUTION_ID --paths
'/*'",

Make sure to replace YOUR_S3_DEPLOY_BUCKET_NAME , YOUR_CF_DISTRIBUTION_ID , and


YOUR_WWW_CF_DISTRIBUTION_ID with the ones from above.
For Windows users, if postdeploy returns an error like.

An error occurred (InvalidArgument) when calling the


CreateInvalidation operation: Your request contains one or more
invalid invalidation paths.

Make sure that there is no quote in the /* .

"postdeploy": "aws cloudfront create-invalidation --distribution-id


YOUR_CF_DISTRIBUTION_ID --paths /* && aws cloudfront create-
invalidation --distribution-id YOUR_WWW_CF_DISTRIBUTION_ID --paths
/*",

Now simply run the following command from your project root when you want to deploy your
updates. It’ll build your app, upload it to S3, and invalidate the CloudFront cache.

$ npm run deploy

Our app is now complete. And this is the end of Part I. Next we’ll be looking at how to
automate this stack so we can use it for our future projects. You can also take a look at how to
add a Login with Facebook op:on in the Facebook Login with Cognito using AWS Amplify
(/chapters/facebook-login-with-cognito-using-aws-amplify.html) chapter. It builds on what we
have covered in Part I so far.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploy-
again/138)

For reference, here is the code for Part I

" Frontend Part I Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-client/tree/part-1)
Getting Production Ready
Now that we’ve gone through the basics of crea4ng a Serverless Stack, you are probably
wondering if you need to do all these manual steps every4me you create a new project. Plenty
of our readers have used this stack for their personal and professional projects. So in Part II we
are going to address some of the common issues that they run into. Specifically, we will go
over the following:

Infrastructure as code

Currently, you go through a bunch of manual steps with a lot of clicking around to
configure your backend. This makes it preEy tricky to re-create this stack for a new
project. Or to configure a new environment for the same project. Serverless Framework is
really good for conver4ng this en4re stack into code. This means that it can automa4cally
re-create the en4re project from scratch without ever touching the AWS Console.

Working with 3rd party APIs

A lot of our readers are curious about how to use serverless with 3rd party APIs. We will
go over how to connect to the Stripe API and accept credit card payments.

Unit tests

We will also look at how to configure unit tests for our backend using Jest
(hEps://facebook.github.io/jest/).

Automa;ng deployments

In the current tutorial you need to deploy through your command line using the
serverless deploy command. This can be a bit tricky when you have a team working
on your project. To start with, we’ll add our frontend and backend projects to GitHub.
We’ll then go over how to automate your deployments using Seed (hEps://seed.run) (for
the backend) and Netlify (hEps://netlify.com) (for the frontend).

Configuring environments
Typically while working on projects you end up crea4ng mul4ple environments. For
example, you’d want to make sure not to make changes directly to your app while it is in
use. Thanks to the Serverless Framework and Seed we’ll be able to do this with ease for
the backend. And we’ll do something similar for our frontend using React and Netlify.
We’ll also configure custom domains for our backend API environments.

Working with secrets

We will look at how to work with secret environment variables in our local environment
and in produc4on.

The goal of Part II is to ensure that you have a setup that you can easily replicate and use for
your future projects. This is almost exactly what we and a few of our readers have been using.

This part of the guide is fairly standalone but it does rely on the original setup. If you haven’t
completed Part I; you can quickly browse through some of the chapters but you don’t
necessarily need to redo them all. We’ll start by forking the code from the original setup and
then building on it.

Let’s get started by first conver4ng our backend infrastructure into code.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/getting-
production-ready/158)
Initialize the Backend Repo
To start with we are going to create our new project and add it to GitHub. We are going to be
working off the code we’ve created so far.

Clone the Code so Far


In your working directory, start by cloning the original repo
(hBps://github.com/AnomalyInnovaGons/serverless-stack-demo-api).

$ git clone --branch handle-api-gateway-cors-errors --depth 1


https://github.com/AnomalyInnovations/serverless-stack-demo-api.git
serverless-stack-2-api/
$ cd serverless-stack-2-api/

And remove the .git/ dir.

$ rm -rf .git/

Let’s install our Node modules as well.

$ npm install

Create a New Github Repo


Let’s head over to GitHub (hBps://github.com). Make sure you are signed in and hit New
repository.
Give your repository a name, in our case we are calling it serverless-stack-2-api . Next
hit Create repository.
Once your repository is created, copy the repository URL. We’ll need this soon.
In our case the URL is:

https://github.com/jayair/serverless-stack-2-api.git

Initialize Your New Repo


Now head back to your project and use the following command to iniGalize your
new repo.

$ git init

Add the exisGng files.

$ git add .

Create your first commit.

$ git commit -m "First commit"

Link it to the repo you created on GitHub.

$ git remote add origin REPO_URL

Here REPO_URL is the URL we copied from GitHub in the steps above. You can verify that it
has been set correctly by doing the following.

$ git remote -v

Finally, let’s push our first commit to GitHub using:

$ git push -u origin master

Next, let’s make a couple of quick changes to our project to get organized.

For help and discussion


! Comments on this chapter
(https://discourse.serverless-stack.com/t/initialize-
the-backend-repo/159)
Organize the Backend Repo
Let’s make a couple of quick changes to our project before we get started.

Remove Unused Files


We have a couple of files as a part of the starter project that we can now remove.

$ rm handler.js
$ rm tests/handler.test.js

Update the serverless.yml


We are going to use a different service name.

Open the serverless.yml and find the following line:

service: notes-app-api

And replace it with this:

service: notes-app-2-api

The reason we are doing this is because Serverless Framework uses the service name to
idenEfy projects. Since we are creaEng a new project we want to ensure that we use a
different name from the original. Now we could have simply overwriHen the exisEng project
but the resources were previously created by hand and will conflict when we try to create
them through code.

Also, find this line in the serverless.yml :

stage: prod
And replace it with:

stage: dev

We are defaulEng the stage to dev instead of prod . This will become clear later when we
create mulEple environments.

Let’s quickly commit these changes.

$ git add .
$ git commit -m "Organizing project"

Next let’s look into configuring our enEre notes app backend via our serverless.yml . This
is commonly known as Infrastructure as code.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/organize-the-
backend-repo/160)
What Is Infrastructure as Code
Serverless Framework (h0ps://serverless.com) converts your serverless.yml into a
CloudForma>on (h0ps://aws.amazon.com/cloudforma>on) template. This is a descrip>on of
the infrastructure that you are trying to configure as a part of your serverless project. In our
case we were describing the Lambda func>ons and API Gateway endpoints that we were
trying to configure.

However, in Part I we created our DynamoDB table, Cognito User Pool, S3 uploads bucket,
and Cognito Iden>ty Pool through the AWS Console. You might be wondering if this too can
be configure programma>cally, instead of doing them manually through the console. It
definitely can!

This general pa0ern is called Infrastructure as code and it has some massive benefits. Firstly, it
allows us to simply replicate our setup with a couple of simple commands. Secondly, it is not
as error prone as doing it by hand. We know a few of you have run into configura>on related
issues by simply following the steps in the tutorial. Addi>onally, describing our en>re
infrastructure as code allows us to create mul>ple environments with ease. For example, you
can create a dev environment where you can make and test all your changes as you work on it.
And this can be kept separate from your produc>on environment that your users are
interac>ng with.

In the next few chapters we are going to configure our various infrastructure pieces through
our serverless.yml .

Let’s start by configuring our DynamoDB in our serverless.yml .

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/what-is-
infrastructure-as-code/161)
Configure DynamoDB in Serverless
We are now going to start crea.ng our resources through our serverless.yml . Star.ng
with DynamoDB.

Create the Resource


Add the following to resources/dynamodb-table.yml .

Resources:
NotesTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:custom.tableName}
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
- AttributeName: noteId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
- AttributeName: noteId
KeyType: RANGE
# Set the capacity to auto-scale
BillingMode: PAY_PER_REQUEST

Let’s quickly go over what we are doing here.

1. We are describing a DynamoDB table resource called NotesTable .

2. The table we get from a custom variable ${self:custom.tableName} . This is


generated dynamically in our serverless.yml . We will look at this in detail below.
3. We are also configuring the two aFributes of our table as userId and noteId .

4. Finally, we are provisioning the read/write capacity for our table through a couple of
custom variables as well. We will be defining this shortly.

Add the Resource


Now let’s add a reference to this resource in our project.

Replace the resources: block at the boFom of our serverless.yml with the
following:

# Create our resources with separate CloudFormation templates


resources:
# API Gateway Errors
- ${file(resources/api-gateway-errors.yml)}
# DynamoDB
- ${file(resources/dynamodb-table.yml)}

Add the following custom: block at the top of our serverless.yml above the
provider: block.

custom:
# Our stage is based on what is passed in when running serverless
# commands. Or fallsback to what we have set in the provider
section.
stage: ${opt:stage, self:provider.stage}
# Set the table name here so we can use it while testing locally
tableName: ${self:custom.stage}-notes

We added a couple of things here that are worth spending some .me on:

We first create a custom variable called stage . You might be wondering why we need a
custom variable for this when we already have stage: dev in the provider: block.
This is because we want to set the current stage of our project based on what is set
through the serverless deploy --stage $STAGE command. And if a stage is not
set when we deploy, we want to fallback to the one we have set in the provider block. So
${opt:stage, self:provider.stage} , is telling Serverless to first look for the
opt:stage (the one passed in through the command line), and then fallback to
self:provider.stage (the one in the provider block.

The table name is based on the stage we are deploying to - ${self:custom.stage}-


notes . The reason this is dynamically set is because we want to create a separate table
when we deploy to a new stage (environment). So when we deploy to dev we will create
a DynamoDB table called dev-notes and when we deploy to prod , it’ll be called
prod-notes . This allows us to clearly separate the resources (and data) we use in our
various environments.

Finally, we are using the PAY_PER_REQUEST seTng for the BillingMode . This tells
DynamoDB that we want to pay per request and use the On-Demand Capacity
(hFps://aws.amazon.com/dynamodb/pricing/on-demand/) op.on. With DynamoDB in
On-Demand mode, our database is now truly Serverless. This op.on can be very cost-
effec.ve, especially if you are just star.ng out and your workloads are not very
predictable or stable. On the other hand, if you know exactly how much capacity you
need, the Provisioned Capacity (hFps://aws.amazon.com/dynamodb/pricing/provisioned/)
mode would work out to be cheaper.

A lot of the above might sound tricky and overly complicated right now. But we are seTng it
up so that we can automate and replicate our en.re setup with ease. Note that, Serverless
Framework (and CloudForma.on behind the scenes) will be completely managing our
resources based on the serverless.yml . This means that if you have a typo in your table
name, the old table will be removed and a new one will be created in place. To prevent
accidentally dele.ng serverless resources (like DynamoDB tables), you need to set the
DeletionPolicy: Retain flag. We have a detailed post on this over on the Seed blog
(hFps://seed.run/blog/how-to-prevent-accidentally-dele.ng-serverless-resources).

We are also going to make a quick tweak to reference the DynamoDB resource that we are
crea.ng.

Replace the iamRoleStatements: block in your serverless.yml with the


following.

# These environment variables are made available to our functions


# under process.env.
environment:
tableName: ${self:custom.tableName}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
# Restrict our IAM role permissions to
# the specific table for the stage
Resource:
- "Fn::GetAtt": [ NotesTable, Arn ]

Make sure to copy the indenta-on properly. These two blocks fall under the provider
block and need to be indented as such.

A couple of interes.ng things we are doing here:

1. The environment: block here is basically telling Serverless Framework to make the
variables available as process.env in our Lambda func.ons. For example,
process.env.tableName would be set to the DynamoDB table name for this stage.
We will need this later when we are connec.ng to our database.

2. For the tableName specifically, we are geTng it by referencing our custom variable
from above.

3. For the case of our iamRoleStatements: we are now specifically sta.ng which table
we want to connect to. This block is telling AWS that these are the only resources that
our Lambda func.ons have access to.

Commit Your Code


Let’s commit the changes we’ve made so far.

$ git add .
$ git commit -m "Adding our DynamoDB resource"

Next, let’s add our S3 bucket for file uploads.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/configure-
dynamodb-in-serverless/162)
Configure S3 in Serverless
Now that we have DynamoDB configured, let’s look at how we can configure the S3 file
uploads bucket through our serverless.yml .

Create the Resource


Add the following to resources/s3-bucket.yml .

Resources:
AttachmentsBucket:
Type: AWS::S3::Bucket
Properties:
# Set the CORS policy
CorsConfiguration:
CorsRules:
-
AllowedOrigins:
- '*'
AllowedHeaders:
- '*'
AllowedMethods:
- GET
- PUT
- POST
- DELETE
- HEAD
MaxAge: 3000

# Print out the name of the bucket that is created


Outputs:
AttachmentsBucketName:
Value:
Ref: AttachmentsBucket
If you recall from the Create an S3 bucket for file uploads (/chapters/create-an-s3-bucket-for-
file-uploads.html) chapter, we had created a bucket and configured the CORS policy for it. We
needed to do this because we are going to be uploading directly from our frontend client. We
configure the same policy here.

S3 buckets (unlike DynamoDB tables) are globally named. So it is not really possible for us to
know what it is going to be called before hand. Hence, we let CloudFormaMon generate the
name for us and we just add the Outputs: block to tell it to print it out so we can use it
later.

Add the Resource


Let’s reference the resource in our serverless.yml . Replace your resources:
block with the following.

# Create our resources with separate CloudFormation templates


resources:
# API Gateway Errors
- ${file(resources/api-gateway-errors.yml)}
# DynamoDB
- ${file(resources/dynamodb-table.yml)}
# S3
- ${file(resources/s3-bucket.yml)}

Commit Your Code


Let’s commit the changes we’ve made so far.

$ git add .
$ git commit -m "Adding our S3 resource"

And that’s it. Next let’s look into configuring our Cognito User Pool.

For help and discussion


! Comments on this chapter
(https://discourse.serverless-stack.com/t/configure-s3-
in-serverless/163)
Configure Cognito User Pool in
Serverless
Now let’s look into se-ng up Cognito User Pool through the serverless.yml . It should be
very similar to the one we did by hand in the Create a Cognito user pool (/chapters/create-a-
cognito-user-pool.html) chapter.

Create the Resource


Add the following to resources/cognito-user-pool.yml .

Resources:
CognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
# Generate a name based on the stage
UserPoolName: ${self:custom.stage}-user-pool
# Set email as an alias
UsernameAttributes:
- email
AutoVerifiedAttributes:
- email

CognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
# Generate an app client name based on the stage
ClientName: ${self:custom.stage}-user-pool-client
UserPoolId:
Ref: CognitoUserPool
ExplicitAuthFlows:
- ADMIN_NO_SRP_AUTH
GenerateSecret: false

# Print out the Id of the User Pool that is created


Outputs:
UserPoolId:
Value:
Ref: CognitoUserPool

UserPoolClientId:
Value:
Ref: CognitoUserPoolClient

Let’s quickly go over what we are doing here:

We are naming our User Pool (and the User Pool app client) based on the stage by using
the custom variable ${self:custom.stage} .

We are se-ng the UsernameAttributes as email. This is telling the User Pool that we
want our users to be able to log in with their email as their username.

Just like our S3 bucket, we want CloudFormaOon to tell us the User Pool Id and the User
Pool Client Id that is generated. We do this in the Outputs: block at the end.

Add the Resource


Let’s reference the resource in our serverless.yml . Replace your resources:
block with the following.

# Create our resources with separate CloudFormation templates


resources:
# API Gateway Errors
- ${file(resources/api-gateway-errors.yml)}
# DynamoDB
- ${file(resources/dynamodb-table.yml)}
# S3
- ${file(resources/s3-bucket.yml)}
# Cognito
- ${file(resources/cognito-user-pool.yml)}
Commit Your Code
Let’s commit the changes we’ve made so far.

$ git add .
$ git commit -m "Adding our Cognito User Pool resource"

And next let’s Oe all of this together by configuring our Cognito IdenOty Pool.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/configure-
cognito-user-pool-in-serverless/164)
Configure Cognito Identity Pool in
Serverless
If you recall from the first part of tutorial, we use the Cognito Iden9ty Pool as a way to control
which AWS resources our logged in users will have access to. And also 9e in our Cognito User
Pool as our authen9ca9on provider.

Create the Resource


Add the following to resources/cognito-identity-pool.yml .

Resources:
# The federated identity for our user pool to auth with
CognitoIdentityPool:
Type: AWS::Cognito::IdentityPool
Properties:
# Generate a name based on the stage
IdentityPoolName: ${self:custom.stage}IdentityPool
# Don't allow unathenticated users
AllowUnauthenticatedIdentities: false
# Link to our User Pool
CognitoIdentityProviders:
- ClientId:
Ref: CognitoUserPoolClient
ProviderName:
Fn::GetAtt: [ "CognitoUserPool", "ProviderName" ]

# IAM roles
CognitoIdentityPoolRoles:
Type: AWS::Cognito::IdentityPoolRoleAttachment
Properties:
IdentityPoolId:
Ref: CognitoIdentityPool
Roles:
authenticated:
Fn::GetAtt: [CognitoAuthRole, Arn]

# IAM role used for authenticated users


CognitoAuthRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Federated: 'cognito-identity.amazonaws.com'
Action:
- 'sts:AssumeRoleWithWebIdentity'
Condition:
StringEquals:
'cognito-identity.amazonaws.com:aud':
Ref: CognitoIdentityPool
'ForAnyValue:StringLike':
'cognito-identity.amazonaws.com:amr': authenticated
Policies:
- PolicyName: 'CognitoAuthorizedPolicy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action:
- 'mobileanalytics:PutEvents'
- 'cognito-sync:*'
- 'cognito-identity:*'
Resource: '*'

# Allow users to invoke our API


- Effect: 'Allow'
Action:
- 'execute-api:Invoke'
Resource:
Fn::Join:
- ''
-
- 'arn:aws:execute-api:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':'
- Ref: ApiGatewayRestApi
- '/*'

# Allow users to upload attachments to their


# folder inside our S3 bucket
- Effect: 'Allow'
Action:
- 's3:*'
Resource:
- Fn::Join:
- ''
-
- Fn::GetAtt: [AttachmentsBucket, Arn]
- '/private/'
- '$'
- '{cognito-identity.amazonaws.com:sub}/*'

# Print out the Id of the Identity Pool that is created


Outputs:
IdentityPoolId:
Value:
Ref: CognitoIdentityPool

Now it looks like there is a whole lot going on here. But it is preDy much exactly what we did
back in the Create a Cognito iden9ty pool (/chapters/create-a-cognito-iden9ty-pool.html)
chapter. It’s just that CloudForma9on can be a bit verbose and can end up looking a bit
in9mida9ng.
Let’s quickly go over the various sec9ons of this configura9on:

1. First we name our Iden9ty Pool based on the stage name using
${self:custom.stage} .

2. We set that we only want logged in users by adding


AllowUnauthenticatedIdentities: false .

3. Next we state that we want to use our User Pool as the iden9ty provider. We are doing
this specifically using the Ref: CognitoUserPoolClient line. If you refer back to the
Configure Cognito User Pool in Serverless (/chapters/configure-cognito-user-pool-in-
serverless.html) chapter, you’ll no9ce we have a block under CognitoUserPoolClient
that we are referencing here.

4. We then aDach a IAM role to our authen9cated users.

5. We add the various parts to this role. This is exactly what we use in the Create a Cognito
iden9ty pool (/chapters/create-a-cognito-iden9ty-pool.html) chapter. It just needs to be
formaDed this way to work with CloudForma9on.

6. The ApiGatewayRestApi ref that you might no9ce is generated by Serverless


Framework when you define an API endpoint in your serverless.yml . So in this case,
we are referencing the API resource that we are crea9ng.

7. For the S3 bucket the name is generated by AWS. So for this case we use the
Fn::GetAtt: [AttachmentsBucket, Arn] to get it’s exact name.

8. Finally, we print out the generated Iden9ty Pool Id in the Outputs: block.

Add the Resource


Let’s reference the resource in our serverless.yml . Replace your resources:
block with the following.

# Create our resources with separate CloudFormation templates


resources:
# API Gateway Errors
- ${file(resources/api-gateway-errors.yml)}
# DynamoDB
- ${file(resources/dynamodb-table.yml)}
# S3
- ${file(resources/s3-bucket.yml)}
# Cognito
- ${file(resources/cognito-user-pool.yml)}
- ${file(resources/cognito-identity-pool.yml)}

Commit Your Code


Let’s commit the changes we’ve made so far.

$ git add .
$ git commit -m "Adding our Cognito Identity Pool resource"

Next, let’s quickly reference our DynamoDB table in our Lambda func9ons using environment
variables.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/configure-
cognito-identity-pool-in-serverless/165)
Use Environment Variables in
Lambda Functions
Back in the Configure DynamoDB in Serverless (/chapters/configure-dynamodb-in-
serverless.html) chapter, we are creaBng our table through CloudFormaBon. The table that is
created is based on the stage we are currently in. This means that in our Lambda funcBons
when we talk to our database, we cannot simply hard code the table names. Since, in the
dev stage it would be called dev-notes and in prod it’ll be called prod-notes .

This requires us to use environment variables in our Lambda funcBons to figure out which
table we should be talking to. Currently, if you pull up create.js you’ll noBce the following
secBon.

const params = {
TableName: "notes",
Item: {
userId: event.requestContext.identity.cognitoIdentityId,
noteId: uuid.v1(),
content: data.content,
attachment: data.attachment,
createdAt: new Date().getTime()
}
};

We need to change the TableName: "notes" line to use the relevant table name. In the
Configure DynamoDB in Serverless (/chapters/configure-dynamodb-in-serverless.html)
chapter, we also added tableName: to our serverless.yml under the environment:
block.

# These environment variables are made available to our functions


# under process.env.
environment:
tableName: ${self:custom.tableName}

As we noted there, we can reference this in our Lambda funcBons as


process.env.tableName .

So let’s go ahead and make the change.

Replace this line in create.js .

TableName: "notes",

with this:

TableName: process.env.tableName,

Similarly, in the get.js , replace this:

TableName: "notes",

with this:

TableName: process.env.tableName,

In list.js , replace this:

TableName: "notes",

with this:

TableName: process.env.tableName,

Also in update.js , replace this:

TableName: "notes",

with this:
TableName: process.env.tableName,

Finally in delete.js , replace this:

TableName: "notes",

with this:

TableName: process.env.tableName,

Commit Your Code


Make sure to commit your code using:

$ git add .
$ git commit -m "Use environment variables in our functions"

Next let’s deploy our newly configured serverless backend API.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/use-
environment-variables-in-lambda-functions/166)
Deploy Your Serverless
Infrastructure
Now that we have all our resources configured, let’s go ahead and deploy our en7re
infrastructure.

We should men7on though that our current project has all of our resources and the Lambda
func7ons that we had created in the first part of our tutorial. This is a common trend in
serverless projects. Your code and infrastructure are not treated differently. Of course, as your
projects get larger, you end up spliDng them up. So you might have a separate Serverless
Framework project that deploys your infrastructure while a different project just deploys your
Lambda func7ons.

Deploy Your Project


Deploying our project is fairly straighIorward thanks to our serverless deploy
command. So go ahead and run this from the root of your project.

$ serverless deploy -v

Your output should look something like this:

Serverless: Stack update finished...


Service Information
service: notes-app-2-api
stage: dev
region: us-east-1
stack: notes-app-2-api-dev
api keys:
None
endpoints:
POST - https://mqqmkwnpbc.execute-api.us-east-
1.amazonaws.com/dev/notes
GET - https://mqqmkwnpbc.execute-api.us-east-
1.amazonaws.com/dev/notes/{id}
GET - https://mqqmkwnpbc.execute-api.us-east-
1.amazonaws.com/dev/notes
PUT - https://mqqmkwnpbc.execute-api.us-east-
1.amazonaws.com/dev/notes/{id}
DELETE - https://mqqmkwnpbc.execute-api.us-east-
1.amazonaws.com/dev/notes/{id}
functions:
create: notes-app-2-api-dev-create
get: notes-app-2-api-dev-get
list: notes-app-2-api-dev-list
update: notes-app-2-api-dev-update
delete: notes-app-2-api-dev-delete

Stack Outputs
AttachmentsBucketName: notes-app-2-api-dev-attachmentsbucket-
oj4rfiumzqf5
UserPoolClientId: ft93dvu3cv8p42bjdiip7sjqr
UserPoolId: us-east-1_yxO5ed0tq
DeleteLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-
1:232771856781:function:notes-app-2-api-dev-delete:2
CreateLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-
1:232771856781:function:notes-app-2-api-dev-create:2
GetLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-
1:232771856781:function:notes-app-2-api-dev-get:2
UpdateLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-
1:232771856781:function:notes-app-2-api-dev-update:2
IdentityPoolId: us-east-1:64495ad1-617e-490e-a6cf-fd85e7c8327e
BillingLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-
1:232771856781:function:notes-app-2-api-dev-billing:1
ListLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-
1:232771856781:function:notes-app-2-api-dev-list:2
ServiceEndpoint: https://mqqmkwnpbc.execute-api.us-east-
1.amazonaws.com/dev
ServerlessDeploymentBucketName: notes-app-2-api-dev-
serverlessdeploymentbucket-1p2o0dshaz2qc
A couple of things to note here:

We are deploying to a stage called dev . This has been set in our serverless.yml
under the provider: block. We can override this by explicitly passing it in by running
the serverless deploy --stage $STAGE_NAME command instead.

Our deploy command (with the -v op7on) prints out the output we had requested in our
resources. For example, AttachmentsBucketName is the S3 file uploads bucket that
was created and the UserPoolId is the Id of our User Pool.

Finally, you can run the deploy command and CloudForma7on will only update the parts
that have changed. So you can confidently run this command without worrying about it
re-crea7ng your en7re infrastructure from scratch.

And that’s it! Our en7re infrastructure is completely configured and deployed automa7cally.

Next, we will add a new API (and Lambda func7on) to work with 3rd party APIs. In our case
we are going to add an API that will use Stripe to bill the users of our notes app!

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploy-your-
serverless-infrastructure/167)

For reference, here is the code we are using

" Backend Source :deploy-your-serverless-infrastructure


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/deploy-your-serverless-infrastructure)
Working with 3rd Party APIs
In the first part of the tutorial, we created a basic CRUD API. We are going to make a small
addiAon to this by adding an endpoint that works with a 3rd party API. This secAon is also
going to illustrate how to work with secret environment variables and how to accept credit
card payments using Stripe.

A common extension of Serverless Stack (that we have noAced) is to add a billing API that
works with Stripe. In the case of our notes app we are going to allow our users to pay a fee for
storing a certain number of notes. The flow is going to look something like this:

1. The user is going to select the number of notes he wants to store and puts in his credit
card informaAon.

2. We are going to generate a one Ame token by calling the Stripe SDK on the frontend to
verify that the credit card info is valid.

3. We will then call an API passing in the number of notes and the generated token.

4. The API will take the number of notes, figure out how much to charge (based on our
pricing plan), and call the Stripe API to charge our user.

We aren’t going to do much else in the way of storing this info in our database. We’ll leave
that as an exercise for the reader.

Let’s get started with first seRng up our Stripe account.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/working-with-
3rd-party-apis/168)
Setup a Stripe Account
Let’s start by crea,ng a free Stripe account. Head over to Stripe
(h;ps://dashboard.stripe.com/register) and register for an account.

Once signed in, click the Developers link on the leD.


And hit API keys.
The first thing to note here is that we are working with a test version of API keys. To create
the live version, you’d need to verify your email address and business details to ac,vate your
account. For the purpose of this guide we’ll con,nue working with our test version.

The second thing to note is that we need to generate the Publishable key and the Secret key.
The Publishable key is what we are going to use in our frontend client with the Stripe SDK.
And the Secret key is what we are going to use in our API when asking Stripe to charge our
user. As denoted, the Publishable key is public while the Secret key needs to stay private.

Hit the Reveal test key token.

Make a note of both the Publishable key and the Secret key. We are going to be using these
later.

Next let’s create our billing API.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/setup-a-
stripe-account/169)
Add a Billing API
Now let’s get started with crea1ng our billing API. It is going to take a Stripe token and the
number of notes the user wants to store.

Add a Billing Lambda


Start by installing the Stripe NPM package. Run the following in the root of our
project.

$ npm install --save stripe

Next, add the following to billing.js .

import stripePackage from "stripe";


import { calculateCost } from "./libs/billing-lib";
import { success, failure } from "./libs/response-lib";

export async function main(event, context) {


const { storage, source } = JSON.parse(event.body);
const amount = calculateCost(storage);
const description = "Scratch charge";

// Load our secret key from the environment variables


const stripe = stripePackage(process.env.stripeSecretKey);

try {
await stripe.charges.create({
source,
amount,
description,
currency: "usd"
});
return success({ status: true });
} catch (e) {
return failure({ message: e.message });
}
}

Most of this is fairly straighDorward but let’s go over it quickly:

We get the storage and source from the request body. The storage variable is
the number of notes the user would like to store in his account. And source is the
Stripe token for the card that we are going to charge.

We are using a calculateCost(storage) func1on (that we are going to add soon) to


figure out how much to charge a user based on the number of notes that are going to be
stored.

We create a new Stripe object using our Stripe Secret key. We are going to get this as an
environment variable. We do not want to put our secret keys in our code and commit that
to Git. This is a security issue.

Finally, we use the stripe.charges.create method to charge the user and respond
to the request if everything went through successfully.

Add the Business Logic


Now let’s implement our calculateCost method. This is primarily our business logic.

Create a libs/billing-lib.js and add the following.

export function calculateCost(storage) {


const rate = storage <= 10
? 4
: storage <= 100
? 2
: 1;

return rate * storage * 100;


}
This is basically saying that if a user wants to store 10 or fewer notes, we’ll charge them $4 per
note. For 100 or fewer, we’ll charge $2 and anything more than a 100 is $1 per note. Clearly,
our serverless infrastructure might be cheap but our service isn’t!

Configure the API Endpoint


Let’s add a reference to our new API and Lambda func1on.

Add the following above the resources: block in the serverless.yml .

billing:
handler: billing.main
events:
- http:
path: billing
method: post
cors: true
authorizer: aws_iam

Make sure this is indented correctly. This block falls under the functions block.

Commit Our Changes


Let’s quickly commit these to Git.

$ git add .
$ git commit -m "Adding a billing API"

Now before we can test our API we need to load our Stripe secret key in our environment.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/add-a-billing-
api/170)
Load Secrets from env.yml
As we had previously men3oned, we do not want to store our secret environment variables in
our code. In our case it is the Stripe secret key. In this chapter, we’ll look at how to do that.

We have a env.example file for this exact purpose.

Start by renaming the env.example file to env.yml and replace its contents
with the following.

# Add the environment variables for the various stages

prod:
stripeSecretKey: "STRIPE_PROD_SECRET_KEY"

default:
stripeSecretKey: "STRIPE_TEST_SECRET_KEY"

Make sure to replace the STRIPE_PROD_SECRET_KEY and STRIPE_TEST_SECRET_KEY


with the Secret key from the Setup a Stripe account (/chapters/setup-a-stripe-account.html)
chapter. In our case we only have the test versions of the Stripe Secret key, so both these will
be the same.

Next, let’s add a reference to these.

Add the following in the custom: block of serverless.yml .

# Load our secret environment variables based on the current stage.


# Fallback to default if it is not in prod.
environment: ${file(env.yml):${self:custom.stage},
file(env.yml):default}

The custom: block of our serverless.yml should look like the following:
custom:
# Our stage is based on what is passed in when running serverless
# commands. Or fallsback to what we have set in the provider
section.
stage: ${opt:stage, self:provider.stage}
# Set the table name here so we can use it while testing locally
tableName: ${self:custom.stage}-notes
# Load our secret environment variables based on the current stage.
# Fallback to default if it is not in prod.
environment: ${file(env.yml):${self:custom.stage},
file(env.yml):default}

And add the following in the environment: block in your serverless.yml .

stripeSecretKey: ${self:custom.environment.stripeSecretKey}

Your environment: block should look like this:

# These environment variables are made available to our functions


# under process.env.
environment:
tableName: ${self:custom.tableName}
stripeSecretKey: ${self:custom.environment.stripeSecretKey}

A quick explana3on on the above:

We are loading a custom variable called environment from the env.yml file. This is
based on the stage (we are deploying to) using
file(env.yml):${self:custom.stage} . But if that stage is not defined in the
env.yml then we fallback to loading everything under the default: block using
file(env.yml):default . So Serverless Framework checks if the first is available
before falling back to the second.

We then use this to add it to our environment variables by adding stripeSecretKey to


the environment: block using ${self:custom.environment.stripeSecretKey} .
This makes it available as process.env.stripeSecretKey in our Lambda func3ons.
You’ll recall this from the previous chapter.
Commit Our Changes
Now we need to ensure that we don’t commit our env.yml file to git. The starter project
that we are using has the following in the .gitignore .

# Env
env.yml

This will tell Git to not commit this file.

Next let’s commit the rest of our changes.

$ git add .
$ git commit -m "Adding stripe environment variable"

Now we are ready to test our billing API.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/load-secrets-
from-env-yml/171)
Test the Billing API
Now that we have our billing API all set up, let’s do a quick test in our local environment.

Create a mocks/billing-event.json file and add the following.

{
"body": "{\"source\":\"tok_visa\",\"storage\":21}",
"requestContext": {
"identity": {
"cognitoIdentityId": "USER-SUB-1234"
}
}
}

We are going to be tesBng with a Stripe test token called tok_visa and with 21 as the
number of notes we want to store. You can read more about the Stripe test cards and tokens
in the Stripe API Docs here (hGps://stripe.com/docs/tesBng#cards).

Let’s now invoke our billing API by running the following in our project root.

$ serverless invoke local --function billing --path mocks/billing-


event.json

The response should look similar to this.

{
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
},
"body": "{\"status\":true}"
}
Commit the Changes
Let’s commit these to Git.

$ git add .
$ git commit -m "Adding a mock event for the billing API"

Now that we have our new billing API ready. Let’s look at how to setup unit tests to ensure
that our business logic has been configured correctly.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/test-the-
billing-api/172)
Unit Tests in Serverless
So we have some simple business logic that figures out exactly how much to charge our user
based on the number of notes they want to store. We want to make sure that we test all the
possible cases for this before we start charging people. To do this we are going to configure
unit tests for our Serverless Framework project.

We are going to use Jest (hBps://facebook.github.io/jest/) for this and it is already a part of
our starter project (hBps://github.com/AnomalyInnovaHons/serverless-nodejs-starter).

However, if you are starHng a new Serverless Framework project. Add Jest to your dev
dependencies by running the following.

$ npm install --save-dev jest

And update the scripts block in your package.json with the following:

"scripts": {
"test": "jest"
},

This will allow you to run your tests using the command npm test .

AlternaHvely, if you are using the serverless-bundle


(hBps://github.com/AnomalyInnovaHons/serverless-bundle) plugin to package your funcHons,
it comes with a built-in script to transpile your code and run your tests. Add the following to
your package.json instead.

"scripts": {
"test": "serverless-bundle test"
},

Add Unit Tests


Now create a new file in tests/billing.test.js and add the following.

import { calculateCost } from "../libs/billing-lib";

test("Lowest tier", () => {


const storage = 10;

const cost = 4000;


const expectedCost = calculateCost(storage);

expect(cost).toEqual(expectedCost);
});

test("Middle tier", () => {


const storage = 100;

const cost = 20000;


const expectedCost = calculateCost(storage);

expect(cost).toEqual(expectedCost);
});

test("Highest tier", () => {


const storage = 101;

const cost = 10100;


const expectedCost = calculateCost(storage);

expect(cost).toEqual(expectedCost);
});

This should be straighMorward. We are adding 3 tests. They are tesHng the different Hers of
our pricing structure. We test the case where a user is trying to store 10, 100, and 101 notes.
And comparing the calculated cost to the one we are expecHng. You can read more about
using Jest in the Jest docs here (hBps://facebook.github.io/jest/docs/en/geSng-started.html).

Run tests
And we can run our tests by using the following command in the root of our project.

$ npm test

You should see something like this:

PASS tests/billing.test.js
✓ Lowest tier (4ms)
✓ Middle tier
✓ Highest tier (1ms)

Test Suites: 1 passed, 1 total


Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.665s
Ran all test suites.

And that’s it! We have unit tests all configured.

Commit the Changes


Let’s commit these changes.

$ git add .
$ git commit -m "Adding unit tests"

Push the Changes


We are done making changes to our project, so let’s go ahead and push them to
GitHub.

$ git push

Next we’ll use our Git repo to automate our deployments. This will ensure that when we push
our changes to Git, it will run our tests, and deploy them for us automaHcally. We’ll also learn
to configure mulHple environments.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/unit-tests-in-
serverless/173)

For reference, here is the code we are using

" Backend Source :unit-tests-in-serverless


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api/tree/unit-tests-in-serverless)
Automating Serverless Deployments
So to recap, this is what we have so far:

A serverless project that has all it’s infrastructure completely configured in code
A way to handle secrets locally
And finally, a way to run unit tests to test our business logic

All of this is neatly commi>ed in a Git repo.

Next we are going to use our Git repo to automate our deployments. This essenDally means
that we can deploy our enDre project by simply pushing our changes to Git. This can be
incredibly useful since you won’t need to create any special scripts or configuraDons to deploy
your code. You can also have mulDple people on your team deploy with ease.

Along with automaDng deployments, we are also going to look at working with mulDple
environments. We want to create clear separaDon between our producDon environment and
our dev environment. We are going to create a workflow where we conDnually deploy to our
dev (or any non-prod) environment. But we will be using a manual promoDon step when we
promote to producDon. We’ll also look at configuring custom domains for APIs.

For automaDng our serverless backend, we are going to be using a service called Seed
(h>ps://seed.run). Full disclosure, we also built Seed. You can replace most of this secDon with
a service like Travis CI (h>ps://travis-ci.org) or CircleCI (h>ps://circleci.com). It’s a bit more
cumbersome and needs some scripDng. We have a couple of posts on this over on the Seed
(h>ps://seed.run) blog:

Configure a CI/CD pipeline for Serverless apps on CircleCI (h>ps://seed.run/blog/how-to-


build-a-cicd-pipeline-for-serverless-apps-with-circleci)
Configure a CI/CD pipeline for Serverless apps on Travis CI (h>ps://seed.run/blog/how-
to-build-a-cicd-pipeline-for-serverless-apps-with-travis-ci)

Let’s get started with seTng up your project on Seed.


For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/automating-
serverless-deployments/174)
Setting up Your Project on Seed
We are going to use Seed (h1ps://seed.run) to automate our serverless deployments and
manage our environments.

Start by signing up for a free account here (h1ps://console.seed.run/signup-account).

Let’s Add your first app.


Now to add your project, select GitHub as your git provider. You’ll be asked to give Seed
permission to your GitHub account.
Select the repo we’ve been using so far.

Next, Seed will scan your repos for a serverless.yml . Hit Add Service to confirm this.
Note that, if your serverless.yml is not in your project root, you will need to change the
path.

Seed deploys to your AWS account on your behalf. You should create a separate IAM user
with exact permissions that your project needs. You can read more about this here
(h1ps://seed.run/docs/customizing-your-iam-policy). But for now we’ll simply use the one
we’ve used in this tutorial.

Run the following command.

$ cat ~/.aws/credentials

The output should look something like this.

[default]
aws_access_key_id = YOUR_IAM_ACCESS_KEY
aws_secret_access_key = YOUR_IAM_SECRET_KEY

Seed will also create a couple of stages (or environments) for you. By default, it’ll create a dev
and a prod stage using the same AWS credenRals. You can customize these but for us this is
perfect.

Fill in the credenRals and click Add a New App.

You new app is ready to go!


You’ll noRce a few things here. First, we have a service called notes-app-2-api. It’s picking up
the service name from our serverless.yml . You can choose to change this by clicking on
the service and ediRng its name. A Serverless app can have mulRple services within it. A
service (roughly speaking) is a reference to a serverless.yml file. In our case we have one
service in the root of our repo. You’ll also noRce the two stages that have been created.

Now before we proceed to deploying our app, we need to enable running unit tests as a part
of our build process. You’ll recall that we had added a couple of tests back in the unit tests
(/chapters/unit-tests-in-serverless.html) chapter. And we want to run those before we deploy
our app.

To do this, hit the Se:ngs link and click Enable Unit Tests.
Back in our pipeline, you’ll noRce that our dev stage is hooked up to master. This means that
any commits to master will trigger a build in dev.

Click on dev.
You’ll see that we haven’t deployed to this stage yet.
However, before we do that, we’ll need to add our secret environment variables.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/setting-up-
your-project-on-seed/175)
Configure Secrets in Seed
Before we can do our first deployment, we need to make sure to configure our secret
environment variables. If you’ll recall, we have explicitly not stored these in our code (or in
Git). This means that if somebody else on our team needs to deploy, we’ll need to pass the
env.yml file around. Instead we’ll configure Seed (hDps://seed.run) to deploy with our
secrets for us.

To do that, hit the Se#ngs buDon in our dev stage.

Here click Show Env Variables.


And type in stripeSecretKey as the Key and the value should be the
STRIPE_TEST_SECRET_KEY back from the Load secrets from env.yml (/chapters/load-
secrets-from-env-yml.html) chapter. Hit Add to save your secret key.
Next we need to configure our secrets for the prod stage. Head over there and hit the
Se#ngs buDon.
Click Show Env Variables.
And type in stripeSecretKey as the Key and the value should be the
STRIPE_PROD_SECRET_KEY back from the Load secrets from env.yml (/chapters/load-
secrets-from-env-yml.html) chapter. Hit Add to save your secret key.

Next, we’ll trigger our first deployment on Seed.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/configure-
secrets-in-seed/176)
Deploying Through Seed
Now, we are ready to make our first deployment. You can either Git push a new change to
master to trigger it. Or we can just go into the dev stage and hit the Trigger Deploy bu>on.

Let’s do it through Git.

Go back to your project root and run the following.

$ npm version patch

This is simply updaCng the NPM version for your project. It is a good way to keep track of the
changes you are making to your project. And it also creates a quick Git commit for us.

Push the change using.

$ git push

Now if you head into the dev stage in Seed, you should see a build in progress. Now to see the
build logs, you can hit Build v1.
Here you’ll see the build taking place live. Click on the service that is being deployed. In this
case, we only have one service.
You’ll see the build logs for the in progress build here.
NoCce the tests are being run as a part of the build.

Something cool to note here is that, the build process is split into a few parts. First the code is
checked out through Git and the tests are run. But we don’t directly deploy. Instead, we create
a package for the dev stage and the prod stage. And finally we deploy to dev with that
package. The reason this is split up is because we want avoid the build process while
promoCng to prod . This ensures that if we have a tested working build, it should just work
when we promote to producCon.

You might also noCce a couple of warnings that look like the following.

Serverless Warning --------------------------------------

A valid file to satisfy the declaration


'file(env.yml):dev,file(env.yml):default' could not be found.

Serverless Warning --------------------------------------


A valid file to satisfy the declaration
'file(env.yml):dev,file(env.yml):default' could not be found.

Serverless Warning --------------------------------------

A valid service attribute to satisfy the declaration


'self:custom.environment.stripeSecretKey' could not be found.

These are expected since the env.yml is not a part of our Git repo and is not available in the
build process. The Stripe key is instead set directly in the Seed console.

Once the build is complete, take a look at the build log and make a note of the following:

Region: region
Cognito User Pool Id: UserPoolId
Cognito App Client Id: UserPoolClientId
Cognito IdenCty Pool Id: IdentityPoolId
S3 File Uploads Bucket: AttachmentsBucketName
API Gateway URL: ServiceEndpoint

We’ll be needing these later in our frontend and when we test our APIs.
Now head over to the app home page. You’ll noCce that we are ready to promote to
producCon.

We have a manual promoCon step so that you get a chance to review the changes and ensure
that you are ready to push to producCon.

Hit the Promote bu>on.


This brings up a dialog that will generate a Change Set. It compares the resources that are
being updated with respect to what you have in producCon. It’s a great way to compare the
infrastructure changes that are being promoted.
Scroll down and hit Promote to Produc5on.
You’ll noCce that the build is being promoted to the prod stage.

And if you head over to the prod stage, you should see your prod deployment in acCon. It
should take a second to deploy to producCon. And just like before, make a note of the
following.

Region: region
Cognito User Pool Id: UserPoolId
Cognito App Client Id: UserPoolClientId
Cognito IdenCty Pool Id: IdentityPoolId
S3 File Uploads Bucket: AttachmentsBucketName
API Gateway URL: ServiceEndpoint
Next let’s configure our serverless API with a custom domain.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploying-
through-seed/177)
Set Custom Domains Through Seed
Our serverless API uses API Gateway and it gives us some auto-generated endpoints. We
would like to configure them to use a scheme like api.my-domain.com or something
similar. This can take a few different steps through the AWS Console, but it is preFy
straighGorward to configure through Seed (hFps://seed.run).

From our prod stage, click on View Resources.

This shows you a list of the API endpoints and Lambda funcNons that are a part of this
deployment. Now click on Se/ngs.
And hit Update Custom Domain.
In the first part of the tutorial we had added our domain to Route 53. If you haven’t done so
you can read more about it here
(hFps://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigraNngDNS.html). Hit
Select a domain and you should see a list of all your Route 53 domains. Select the one you
intend to use. And fill in the sub-domain and base path. For example, you could use api.my-
domain.com/prod ; where api is the sub-domain and prod is the base path.

And hit Update.

Seed will now go through and configure the domain for this API Gateway endpoint, create the
SSL cerNficate and aFach it to the domain. This process can take up to 40 mins.

While we wait, we can do the same for our dev stage. Go into the dev stage > click View
Deployment > click Se/ngs > and hit Update Custom Domain. And select the domain, sub-
domain, and base path. In our case we’ll use something like api.my-domain.com/dev .
Hit Update and wait for the changes to take place.

Once complete, we are ready to test our fully-configured serverless API backend!

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/set-custom-
domains-through-seed/178)
Test the Configured APIs
Now we have two sets of APIs (prod and dev), let’s quickly test them to make sure they are
working fine before we plug our frontend into them. Back in the Test the APIs (/chapters/test-
the-apis.html) chapter, we used a simple uGlity called AWS API Gateway Test CLI
(hMps://github.com/AnomalyInnovaGons/aws-api-gateway-cli-test).

Before we do the test let’s create a test user for both the environments. We’ll be following the
exact same steps as the Create a Cognito test user (/chapters/create-a-cognito-test-user.html)
chapter.

Create Test Users


We are going to use the AWS CLI for this.

In your terminal, run.

$ aws cognito-idp sign-up \


--region YOUR_DEV_COGNITO_REGION \
--client-id YOUR_DEV_COGNITO_APP_CLIENT_ID \
--username [email protected] \
--password Passw0rd!

Refer back to the Deploying through Seed (/chapters/deploying-through-seed.html) chapter to


look up the dev version of your Cognito App Client Id. And replace
YOUR_DEV_COGNITO_REGION with the region that you deployed to.

Next we’ll confirm the user through the Cognito Admin CLI.

$ aws cognito-idp admin-confirm-sign-up \


--region YOUR_DEV_COGNITO_REGION \
--user-pool-id YOUR_DEV_COGNITO_USER_POOL_ID \
--username [email protected]
Again, replace YOUR_DEV_COGNITO_USER_POOL_ID with the dev version of your Cognito
User Pool Id from the Deploying through Seed (/chapters/deploying-through-seed.html)
chapter and the region from the previous command.

Let’s quickly do the same with prod versions as well.

In your terminal, run.

$ aws cognito-idp sign-up \


--region YOUR_PROD_COGNITO_REGION \
--client-id YOUR_PROD_COGNITO_APP_CLIENT_ID \
--username [email protected] \
--password Passw0rd!

Here use your prod version of your Cognito details.

And confirm the user.

$ aws cognito-idp admin-confirm-sign-up \


--region YOUR_PROD_COGNITO_REGION \
--user-pool-id YOUR_PROD_COGNITO_USER_POOL_ID \
--username [email protected]

Make sure to use the prod versions here as well.

Now we are ready to test our APIs.

Test the API


Let’s test our dev endpoint. Run the following command:

$ npx aws-api-gateway-cli-test \
--username='[email protected]' \
--password='Passw0rd!' \
--user-pool-id='YOUR_DEV_COGNITO_USER_POOL_ID' \
--app-client-id='YOUR_DEV_COGNITO_APP_CLIENT_ID' \
--cognito-region='YOUR_DEV_COGNITO_REGION' \
--identity-pool-id='YOUR_DEV_IDENTITY_POOL_ID' \
--invoke-url='YOUR_DEV_API_GATEWAY_URL' \
--api-gateway-region='YOUR_DEV_API_GATEWAY_REGION' \
--path-template='/notes' \
--method='POST' \
--body='{"content":"hello world","attachment":"hello.jpg"}'

Refer back to the Deploying through Seed (/chapters/deploying-through-seed.html) chapter


for these:

YOUR_DEV_COGNITO_USER_POOL_ID and YOUR_DEV_COGNITO_APP_CLIENT_ID are


all related to your Cognito User Pool.
YOUR_DEV_IDENTITY_POOL_ID is for your Cognito IdenGty Pool.
And YOUR_DEV_API_GATEWAY_URL is your API Gateway endpoint. It looks something
likes this https://ly55wbovq4.execute-api.us-east-1.amazonaws.com/dev .
But if you have configured it with a custom domain use the one from the Set custom
domains through Seed (/chapters/set-custom-domains-through-seed.html) chapter.
Finally, the YOUR_DEV_API_GATEWAY_REGION and YOUR_DEV_COGNITO_REGION is
the region you deployed to. In our case it is us-east-1 .

If the command is successful, it’ll look something like this.

Authenticating with User Pool


Getting temporary credentials
Making API request
{ status: 200,
statusText: 'OK',
data:
{ userId: 'us-east-1:9bdc031d-ee9e-4ffa-9a2d-123456789',
noteId: '8f7da030-650b-11e7-a661-123456789',
content: 'hello world',
attachment: 'hello.jpg',
createdAt: 1499648598452 } }

Also run the same command for prod. Make sure to use the prod versions.

$ npx aws-api-gateway-cli-test \
--username='[email protected]' \
--password='Passw0rd!' \
--user-pool-id='YOUR_PROD_COGNITO_USER_POOL_ID' \
--app-client-id='YOUR_PROD_COGNITO_APP_CLIENT_ID' \
--cognito-region='YOUR_PROD_COGNITO_REGION' \
--identity-pool-id='YOUR_PROD_IDENTITY_POOL_ID' \
--invoke-url='YOUR_PROD_API_GATEWAY_URL' \
--api-gateway-region='YOUR_PROD_API_GATEWAY_REGION' \
--path-template='/notes' \
--method='POST' \
--body='{"content":"hello world","attachment":"hello.jpg"}'

And you should see it give a similar output as dev.

Now that our APIs our tested we are ready to plug these into our frontend. But before we do
that, let’s do a quick test to see what will happen if we make a mistake and push some faulty
code to producGon.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/test-the-
configured-apis/179)
Monitoring Deployments in Seed
Despite our best inten-ons we might run into cases where some faulty code ends up in
produc-on. We want to make sure we have a plan for that. Let’s go through what this would
look like in Seed (h@ps://seed.run).

Push Some Faulty Code


First start by pushing an obvious mistake.

Add the following to functions/create.js right at the top of our func-on.

gibberish.what;

Now there is no such variable as gibberish so this code should fail.

Let’s commit and push this to dev.

$ git add .
$ git commit -m "Making a mistake"
$ git push

Now you can see a build in progress. Wait for it to complete and hit Promote.
Confirm the Change Set by hiIng Promote to Produc+on.
Enable Access Logs
Now before we test our faulty code, we’ll turn on API Gateway access logs so we can see the
error. Click on the prod stage View Resources.

Hit Se4ngs.
Hit Enable Access Logs.
This will take a couple of minutes but Seed will automa-cally configure the IAM roles
necessary for this and enable API Gateway access logs for your prod environment.

Test the Faulty Code


Now to test our code, run the same command from the last chapter (/chapters/test-the-
configured-apis.html) to test our API.

$ npx aws-api-gateway-cli-test \
--username='[email protected]' \
--password='Passw0rd!' \
--user-pool-id='YOUR_PROD_COGNITO_USER_POOL_ID' \
--app-client-id='YOUR_PROD_COGNITO_APP_CLIENT_ID' \
--cognito-region='YOUR_PROD_COGNITO_REGION' \
--identity-pool-id='YOUR_PROD_IDENTITY_POOL_ID' \
--invoke-url='YOUR_PROD_API_GATEWAY_URL' \
--api-gateway-region='YOUR_PROD_API_GATEWAY_REGION' \
--path-template='/notes' \
--method='POST' \
--body='{"content":"hello world","attachment":"hello.jpg"}'

Make sure to use the prod version of your resources.

You should see an error that looks something like this.

Authenticating with User Pool


Getting temporary credentials
Making API request
{ status: 502,
statusText: 'Bad Gateway',
data: { message: 'Internal server error' } }

View Logs and Metrics


Back in the Seed console, you should be able to click on Access Logs.
This should show you that there was a 502 error on a recent request.
If you go back, you can click on Metrics to get a good overview of our requests.

You’ll no-ce the number of requests that were made, 4xx errors, 5xx error, and latency for
those requests.
Now if we go back and click on the Logs for the create Lambda func-on.
This should show you clearly that there was an error in our code. No-ce, that it is complaining
that gibberish is not defined.

And just like the API metrics, the Lambda metrics will show you an overview of what is going
on at a func-on level.
Rollback in Production
Now obviously, we have a problem. Usually you might be tempted to fix the code and push
and promote the change. But since our users might be affected by faulty promo-ons to prod,
we want to rollback our changes immediately.

To do this, head back to the prod stage. And hit the Rollback bu@on on the previous build we
had in produc-on.
Seed keeps track of your past builds and simply uses the previously built package to deploy it
again.
And now if you run your test command from before.

$ npx aws-api-gateway-cli-test \
--username='[email protected]' \
--password='Passw0rd!' \
--user-pool-id='YOUR_PROD_COGNITO_USER_POOL_ID' \
--app-client-id='YOUR_PROD_COGNITO_APP_CLIENT_ID' \
--cognito-region='YOUR_PROD_COGNITO_REGION' \
--identity-pool-id='YOUR_PROD_IDENTITY_POOL_ID' \
--invoke-url='YOUR_PROD_API_GATEWAY_URL' \
--api-gateway-region='YOUR_PROD_API_GATEWAY_REGION' \
--path-template='/notes' \
--method='POST' \
--body='{"content":"hello world","attachment":"hello.jpg"}'

You should see it succeed this -me.

Authenticating with User Pool


Getting temporary credentials
Making API request
{ status: 200,
statusText: 'OK',
data:
{ userId: 'us-east-1:9bdc031d-ee9e-4ffa-9a2d-123456789',
noteId: '8f7da030-650b-11e7-a661-123456789',
content: 'hello world',
attachment: 'hello.jpg',
createdAt: 1499648598452 } }

Revert the Code


Finally, don’t forget to revert your code in functions/create.js .

gibberish.what;

And commit and push the changes.

$ git add .
$ git commit -m "Fixing the mistake"
$ git push

And that’s it! We are now ready to plug this into our frontend.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/monitoring-
deployments-in-seed/180)

For reference, here is the code for Part II

" Backend Part II Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-api)
Initialize the Frontend Repo
Just as we did in the backend por3on, we’ll start by crea3ng our project and adding it to
GitHub. We will use what we had in Part I as a star3ng point.

Clone the Original Repo


In your working directory, start by cloning the original repo
(hAps://github.com/AnomalyInnova3ons/serverless-stack-demo-client). Make sure this is not
inside the directory for our backend.

$ git clone --branch part-1 --depth 1


https://github.com/AnomalyInnovations/serverless-stack-demo-client.git
serverless-stack-2-client/
$ cd serverless-stack-2-client/

And remove the .git/ dir.

$ rm -rf .git/

Let’s install our Node modules.

$ npm install

Create a New GitHub Repo


Let’s head over to GitHub (hAps://github.com). Make sure you are signed in and hit New
repository.
Give your repository a name, in our case we are calling it serverless-stack-2-client .
And hit Create repository.
Once your repository is created, copy the repository URL. We’ll need this soon.
In our case the URL is:

https://github.com/jayair/https://github.com/jayair/serverless-stack-
2-client.git

Initialize Your New Repo


Now head back to your project and use the following command to ini3alize your
new repo.

$ git init

Add the exis3ng files.

$ git add .

Create your first commit.

$ git commit -m "First commit"

Link it to the repo you created on GitHub.

$ git remote add origin REPO_URL

Here REPO_URL is the URL we copied from GitHub in the steps above. You can verify that it
has been set correctly by doing the following.

$ git remote -v

Finally, let’s push our first commit to GitHub using:

$ git push -u origin master

Next let’s look into configuring our frontend client with the environments that we have in our
backend.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/initialize-
the-frontend-repo/181)
Manage Environments in Create
React App
Recall from our backend sec1on that we created two environments (dev and prod) for our
serverless backend API. In this chapter we’ll configure our frontend Create React App to
connect to it.

Let’s start by looking at how our app is configured currently. Our src/config.js stores the
info to all of our backend resources.

export default {
MAX_ATTACHMENT_SIZE: 5000000,
s3: {
REGION: "us-east-1",
BUCKET: "notes-app-uploads"
},
apiGateway: {
REGION: "us-east-1",
URL: "https://5by75p4gn3.execute-api.us-east-1.amazonaws.com/prod"
},
cognito: {
REGION: "us-east-1",
USER_POOL_ID: "us-east-1_udmFFSb92",
APP_CLIENT_ID: "4hmari2sqvskrup67crkqa4rmo",
IDENTITY_POOL_ID: "us-east-1:ceef8ccc-0a19-4616-9067-854dc69c2d82"
}
};

We need to change this so that when we push our app to dev it connectes to the dev
environment of our backend and for prod it connects to the prod environment. Of course you
can add many more environments, but let’s just s1ck to these for now.
Environment Variables in Create React App
Our React app is a sta1c single page app. This means that once a build is created for a certain
environment it persists for that environment.

Create React App (hIps://github.com/facebookincubator/create-react-


app/blob/master/packages/react-scripts/template/README.md#adding-custom-
environment-variables) has support for custom environment variables baked into the build
system. To set a custom environment variable, simply set it while star1ng the Create React
App build process.

$ REACT_APP_TEST_VAR=123 npm start

Here REACT_APP_TEST_VAR is the custom environment variable and we are seRng it to the
value 123 . In our app we can access this variable as process.env.REACT_APP_TEST_VAR .
So the following line in our app:

console.log(process.env.REACT_APP_TEST_VAR);

Will print out 123 in our console.

Note that, these variables are embedded during build 1me. Also, only the variables that start
with REACT_APP_ are embedded in our app. All the other environment variables are ignored.

Stage Environment Variable


For our purpose let’s use an environment variable called REACT_APP_STAGE . This variable
will take the values dev and prod . And by default it is set to dev . Now we can rewrite
our config with this.

Replace src/config.js with this.

const dev = {
s3: {
REGION: "YOUR_DEV_S3_UPLOADS_BUCKET_REGION",
BUCKET: "YOUR_DEV_S3_UPLOADS_BUCKET_NAME"
},
apiGateway: {
REGION: "YOUR_DEV_API_GATEWAY_REGION",
URL: "YOUR_DEV_API_GATEWAY_URL"
},
cognito: {
REGION: "YOUR_DEV_COGNITO_REGION",
USER_POOL_ID: "YOUR_DEV_COGNITO_USER_POOL_ID",
APP_CLIENT_ID: "YOUR_DEV_COGNITO_APP_CLIENT_ID",
IDENTITY_POOL_ID: "YOUR_DEV_IDENTITY_POOL_ID"
}
};

const prod = {
s3: {
REGION: "YOUR_PROD_S3_UPLOADS_BUCKET_REGION",
BUCKET: "YOUR_PROD_S3_UPLOADS_BUCKET_NAME"
},
apiGateway: {
REGION: "YOUR_PROD_API_GATEWAY_REGION",
URL: "YOUR_PROD_API_GATEWAY_URL"
},
cognito: {
REGION: "YOUR_PROD_COGNITO_REGION",
USER_POOL_ID: "YOUR_PROD_COGNITO_USER_POOL_ID",
APP_CLIENT_ID: "YOUR_PROD_COGNITO_APP_CLIENT_ID",
IDENTITY_POOL_ID: "YOUR_PROD_IDENTITY_POOL_ID"
}
};

// Default to dev if not set


const config = process.env.REACT_APP_STAGE === 'prod'
? prod
: dev;

export default {
// Add common config values here
MAX_ATTACHMENT_SIZE: 5000000,
...config
};

Make sure to replace the different version of the resources with the ones from the Deploying
through Seed (/chapters/deploying-through-seed.html) chapter.

Note that we are defaul1ng our environment to dev if the REACT_APP_STAGE is not set. This
means that our current build process ( npm start and npm run build ) will default to the
dev environment. And for config values like MAX_ATTACHMENT_SIZE that are common to
both environments we moved it in a different sec1on.

If we switch over to our app, we should see it in development mode and it’ll be connected to
the dev version of our backend. We haven’t changed the deployment process yet but in the
coming chapters we’ll change this when we automate our frontend deployments.

We don’t need to worry about the prod version just yet. But as an example, if we wanted to
build the prod version of our app we’d have to run the following:

$ REACT_APP_STAGE=prod npm run build

OR for Windows

set "REACT_APP_STAGE=prod" && npm start

Commit the Changes


Let’s quickly commit these to Git.

$ git add .
$ git commit -m "Configuring environments"

Next, let’s add a seRngs page to our app. This is where a user will be able to pay for our
service!

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/manage-
environments-in-create-react-app/182)
Create a Settings Page
We are going to add a se-ngs page to our app. This is going to allow users to pay for our
service. The flow will look something like this:

1. Users put in their credit card info and the number of notes they want to store.
2. We call Stripe on the frontend to generate a token for the credit card.
3. We then call our billing API with the token and the number of notes.
4. Our billing API calculates the amount and bills the card!

To get started let’s add our se-ngs page.

Create a new file in src/containers/Settings.js and add the following.

import React, { Component } from "react";


import { API } from "aws-amplify";

export default class Settings extends Component {


constructor(props) {
super(props);

this.state = {
isLoading: false
};
}

billUser(details) {
return API.post("notes", "/billing", {
body: details
});
}

render() {
return (
<div className="Settings">
</div>
);
}
}

Next import this component in the header of src/Routes.js .

import Settings from "./containers/Settings";

And replace our <Switch> block in src/Routes.js with this.

<Switch>
<AppliedRoute path="/" exact component={Home} props={childProps} />
<UnauthenticatedRoute path="/login" exact component={Login} props=
{childProps} />
<UnauthenticatedRoute path="/signup" exact component={Signup} props=
{childProps} />
<AuthenticatedRoute path="/settings" exact component={Settings}
props={childProps} />
<AuthenticatedRoute path="/notes/new" exact component={NewNote}
props={childProps} />
<AuthenticatedRoute path="/notes/:id" exact component={Notes} props=
{childProps} />
{ /* Finally, catch all unmatched routes */ }
<Route component={NotFound} />
</Switch>

NoNce that we added a route for our new se-ngs page.

Next add a link to our se-ngs page in the navbar by replacing the render
method in src/App.js with this.

render() {
const childProps = {
isAuthenticated: this.state.isAuthenticated,
userHasAuthenticated: this.userHasAuthenticated
};
return (
!this.state.isAuthenticating &&
<div className="App container">
<Navbar fluid collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Scratch</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
{this.state.isAuthenticated
? <Fragment>
<LinkContainer to="/settings">
<NavItem>Settings</NavItem>
</LinkContainer>
<NavItem onClick=
{this.handleLogout}>Logout</NavItem>
</Fragment>
: <Fragment>
<LinkContainer to="/signup">
<NavItem>Signup</NavItem>
</LinkContainer>
<LinkContainer to="/login">
<NavItem>Login</NavItem>
</LinkContainer>
</Fragment>
}
</Nav>
</Navbar.Collapse>
</Navbar>
<Routes childProps={childProps} />
</div>
);
}
You’ll noNce that we added another link in the navbar for the case a user is logged in.

Now if you head over to your app, you’ll see a new Se#ngs link at the top. Of course, the
page is preQy empty right now.

Commit the Changes


Let’s quickly commit these to Git.

$ git add .
$ git commit -m "Adding settings page"

Next, we’ll add our Stripe SDK keys to our config.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
settings-page/184)
Add Stripe Keys to Config
Back in the Setup a Stripe account (/chapters/setup-a-stripe-account.html) chapter, we had
two keys in the Stripe console. The Secret key that we used in the backend and the
Publishable key. The Publishable key is meant to be used in the frontend.

We did not complete our Stripe account setup back then, so we don’t have the live version of
this key. For now we’ll just assume that we have two versions of the same key.

Add the following line in the dev block of src/config.js .

STRIPE_KEY: "YOUR_STRIPE_DEV_PUBLIC_KEY",

And this in the prod block of src/config.js .

STRIPE_KEY: "YOUR_STRIPE_PROD_PUBLIC_KEY",

Make sure to replace, YOUR_STRIPE_DEV_PUBLIC_KEY and


YOUR_STRIPE_PROD_PUBLIC_KEY with the Publishable key from the Setup a Stripe account
(/chapters/setup-a-stripe-account.html) chapter. For now they’ll be the same. Just make sure
to use the live version in the prod block when you configure your Stripe account completely.

Commit the Changes


Let’s quickly commit these to Git.

$ git add .
$ git commit -m "Adding Stripe keys to config"

Next, we’ll build our billing form.

For help and discussion


! Comments on this chapter
(https://discourse.serverless-stack.com/t/add-stripe-
keys-to-config/185)
Create a Billing Form
Now our se)ngs page is going to have a form that will take a user’s credit card details, get a
stripe token and call our billing API with it. Let’s start by adding the Stripe React SDK to our
project.

From our project root, run the following.

$ npm install --save react-stripe-elements

Next let’s create our billing form component.

Add the following to a new file in src/components/BillingForm.js .

import React, { Component } from "react";


import { FormGroup, FormControl, ControlLabel } from "react-
bootstrap";
import { CardElement, injectStripe } from "react-stripe-elements";
import LoaderButton from "./LoaderButton";
import "./BillingForm.css";

class BillingForm extends Component {


constructor(props) {
super(props);

this.state = {
name: "",
storage: "",
isProcessing: false,
isCardComplete: false
};
}

validateForm() {
return (
this.state.name !== "" &&
this.state.storage !== "" &&
this.state.isCardComplete
);
}

handleFieldChange = event => {


this.setState({
[event.target.id]: event.target.value
});
}

handleCardFieldChange = event => {


this.setState({
isCardComplete: event.complete
});
}

handleSubmitClick = async event => {


event.preventDefault();

const { name } = this.state;

this.setState({ isProcessing: true });

const { token, error } = await this.props.stripe.createToken({


name });

this.setState({ isProcessing: false });

this.props.onSubmit(this.state.storage, { token, error });


}

render() {
const loading = this.state.isProcessing || this.props.loading;

return (
<form className="BillingForm" onSubmit={this.handleSubmitClick}>
<FormGroup bsSize="large" controlId="storage">
<ControlLabel>Storage</ControlLabel>
<FormControl
min="0"
type="number"
value={this.state.storage}
onChange={this.handleFieldChange}
placeholder="Number of notes to store"
/>
</FormGroup>
<hr />
<FormGroup bsSize="large" controlId="name">
<ControlLabel>Cardholder&apos;s name</ControlLabel>
<FormControl
type="text"
value={this.state.name}
onChange={this.handleFieldChange}
placeholder="Name on the card"
/>
</FormGroup>
<ControlLabel>Credit Card Info</ControlLabel>
<CardElement
className="card-field"
onChange={this.handleCardFieldChange}
style={{
base: { fontSize: "18px", fontFamily: '"Open Sans", sans-
serif' }
}}
/>
<LoaderButton
block
bsSize="large"
type="submit"
text="Purchase"
isLoading={loading}
loadingText="Purchasing…"
disabled={!this.validateForm()}
/>
</form>
);
}
}

export default injectStripe(BillingForm);

Let’s quickly go over what we are doing here:

To begin with we are going to wrap our component with a Stripe module using the
injectStripe HOC. This gives our component access to the
this.props.stripe.createToken method.

As for the fields in our form, we have input field of type number that allows a user to
enter the number of notes they want to store. We also take the name on the credit card.
These are stored in the state through the this.handleFieldChange method.

The credit card number form is provided by the Stripe React SDK through the
CardElement component that we import in the header.

The submit buPon has a loading state that is set to true when we call Stripe to get a token
and when we call our billing API. However, since our Se)ngs container is calling the
billing API we use the this.props.loading to set the state of the buPon from the
Se)ngs container.

We also validate this form by checking if the name, the number of notes, and the card
details are complete. For the card details, we use the CardElement’s onChange method.

Finally, once the user completes and submits the form we make a call to Stripe by passing
in the credit card name and the credit card details (this is handled by the Stripe SDK). We
call the this.props.stripe.createToken method and in return we get the token or
an error back. We simply pass this and the number of notes to be stored to the se)ngs
page via the this.props.onSubmit method. We will be se)ng this up shortly.

You can read more about how to use the React Stripe Elements here
(hPps://github.com/stripe/react-stripe-elements).

Also, let’s add some styles to the card field so it matches the rest of our UI.
Create a file at src/components/BillingForm.css .

.BillingForm .card-field {
margin-bottom: 15px;
background-color: white;
padding: 11px 16px;
border-radius: 6px;
border: 1px solid #CCC;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
line-height: 1.3333333;
}

.BillingForm .card-field.StripeElement--focus {
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102,
175, 233, .6);
border-color: #66AFE9;
}

Commit the Changes


Let’s quickly commit these to Git.

$ git add .
$ git commit -m "Adding a billing form"

Next we’ll plug our form into the se)ngs page.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
billing-form/186)
Connect the Billing Form
Now all we have le* to do is to connect our billing form to our billing API.

Let’s start by including Stripe.js in our HTML.

Append the following the the <head> block in our public/index.html .

<script src="https://js.stripe.com/v3/"></script>

Replace our render method in src/containers/Settings.js with this.

handleFormSubmit = async (storage, { token, error }) => {


if (error) {
alert(error);
return;
}

this.setState({ isLoading: true });

try {
await this.billUser({
storage,
source: token.id
});

alert("Your card has been charged successfully!");


this.props.history.push("/");
} catch (e) {
alert(e);
this.setState({ isLoading: false });
}
}
render() {
return (
<div className="Settings">
<StripeProvider apiKey={config.STRIPE_KEY}>
<Elements>
<BillingForm
loading={this.state.isLoading}
onSubmit={this.handleFormSubmit}
/>
</Elements>
</StripeProvider>
</div>
);
}

And add the following to the header.

import { Elements, StripeProvider } from "react-stripe-elements";


import BillingForm from "../components/BillingForm";
import config from "../config";
import "./Settings.css";

We are adding the BillingForm component that we previously created here and passing in
the loading and onSubmit prop that we referenced in the last chapter. In the
handleFormSubmit method, we are checking if the Stripe method from the last chapter
returned an error. And if things looked okay then we call our billing API and redirect to the
home page a*er leHng the user know.

An important detail here is about the StripeProvider and the Elements component
that we are using. The StripeProvider component let’s the Stripe SDK know that we want
to call the Stripe methods using config.STRIPE_KEY . And it needs to wrap around at the
top level of our billing form. Similarly, the Elements component needs to wrap around any
component that is going to be using the CardElement Stripe component.

Finally, let’s handle some styles for our seHngs page as a whole.

Add the following to src/containers/Settings.css .


@media all and (min-width: 480px) {
.Settings {
padding: 60px 0;
}

.Settings form {
margin: 0 auto;
max-width: 480px;
}
}

This ensures that our form displays properly for larger screens.

And that’s it. We are ready to test our Stripe form. Head over to your browser and try picking
the number of notes you want to store and use the following for your card details:

A Stripe test card number is 4242 4242 4242 4242 .


You can use any valid expiry date, security code, and zip code.
And set any name.
You can read more about the Stripe test cards in the Stripe API Docs here
(hQps://stripe.com/docs/tesSng#cards).

If everything is set correctly, you should see the success message and you’ll be redirected to
the homepage.

Commit the Changes


Let’s quickly commit these to Git.

$ git add .
$ git commit -m "Connecting the billing form"

Next, we’ll set up automaSc deployments for our React app using a service called Netlify
(hQps://www.netlify.com). This will be fairly similar to what we did for our serverless backend
API.

For help and discussion


! Comments on this chapter
(https://discourse.serverless-stack.com/t/connect-the-
billing-form/187)
Automating React Deployments
If you’ve followed along with the first part of this guide, you’ll have no8ced that we deployed
our Create React App to S3 and used CloudFront as a CDN in front of it. Then we used Route
53 to configure our domain with it. We also had to configure the www version of our domain
and this needed another S3 and CloudFront distribu8on. This process can be a bit
cumbersome.

In the next few chapters we are going to be using a service called Netlify
(hJps://www.netlify.com) to automate our deployments. It’s a liJle like what we did for our
serverless API backend. We’ll configure it so that it’ll deploy our React app when we push our
changes to Git. However, there are a couple of subtle differences between the way we
configure our backend and frontend deployments.

1. Netlify hosts the React app on their infrastructure. In the case of our serverless API
backend, it was hosted on our AWS account.

2. Any changes that are pushed to our master branch will update the produc8on version
of our React app. This means that we’ll need to use a slightly different workflow than our
backend. We’ll use a separate branch where we will do most of our development and only
push to master once we are ready to update produc8on.

Just as in the case with our backend, we could use Travis CI (hJps://travis-ci.org) or Circle CI
(hJps://circleci.com) for this but it can take a bit more configura8on and we’ll cover that in a
different chapter.

So let’s get started with seXng up your project on Netlify.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/automating-
react-deployments/188)
Create a Build Script
Before we can add our project to Netlify (h6ps://www.netlify.com) we just need to set up a
build script. If you recall, we had configured our app to use the REACT_APP_STAGE build
environment variable. We are going to create a build script to tell Netlify to set this variable up
for the different deployment cases.

Add the Netlify Build Script


Start by adding the following to a file called netlify.toml to your project root.

# Global settings applied to the whole site.


# “base” is directory to change to before starting build, and
# “publish” is the directory to publish (relative to root of your
repo).
# “command” is your build command.

[build]
base = ""
publish = "build"
command = "REACT_APP_STAGE=dev npm run build"

# Production context: All deploys to the main


# repository branch will inherit these settings.
[context.production]
command = "REACT_APP_STAGE=prod npm run build"

# Deploy Preview context: All Deploy Previews


# will inherit these settings.
[context.deploy-preview]
command = "REACT_APP_STAGE=dev npm run build"

# Branch Deploy context: All deploys that are not in


# an active Deploy Preview will inherit these settings.
[context.branch-deploy]
command = "REACT_APP_STAGE=dev npm run build"

The build script is configured based on contexts. There is a default one right up top. There are
three parts to this:

1. The base is the directory where Netlify will run our build commands. In our case it is in
the project root. So this is leI empty.

2. The publish opKon points to where our build is generated. In the case of Create React
App it is the build directory in our project root.

3. The command opKon is the build command that Netlify will use. If you recall the Manage
environments in Create React App (/chapters/manage-environments-in-create-react-
app.html) chapter, this will seem familiar. In the default context the command is
REACT_APP_STAGE=dev npm run build .

The producKon context labelled, context.production is the only one where we set the
REACT_APP_STAGE variable to prod . This is when we push to master . The branch-
deploy is what we will be using when we push to any other non-producKon branch. The
deploy-preview is for pull requests.

Handle HTTP Status Codes


Just as the first part of the tutorial, we’ll need to handle requests to any non-root paths of our
app. Our frontend is a single-page app and the rouKng is handled on the client side. We need
to tell Netlify to always redirect any request to our index.html and return the 200 status
code for it.

To do this, add a redirects rule at the bo6om of netlify.toml :

# Always redirect any request to our index.html


# and return the status code 200.
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Modify the Build Command
To deploy our app to Netlify we need to modify the build commands in our package.json .

Replace the scripts block in your package.json with this.

"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}

You’ll noKce we are geYng rid of our old build and deploy scripts. We are not going to be
deploying to S3.

Commit the Changes


Let’s quickly commit these to Git.

$ git add .
$ git commit -m "Adding a Netlify build script"

Push the Changes


We are pre6y much done making changes to our project. So let’s go ahead and
push them to GitHub.

$ git push

Now we are ready to add our project to Netlify.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/create-a-
build-script/189)

For reference, here is the code for Part II

" Frontend Part II Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-client)
Setting up Your Project on Netlify
Now we are going to set our React app on Netlify (h6ps://www.netlify.com). Start by crea>ng
a free account (h6ps://app.netlify.com/signup).

Next, create a new site by hiAng the New site from Git bu6on.
Pick GitHub as your provider.
Then pick your project from the list.

It’ll default the branch to master . We can now deploy our app! Hit Deploy site.
This should be deploying our app. Once it is done, click on the deployment.
And you should see your app in ac>on!

Of course, it is hosted on a Netlify URL. We’ll change that by configuring custom domains
next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/setting-up-
your-project-on-netlify/190)
Custom Domains in Netlify
Now that we have our first deployment, let’s configure a custom domain for our app through
Netlify.

A Note to Readers from Part I

The following sec<on is assuming that you completed Part I (/#part-1) independently and that
the custom domains below are being set up from scratch. However, if you’ve just completed
Part I, then you have a couple of op<ons:

1. Configure a new custom domain

Let’s say your domain from Part I is something like https://notes-app.my-


domain.com . Then for the following sec<on you can configure something like
https://notes-app-2.my-domain.com . This is the preferred op<on since it does not
interfere with what you had configured previously. This is what we do for the tutorial
demo app as well. You can see the Part I version here (hMps://demo.serverless-stack.com)
and the Part II version here (hMps://demo2.serverless-stack.com). The downside is that
you’ll have two different versions of the frontend React app.

2. Replace the old domain

You might be working through this guide to build an app as opposed to learning how to
build one. If that’s the case, it doesn’t make sense that you have two versions of the
frontend floa<ng around. You’ll need to disconnect the domain from Part I. To do that,
remove the Route 53 records sets that we created for the apex domain (/chapters/setup-
your-domain-with-cloudfront.html#point-domain-to-cloudfront-distribu<on) and the
www domain (/chapters/setup-www-domain-redirect.html#point-www-domain-to-
cloudfront-distribu<on) in Part I.

If you are not sure about the two above op<ons or have any ques<ons, post a comment in the
discussion thread that we link to at the boMom of the chapter.
Let’s get started!

Pick a Netlify Site Name


From the project page in Netlify, hit Site se2ngs.

Under Site informa5on hit Change site name.


The site names are global, so pick a unique one. In our case we are using serverless-
stack-2-client . And hit Save.
This means that our Netlify site URL is now going to be https://serverless-stack-2-
client.netlify.com . Make a note of this as we will use this later in this chapter.

Domain Settings in Netlify


Next hit Domain management from the side panel.
And hit Add custom domain.
Type in the name of our domain, for example it might be demo-serverless-stack.com .
And hit Save.

This will ask you to verify that you are the owner of this domain and to add it. Click Yes, add
domain.
Next hit Check DNS configura5on.
This will show you the instruc<ons for se[ng up your domain through Route 53.

DNS Settings in Route 53


To do this we need to head back to the AWS Console (hMps://console.aws.amazon.com/). and
search for Route 53 as the service.
Click on Hosted zones.
And select the domain we want to configure.

Here click on Create Record Set.


Select Type as A - IPv4 address and set the Value to 104.198.14.52. And hit Create. We get
this IP from the Netlify docs on adding custom domains
(hMps://www.netlify.com/docs/custom-domains/).
Next hit Create Record Set again.

Set Name to www , Type to CNAME - Canonical name, and the value to the Netlify site name
as we noted above. In our case it is https://serverless-stack-2-
client.netlify.com . Hit Create.
And give the DNS around 30 minutes to update.

Configure SSL
Back in Netlify, hit HTTPS in the side panel. And it should say that it is wai<ng for the DNS to
propagate.
Once that is complete, Netlify will automa<cally provision your SSL ceri<ficate using Let’s
Encrypt.
Wait a few seconds for the ceri<ficate to be provisioned.
Now if you head over to your browser and go to your custom domain, your notes app should
be up and running!

We have our app in produc<on but we haven’t had a chance to go through our workflow just
yet. Let’s take a look at that next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/custom-
domains-in-netlify/191)
Frontend Workflow
Now that we have our frontend deployed and configured, let’s go over what our development
workflow will look like.

Working in a Dev Branch


A good prac>se is to create a branch when we are working on something new.

Run the following in the root of your project.

$ git checkout -b "new-feature"

This creates a new branch for us called new-feature .

Let’s make a faulty commit just so we can go over the process of rolling back as well.

Replace the renderLander method in src/containers/Home.js with the


following.

renderLander() {
return (
<div className="lander">
<h1>Scratch</h1>
<p>A very expensive note taking app</p>
<div>
<Link to="/login" className="btn btn-info btn-lg">
Login
</Link>
<Link to="/signup" className="btn btn-success btn-lg">
Signup
</Link>
</div>
</div>
);
}

And commit this change to Git.

$ git add .
$ git commit -m "Committing a typo"

Create a Branch Deployment


To be able to preview this change in its own environment we need to turn on branch
deployments in Netlify. From the Site se'ngs sidebar select Build & deploy.

And hit Edit se'ngs.


Set Branch deploys to All and hit Save.
Now comes the fun part, we can deploy this to dev so we can test it right away. All
we need to do is push it to Git.

$ git push -u origin new-feature

Now if you hop over to your Netlify project page; you’ll see a new branch deploy in ac>on.
Wait for it to complete and click on it.

Hit Preview deploy.


And you can see a new version of your app in ac>on!
You can test around this version of our frontend app. It is connected to the dev version of our
backend API. The idea is that we can test and play around with the changes here without
affec>ng our produc>on users.

Push to Production
Now if we feel happy with the changes we can push this to produc>on just by
merging to master.

$ git checkout master


$ git merge new-feature
$ git push

You should see this deployment in ac>on in Netlify.

And once it is done, your changes should be live!


Rolling Back in Production
Now for some reason if we aren’t happy with the build in produc>on, we can rollback.

Click on the older produc>on deployment.


And hit Publish deploy.
This will publish our previous version again.

And that’s it! Now you have an automated workflow for building and deploying your Create
React App with serverless.

Cleanup
Let’s quickly cleanup our changes.

Replace the renderLander method in src/containers/Home.js with the


original.

renderLander() {
return (
<div className="lander">
<h1>Scratch</h1>
<p>A simple note taking app</p>
<div>
<Link to="/login" className="btn btn-info btn-lg">
Login
</Link>
<Link to="/signup" className="btn btn-success btn-lg">
Signup
</Link>
</div>
</div>
);
}

Commit these changes and push them by running the following.

$ git add .
$ git commit -m "Fixing a typo"
$ git push

This will create a new deployment to live! Let’s wrap up the guide next.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/frontend-
workflow/192)

For reference, here is the code for Part II

" Frontend Part II Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-client)
Wrapping Up
Congratula*ons on comple*ng the guide!

We’ve covered how to build and deploy our backend serverless API and our frontend
serverless app. And not only does it work well on the desktop.

It’s mobile op*mized as well!


The en*re workflow and the ideas covered in this guide are in produc*on at a number of
companies. We hope what you’ve learned here can be adapted to fit the use case you have in
mind. We are going to be covering a few other topics in the future while we keep this guide up
to date.

We’d love to hear from you about your experience following this guide. Please send us any
comments or feedback you might have, via email (mailto:[email protected]). We’d love to
feature your comments here. Also, if you’d like us to cover any of the chapters or concepts in a
bit more detail, feel free to let us know (mailto:[email protected]).

Thank you and we hope you found this guide helpful!

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
wrapping-up/100)
Further Reading
Once you’ve completed the guide, you are probably going to use the Serverless Stack for your
next project. To help you along the way we try to compile a list of docs that you can use as a
reference. The following can be used to drill down in detail for some of the technologies and
services used in this guide.

Serverless Framework DocumentaBon (hDps://serverless.com/framework/docs/):


DocumentaBon for the Serverless Framework

DynamoDB, explained (hDps://www.dynamodbguide.com): A Primer on the DynamoDB


NoSQL database

React JS Docs (hDps://reactjs.org/docs/hello-world.html): The official React docs

JSX In Depth (hDps://reactjs.org/docs/jsx-in-depth.html): Learn JSX in a bit more detail

Create React App User Guide (hDps://github.com/facebook/create-react-


app/blob/master/packages/react-scripts/template/README.md): The really
comprehensive Create React App user guide

React-Bootstrap Docs (hDps://react-bootstrap.github.io/geYng-started/introducBon):


The official React-Bootstrap docs

Bootstrap v3 Docs (hDp://getbootstrap.com/docs/3.3/geYng-started/): The Bootstrap v3


docs that React-Bootstrap is based on

React Router Docs (hDps://reacDraining.com/react-router/web/guides/philosophy): The


official React Router v4 docs

AWS Amplify Developer Guide (hDps://aws.github.io/aws-


amplify/media/developer_guide): The AWS Amplify developer guide

AWS Amplify API Reference (hDps://aws.github.io/aws-amplify/api/): The AWS Amplify


API reference

AWS CloudFormaBon Docs


(hDps://docs.aws.amazon.com/AWSCloudFormaBon/latest/UserGuide/GeYngStarted.Wa
lkthrough.html): The AWS user guide for CloudFormaBon

Jest Unit Test Docs (hDps://facebook.github.io/jest/docs/en/geYng-started.html): The


official Jest docs

Seed Docs (hDps://seed.run/docs/): The official Seed docs

Netlify Docs (hDps://www.netlify.com/docs/): The official Netlify docs

If you have found any other guides or tutorials helpful in building your serverless app, feel free
to edit this page and submit a PR. Or you can let us know via the comments.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/further-
reading/209)
Translations
Our guide is available in several languages thanks to contribu4ons by our incredible readers.
You can view the translated versions of a chapter by clicking on the links below the chapter
4tle.

Below is a list of all the chapters that are available in mul4ple languages. If you are interested
in helping with our transla4on efforts, leave us a comment here (hAps://discourse.serverless-
stack.com/t/help-us-translate-serverless-stack/596/15).

Who Is This Guide For? (/chapters/who-is-this-guide-for.html)


es: ¿Para quién es esta guía? (/chapters/es/who-is-this-guide-for.html)
fr: Pour qui est ce guide? (/chapters/fr/who-is-this-guide-for.html)
ko: Who Is This Guide For? (/chapters/ko/who-is-this-guide-for.html)
pt: Para quem é este guia? (/chapters/pt/who-is-this-guide-for.html)

What Does This Guide Cover? (/chapters/what-does-this-guide-cover.html)


es: ¿Qué cubre esta guía? (/chapters/es/what-does-this-guide-cover.html)
fr: Que couvre ce guide? (/chapters/fr/what-does-this-guide-cover.html)
ko: What Does This Guide Cover? (/chapters/ko/what-does-this-guide-cover.html)
pt: O que esse guia cobre? (/chapters/pt/what-does-this-guide-cover.html)

How to Get Help? (/chapters/how-to-get-help.html)


es: ¿Cómo conseguir ayuda? (/chapters/es/how-to-get-help.html)
fr: Comment obtenir de l'aide? (/chapters/fr/how-to-get-help.html)
ko: How to Get Help? (/chapters/ko/how-to-get-help.html)
pt: Como conseguir ajuda? (/chapters/pt/how-to-get-help.html)

What is Serverless? (/chapters/what-is-serverless.html)


de: Was ist Serverless? (/chapters/de/what-is-serverless.html)
es: ¿Qué es Serverless? (/chapters/es/what-is-serverless.html)
fr: Serverless, c'est quoi ? (/chapters/fr/what-is-serverless.html)
id: Apa itu Serverless? (/chapters/id/what-is-serverless.html)
ko: What is Serverless? (/chapters/ko/what-is-serverless.html)
pt: O que é Serverless? (/chapters/pt/what-is-serverless.html)

What is AWS Lambda? (/chapters/what-is-aws-lambda.html)


es: ¿Qué es AWS Lambda? (/chapters/es/what-is-aws-lambda.html)
fr: Qu'est-ce qu'AWS Lambda ? (/chapters/fr/what-is-aws-lambda.html)
ko: What is AWS Lambda? (/chapters/ko/what-is-aws-lambda.html)
pt: O que é AWS Lambda? (/chapters/pt/what-is-aws-lambda.html)

Why Create Serverless Apps? (/chapters/why-create-serverless-apps.html)


es: ¿Por qué crear aplicaciones serverless? (/chapters/es/why-create-serverless-
apps.html)
ko: Why Create Serverless Apps? (/chapters/ko/why-create-serverless-apps.html)
pt: Por que criar aplicações Serverless? (/chapters/pt/why-create-serverless-apps.html)

Create an AWS Account (/chapters/create-an-aws-account.html)


es: Crear una cuenta de AWS (/chapters/es/create-an-aws-account.html)
ko: Create an AWS Account (/chapters/ko/create-an-aws-account.html)
pt: Crie uma conta na AWS (/chapters/pt/create-an-aws-account.html)
Create an IAM User (/chapters/create-an-iam-user.html)
es: Crear un usuario de IAM (/chapters/es/create-an-iam-user.html)
ko: Create an IAM User (/chapters/ko/create-an-iam-user.html)

What is IAM (/chapters/what-is-iam.html)


es: ¿Qué es IAM? (/chapters/es/what-is-iam.html)
ko: What is IAM (/chapters/ko/what-is-iam.html)

What is an ARN (/chapters/what-is-an-arn.html)


es: ¿Qué es un ARN? (/chapters/es/what-is-an-arn.html)
ko: What is an ARN (/chapters/ko/what-is-an-arn.html)

Configure the AWS CLI (/chapters/configure-the-aws-cli.html)


es: Configurar AWS CLI (/chapters/es/configure-the-aws-cli.html)
ko: Configure the AWS CLI (/chapters/ko/configure-the-aws-cli.html)

Create a DynamoDB Table (/chapters/create-a-dynamodb-table.html)


es: Crear una tabla de DynamoDB (/chapters/es/create-a-dynamodb-table.html)
ko: Create a DynamoDB Table (/chapters/ko/create-a-dynamodb-table.html)

Create an S3 Bucket for File Uploads (/chapters/create-an-s3-bucket-for-file-uploads.html)


ko: Create an S3 Bucket for File Uploads (/chapters/ko/create-an-s3-bucket-for-file-
uploads.html)

Create a Cognito User Pool (/chapters/create-a-cognito-user-pool.html)


ko: Create a Cognito User Pool (/chapters/ko/create-a-cognito-user-pool.html)

Create a Cognito Test User (/chapters/create-a-cognito-test-user.html)


ko: Create a Cognito Test User (/chapters/ko/create-a-cognito-test-user.html)

Set up the Serverless Framework (/chapters/setup-the-serverless-framework.html)


ko: Set up the Serverless Framework (/chapters/ko/setup-the-serverless-framework.html)

Add Support for ES6/ES7 JavaScript (/chapters/add-support-for-es6-es7-javascript.html)


ko: Add Support for ES6/ES7 JavaScript (/chapters/ko/add-support-for-es6-es7-
javascript.html)

Add a Create Note API (/chapters/add-a-create-note-api.html)


ko: Add a Create Note API (/chapters/ko/add-a-create-note-api.html)

Add a Get Note API (/chapters/add-a-get-note-api.html)


ko: Add a Get Note API (/chapters/ko/add-a-get-note-api.html)

Add a List All the Notes API (/chapters/add-a-list-all-the-notes-api.html)


ko: Add a List All the Notes API (/chapters/ko/add-a-list-all-the-notes-api.html)

Add an Update Note API (/chapters/add-an-update-note-api.html)


ko: Add an Update Note API (/chapters/ko/add-an-update-note-api.html)

Add a Delete Note API (/chapters/add-a-delete-note-api.html)


ko: Add a Delete Note API (/chapters/ko/add-a-delete-note-api.html)

Handle API Gateway CORS Errors (/chapters/handle-api-gateway-cors-errors.html)


ko: Handle API Gateway CORS Errors (/chapters/ko/handle-api-gateway-cors-errors.html)

Deploy the APIs (/chapters/deploy-the-apis.html)


ko: Deploy the APIs (/chapters/ko/deploy-the-apis.html)

Create a Cognito Iden4ty Pool (/chapters/create-a-cognito-iden4ty-pool.html)


ko: Create a Cognito Iden4ty Pool (/chapters/ko/create-a-cognito-iden4ty-pool.html)

Cognito User Pool vs Iden4ty Pool (/chapters/cognito-user-pool-vs-iden4ty-pool.html)


ko: Cognito User Pool vs Iden4ty Pool (/chapters/ko/cognito-user-pool-vs-iden4ty-
pool.html)

Test the APIs (/chapters/test-the-apis.html)


ko: Test the APIs (/chapters/ko/test-the-apis.html)

Create a New React.js App (/chapters/create-a-new-reactjs-app.html)


ko: Create a New React.js App (/chapters/ko/create-a-new-reactjs-app.html)

Add App Favicons (/chapters/add-app-favicons.html)


ko: Add App Favicons (/chapters/ko/add-app-favicons.html)

Set up Custom Fonts (/chapters/setup-custom-fonts.html)


ko: Set up Custom Fonts (/chapters/ko/setup-custom-fonts.html)

Set up Bootstrap (/chapters/setup-bootstrap.html)


ko: Set up Bootstrap (/chapters/ko/setup-bootstrap.html)

Handle Routes with React Router (/chapters/handle-routes-with-react-router.html)


ko: Handle Routes with React Router (/chapters/ko/handle-routes-with-react-router.html)
Create Containers (/chapters/create-containers.html)
ko: Create Containers (/chapters/ko/create-containers.html)

Adding Links in the Navbar (/chapters/adding-links-in-the-navbar.html)


ko: Adding Links in the Navbar (/chapters/ko/adding-links-in-the-navbar.html)

Handle 404s (/chapters/handle-404s.html)


ko: Handle 404s (/chapters/ko/handle-404s.html)

Configure AWS Amplify (/chapters/configure-aws-amplify.html)


ko: Configure AWS Amplify (/chapters/ko/configure-aws-amplify.html)

Login with AWS Cognito (/chapters/login-with-aws-cognito.html)


ko: Login with AWS Cognito (/chapters/ko/login-with-aws-cognito.html)

Load the State from the Session (/chapters/load-the-state-from-the-session.html)


ko: Load the State from the Session (/chapters/ko/load-the-state-from-the-session.html)

Clear the Session on Logout (/chapters/clear-the-session-on-logout.html)


ko: Clear the Session on Logout (/chapters/ko/clear-the-session-on-logout.html)

Redirect on Login and Logout (/chapters/redirect-on-login-and-logout.html)


ko: Redirect on Login and Logout (/chapters/ko/redirect-on-login-and-logout.html)

Give Feedback While Logging In (/chapters/give-feedback-while-logging-in.html)


ko: Give Feedback While Logging In (/chapters/ko/give-feedback-while-logging-in.html)

Create a Signup Page (/chapters/create-a-signup-page.html)


ko: Create a Signup Page (/chapters/ko/create-a-signup-page.html)

Create the Signup Form (/chapters/create-the-signup-form.html)


ko: Create the Signup Form (/chapters/ko/create-the-signup-form.html)

Signup with AWS Cognito (/chapters/signup-with-aws-cognito.html)


ko: Signup with AWS Cognito (/chapters/ko/signup-with-aws-cognito.html)

Add the Create Note Page (/chapters/add-the-create-note-page.html)


ko: Add the Create Note Page (/chapters/ko/add-the-create-note-page.html)

Call the Create API (/chapters/call-the-create-api.html)


ko: Call the Create API (/chapters/ko/call-the-create-api.html)
Upload a File to S3 (/chapters/upload-a-file-to-s3.html)
ko: Upload a File to S3 (/chapters/ko/upload-a-file-to-s3.html)

List All the Notes (/chapters/list-all-the-notes.html)


ko: List All the Notes (/chapters/ko/list-all-the-notes.html)

Call the List API (/chapters/call-the-list-api.html)


ko: Call the List API (/chapters/ko/call-the-list-api.html)

Display a Note (/chapters/display-a-note.html)


ko: Display a Note (/chapters/ko/display-a-note.html)

Render the Note Form (/chapters/render-the-note-form.html)


ko: Render the Note Form (/chapters/ko/render-the-note-form.html)

Save Changes to a Note (/chapters/save-changes-to-a-note.html)


ko: Save Changes to a Note (/chapters/ko/save-changes-to-a-note.html)

Delete a Note (/chapters/delete-a-note.html)


ko: Delete a Note (/chapters/ko/delete-a-note.html)

Set up Secure Pages (/chapters/setup-secure-pages.html)


ko: Set up Secure Pages (/chapters/ko/setup-secure-pages.html)

Create a Route That Redirects (/chapters/create-a-route-that-redirects.html)


ko: Create a Route That Redirects (/chapters/ko/create-a-route-that-redirects.html)

Use the Redirect Routes (/chapters/use-the-redirect-routes.html)


ko: Use the Redirect Routes (/chapters/ko/use-the-redirect-routes.html)

Redirect on Login (/chapters/redirect-on-login.html)


ko: Redirect on Login (/chapters/ko/redirect-on-login.html)

Deploy the Frontend (/chapters/deploy-the-frontend.html)


ko: Deploy the Frontend (/chapters/ko/deploy-the-frontend.html)

Create an S3 Bucket (/chapters/create-an-s3-bucket.html)


ko: Create an S3 Bucket (/chapters/ko/create-an-s3-bucket.html)

Deploy to S3 (/chapters/deploy-to-s3.html)
ko: Deploy to S3 (/chapters/ko/deploy-to-s3.html)
Create a CloudFront Distribu4on (/chapters/create-a-cloudfront-distribu4on.html)
ko: Create a CloudFront Distribu4on (/chapters/ko/create-a-cloudfront-distribu4on.html)

Set up Your Domain with CloudFront (/chapters/setup-your-domain-with-cloudfront.html)


ko: Set up Your Domain with CloudFront (/chapters/ko/setup-your-domain-with-
cloudfront.html)

Set up WWW Domain Redirect (/chapters/setup-www-domain-redirect.html)


ko: Set up WWW Domain Redirect (/chapters/ko/setup-www-domain-redirect.html)

Set up SSL (/chapters/setup-ssl.html)


ko: Set up SSL (/chapters/ko/setup-ssl.html)

Deploy Updates (/chapters/deploy-updates.html)


ko: Deploy Updates (/chapters/ko/deploy-updates.html)

Update the App (/chapters/update-the-app.html)


ko: Update the App (/chapters/ko/update-the-app.html)

Deploy Again (/chapters/deploy-again.html)


ko: Deploy Again (/chapters/ko/deploy-again.html)

Gekng Produc4on Ready (/chapters/gekng-produc4on-ready.html)


ko: Gekng Produc4on Ready (/chapters/ko/gekng-produc4on-ready.html)

Ini4alize the Backend Repo (/chapters/ini4alize-the-backend-repo.html)


ko: Ini4alize the Backend Repo (/chapters/ko/ini4alize-the-backend-repo.html)

Organize the Backend Repo (/chapters/organize-the-backend-repo.html)


ko: Organize the Backend Repo (/chapters/ko/organize-the-backend-repo.html)

What Is Infrastructure as Code (/chapters/what-is-infrastructure-as-code.html)


ko: What Is Infrastructure as Code (/chapters/ko/what-is-infrastructure-as-code.html)

Configure DynamoDB in Serverless (/chapters/configure-dynamodb-in-serverless.html)


ko: Configure DynamoDB in Serverless (/chapters/ko/configure-dynamodb-in-
serverless.html)

Configure S3 in Serverless (/chapters/configure-s3-in-serverless.html)


ko: Configure S3 in Serverless (/chapters/ko/configure-s3-in-serverless.html)
Configure Cognito User Pool in Serverless (/chapters/configure-cognito-user-pool-in-
serverless.html)
ko: Configure Cognito User Pool in Serverless (/chapters/ko/configure-cognito-user-pool-
in-serverless.html)

Configure Cognito Iden4ty Pool in Serverless (/chapters/configure-cognito-iden4ty-pool-in-


serverless.html)
ko: Configure Cognito Iden4ty Pool in Serverless (/chapters/ko/configure-cognito-
iden4ty-pool-in-serverless.html)

Use Environment Variables in Lambda Func4ons (/chapters/use-environment-variables-in-


lambda-func4ons.html)
ko: Use Environment Variables in Lambda Func4ons (/chapters/ko/use-environment-
variables-in-lambda-func4ons.html)

Deploy Your Serverless Infrastructure (/chapters/deploy-your-serverless-infrastructure.html)


ko: Deploy Your Serverless Infrastructure (/chapters/ko/deploy-your-serverless-
infrastructure.html)

Working with 3rd Party APIs (/chapters/working-with-3rd-party-apis.html)


ko: Working with 3rd Party APIs (/chapters/ko/working-with-3rd-party-apis.html)

Setup a Stripe Account (/chapters/setup-a-stripe-account.html)


ko: Setup a Stripe Account (/chapters/ko/setup-a-stripe-account.html)

Add a Billing API (/chapters/add-a-billing-api.html)


ko: Add a Billing API (/chapters/ko/add-a-billing-api.html)

Load Secrets from env.yml (/chapters/load-secrets-from-env-yml.html)


ko: Load Secrets from env.yml (/chapters/ko/load-secrets-from-env-yml.html)

Test the Billing API (/chapters/test-the-billing-api.html)


ko: Test the Billing API (/chapters/ko/test-the-billing-api.html)

Unit Tests in Serverless (/chapters/unit-tests-in-serverless.html)


ko: Unit Tests in Serverless (/chapters/ko/unit-tests-in-serverless.html)

Automa4ng Serverless Deployments (/chapters/automa4ng-serverless-deployments.html)


ko: Automa4ng Serverless Deployments (/chapters/ko/automa4ng-serverless-
deployments.html)
Sekng up Your Project on Seed (/chapters/sekng-up-your-project-on-seed.html)
ko: Sekng up Your Project on Seed (/chapters/ko/sekng-up-your-project-on-seed.html)

Configure Secrets in Seed (/chapters/configure-secrets-in-seed.html)


ko: Configure Secrets in Seed (/chapters/ko/configure-secrets-in-seed.html)

Deploying Through Seed (/chapters/deploying-through-seed.html)


ko: Deploying Through Seed (/chapters/ko/deploying-through-seed.html)

Set Custom Domains Through Seed (/chapters/set-custom-domains-through-seed.html)


ko: Set Custom Domains Through Seed (/chapters/ko/set-custom-domains-through-
seed.html)

Test the Configured APIs (/chapters/test-the-configured-apis.html)


ko: Test the Configured APIs (/chapters/ko/test-the-configured-apis.html)

Monitoring Deployments in Seed (/chapters/monitoring-deployments-in-seed.html)


ko: Monitoring Deployments in Seed (/chapters/ko/monitoring-deployments-in-
seed.html)

Ini4alize the Frontend Repo (/chapters/ini4alize-the-frontend-repo.html)


ko: Ini4alize the Frontend Repo (/chapters/ko/ini4alize-the-frontend-repo.html)

Manage Environments in Create React App (/chapters/manage-environments-in-create-react-


app.html)
ko: Manage Environments in Create React App (/chapters/ko/manage-environments-in-
create-react-app.html)

Create a Sekngs Page (/chapters/create-a-sekngs-page.html)


ko: Create a Sekngs Page (/chapters/ko/create-a-sekngs-page.html)

Add Stripe Keys to Config (/chapters/add-stripe-keys-to-config.html)


ko: Add Stripe Keys to Config (/chapters/ko/add-stripe-keys-to-config.html)

Create a Billing Form (/chapters/create-a-billing-form.html)


ko: Create a Billing Form (/chapters/ko/create-a-billing-form.html)

Connect the Billing Form (/chapters/connect-the-billing-form.html)


ko: Connect the Billing Form (/chapters/ko/connect-the-billing-form.html)

Automa4ng React Deployments (/chapters/automa4ng-react-deployments.html)


ko: Automa4ng React Deployments (/chapters/ko/automa4ng-react-deployments.html)

Create a Build Script (/chapters/create-a-build-script.html)


ko: Create a Build Script (/chapters/ko/create-a-build-script.html)

Sekng up Your Project on Netlify (/chapters/sekng-up-your-project-on-netlify.html)


ko: Sekng up Your Project on Netlify (/chapters/ko/sekng-up-your-project-on-
netlify.html)

Custom Domains in Netlify (/chapters/custom-domain-in-netlify.html)


ko: Netlify에서 사용자 정의 도메인 설정 (/chapters/ko/custom-domain-in-netlify.html)

Frontend Workflow (/chapters/frontend-workflow.html)


ko: 프론트엔드 작업흐름 (/chapters/ko/frontend-workflow.html)

Wrapping Up (/chapters/wrapping-up.html)
ko: 마무리하며 (/chapters/ko/wrapping-up.html)

Further Reading (/chapters/further-reading.html)


ko: 더 읽어볼 것들 (/chapters/ko/further-reading.html)

Transla4ons (/chapters/transla4ons.html)
ko: 번역 (/chapters/ko/transla4ons.html)

Giving Back (/chapters/giving-back.html)


ko: Giving Back (/chapters/ko/giving-back.html)

Changelog (/chapters/changelog.html)
ko: 변경로그 (/chapters/ko/changelog.html)

Staying up to date (/chapters/staying-up-to-date.html)


ko: 최신 상태 유지 (/chapters/ko/staying-up-to-date.html)

A big thanks to our contributors for helping make Serverless Stack more accessible!

Bernardo Bugmann (hAps://github.com/bernardobugmann)


Sebas4an Gu4errez (hAps://github.com/pepas24)
Vincent Oliveira (hAps://github.com/vincentoliveira)
Leonardo Gonzalez (hAps://github.com/leogonzalez)
Vieko Franetovic (hAps://github.com/vieko)
Chris4an Kaindl (hAps://github.com/chris4ankaindl)
Jae Chul Kim (hAps://github.com/bsg-bob)

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-
translations/788)
Giving Back
If you’ve found this guide helpful please consider helping us out by doing the following. You
can also read about this in detail in our CONTRIBUTING.md
(hDps://github.com/AnomalyInnovaHons/serverless-stack-
com/blob/master/CONTRIBUTING.md).

Fixing typos and errors

The content on this site is kept up to date thanks in large part to our community and our
readers. Submit a Pull Request (hDps://github.com/AnomalyInnovaHons/serverless-stack-
com/compare) to fix any typos or errors you might find.

Helping others in the comments

If you’ve found yourself using the Discourse comments (hDps://discourse.serverless-


stack.com) to get help, please consider helping anybody else with issues that you might
have run into.

Keep the core guide updated

Serverless Stack is reliant on a large number of services and open source libraries and
projects. The screenshots for the services and the dependencies need to be updated
every once in a while. Here is a liDle more details on this
(hDps://github.com/AnomalyInnovaHons/serverless-stack-
com/blob/master/CONTRIBUTING.md#keep-the-core-guide-updated).

Help translate the guide

Our incredible readers are helping translate Serverless Stack into mulHple languages. You
can check out our progress here (/chapters/translaHons.html). If you would like to help
with our translaHon efforts, leave us a comment here (hDps://discourse.serverless-
stack.com/t/help-us-translate-serverless-stack/596/15).

Add an extra credit chapter


The core chapters are missing some extra details (for the sake of simplicity) that are
necessary once you start customizing the Serverless Stack setup. AddiHonally, there are
cases that we just don’t handle in the core part of the guide. We are addressing these via
Extra Credit chapters. If you have had a chance to extend Serverless Stack consider wriHng
a chapter on it. Here are further details on how to add an extra credit chapter
(hDps://github.com/AnomalyInnovaHons/serverless-stack-
com/blob/master/CONTRIBUTING.md#add-an-extra-credit-chapter).

Improve tooling

Currently we do a lot of manual work to publish updates and maintain the tutorial. You
can help by contribuHng to improve the process. Here are some more details on what we
need help with (hDps://github.com/AnomalyInnovaHons/serverless-stack-
com/blob/master/CONTRIBUTING.md#improve-tooling).

Give us a Star on GitHub (hDps://github.com/AnomalyInnovaHons/serverless-stack-com)

We rely on our GitHub repo for everything from hosHng this site to code samples and
comments. Starring our repo (hDps://github.com/AnomalyInnovaHons/serverless-stack-
com) helps us get the word out.

Sharing this guide

Share this guide via TwiDer (hDps://twiDer.com/intent/tweet?text=Serverless


Stack&url=hDps://serverless-stack.com) or Facebook
(hDps://www.facebook.com/sharer/sharer.php?u=hDps://serverless-
stack.com&p[Htle]=Serverless Stack) with others that might find this helpful.

Also, if you have any other ideas on how to contribute; feel free to let us know via email
(mailto:[email protected]).

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
giving-back/193)
Changelog
As we con)nue to update Serverless Stack, we want to make sure that we give you a clear
idea of all the changes that are being made. This is to ensure that you won’t have to go
through the en)re tutorial again to get caught up on the updates. We also want to leave the
older versions up in case you need a reference. This is also useful for readers who are working
through the tutorial while it gets updated.

Below are the updates we’ve made to Serverless Stack, each with:

Each update has a link to an archived version of the tutorial


Updates to the tutorial compared to the last version
Updates to the API and Client repos

While the hosted version of the tutorial and the code snippets are accurate, the sample
project repo that is linked at the boEom of each chapter is unfortunately not. We do however
maintain the past versions of the completed sample project repo. So you should be able to use
those to figure things out. All this info is also available on the releases page
(hEps://github.com/AnomalyInnova)ons/serverless-stack-com/releases) of our GitHub repo
(hEps://github.com/AnomalyInnova)ons/serverless-stack-com).

You can get these updates emailed to you via our newsleEer
(hEps://emailoctopus.com/lists/1c11b9a8-1500-11e8-a3c9-06b79b628af2/forms/subscribe).

Changes
v3.4: Updating to serverless-bundle and on-demand DynamoDB
(https://branchv34--serverless-stack.netlify.com/) (Current)

Jul 18, 2019: Upda)ng to serverless-bundle plugin and On-Demand Capacity for DynamoDB.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v3.3.3...v3.4)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v3.3.3...v3.4)

v3.3.3: Updating to serverless-bundle and on-demand DynamoDB


(https://branchv333--serverless-stack.netlify.com/)

Jan 27, 2019: Adding CORS headers to API Gateway 4xx and 5xx errors.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v3.3.2...v3.3.3)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v3.3.2...v3.3.3)

v3.3.2: Refactoring async Lambda functions (https://branchv332--


serverless-stack.netlify.com)

Nov 1, 2018: Refactoring async Lambda func)ons to return instead of using the callback.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v3.3.1...v3.3.2)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v3.1...v3.3.2)

v3.3.1: Updated to Create React App v2 (https://branchv331--serverless-


stack.netlify.com)

Oct 5, 2018: Updated the frontend React app to use Create React App v2.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v3.3...v3.3.1)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v3.1...v3.3.1)

v3.3: Added new chapters (https://branchv33--serverless-


stack.netlify.com)

Oct 5, 2018: Added new chapters on Facebook login with AWS Amplify and mapping Iden)ty
Id with User Pool Id. Also, added a new series of chapters on forgot password, change email
and password.
Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-
com/compare/v3.2...v3.3)
Facebook Login Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
c-login-client)
User Management Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
user-mgmt-client)

v3.2: Added section on Serverless architecture (https://branchv32--


serverless-stack.netlify.com)

Aug 18, 2018: Adding a new sec)on on organizing Serverless applica)ons. Outlining how to
use CloudForma)on cross-stack references to link mul)ple Serverless services.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v3.1...v3.2)
Mono-repo API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-mono-
api)

v3.1: Update to use UsernameAttributes (https://branchv31--serverless-


stack.netlify.com)

May 24, 2018: CloudForma)on now supports UsernameAEributes. This means that we don’t
need the email as alias work around.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v3.0...v3.1)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v3.0...v3.1)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v3.0...v3.1)

v3.0: Adding Part II (https://branchv30--serverless-stack.netlify.com)

May 10, 2018: Adding a new part to the guide to help create a produc)on ready version of the
note taking app. Discussion on the update (hEps://discourse.serverless-
stack.com/t/serverless-stack-update-part-ii/194).

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v2.2...v3.0)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v2.2...v3.0)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v2.0...v3.0)

v2.2: Updating to user Node.js starter and v8.10 (https://branchv22--


serverless-stack.netlify.com)

Apr 11, 2018: Upda)ng the backend to use Node.js starter and Lambda Node v8.10.
Discussion on the update (hEps://github.com/AnomalyInnova)ons/serverless-stack-
com/issues/223).

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v2.1...v2.2)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v2.1...v2.2)

v2.1: Updating to Webpack 4 (https://branchv21--serverless-


stack.netlify.com)

Mar 21, 2018: Upda)ng the backend to use Webpack 4 and serverless-webpack 5.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v2.0...v2.1)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v1.2.3...v2.1)

v2.0: AWS Amplify update (https://branchv20--serverless-


stack.netlify.com)

Upda)ng frontend to use AWS Amplify. Verifying SSL cer)ficate now uses DNS valida)on.
Discussion on the update (hEps://github.com/AnomalyInnova)ons/serverless-stack-
com/issues/123).

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.2.5...v2.0)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v1.2.5...v2.0)

v1.2.5: Using specific Bootstrap CSS version (https://branchv125--


serverless-stack.netlify.com)

Feb 5, 2018: Using specific Bootstrap CSS version since latest now points to Bootstrap v4.
But React-Bootstrap uses v3.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.2.4...v1.2.5)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v1.2.4...v1.2.5)

v1.2.4: Updating to React 16 (https://5a4993f3a6188f5a88e0c777--


serverless-stack.netlify.com/)

Dec 31, 2017: Updated to React 16 and fixed sigv4Client.js IE11 issue ({{
site.github_repo }}/issues/114#issuecomment-349938586).

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.2.3...v1.2.4)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v1.2...v1.2.4)

v1.2.3: Updating to babel-preset-env


(https://5a4993898198761218a1279f--serverless-stack.netlify.com/)

Dec 30, 2017: Updated serverless backend to use babel-preset-env plugin and added a note
to the Deploy to S3 chapter on reducing React app bundle size.

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.2.2...v1.2.3)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v1.2...v1.2.3)

v1.2.2: Adding new chapters (https://5a499324a6188f5a88e0c76d--


serverless-stack.netlify.com/)
Dec 1, 2017: Added the following Extra Credit chapters.

1. Customize the Serverless IAM Policy


2. Environments in Create React App

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.2.1...v1.2.2)

v1.2.1: Adding new chapters (https://5a4992e70b79b76fb0948300--


serverless-stack.netlify.com/)

Oct 7, 2017: Added the following Extra Credit chapters.

1. API Gateway and Lambda Logs


2. Debugging Serverless API Issues
3. Serverless environment variables
4. Stages in Serverless Framework
5. Configure mul)ple AWS profiles

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.2...v1.2.1)

v1.2: Upgrade to Serverless Webpack v3


(https://59caac9bcf321c5b78f2c3e2--serverless-stack.netlify.com/)

Sep 16, 2017: Upgrading serverless backend to using serverless-webpack plugin v3. The new
version of the plugin changes some of the commands used to test the serverless backend.
Discussion on the update (hEps://github.com/AnomalyInnova)ons/serverless-stack-
com/issues/130).

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.1...v1.2)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v1.1...v1.2)

v1.1: Improved Session Handling (https://59caae1e6f4c50416e86701d--


serverless-stack.netlify.com/)

Aug 30, 2017: Fixing some issues with session handling in the React app. A few minor updates
bundled together. Discussion on the update
(hEps://github.com/AnomalyInnova)ons/serverless-stack-com/issues/123).

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v1.0...v1.1)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v1.0...v1.1)

v1.0: IAM as authorizer (https://59caae01424ef20727c342ce--serverless-


stack.netlify.com/)

July 19, 2017: Switching to using IAM as an authorizer instead of the authen)ca)ng directly
with User Pool. This was a major update to the tutorial. Discussion on the update
(hEps://github.com/AnomalyInnova)ons/serverless-stack-com/issues/108).

Tutorial changes (hEps://github.com/AnomalyInnova)ons/serverless-stack-


com/compare/v0.9...v1.0)
API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/compare/v0.9...v1.0)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/compare/v0.9...v1.0)

v0.9: Cognito User Pool as authorizer


(https://59caadbd424ef20abdc342b4--serverless-stack.netlify.com/)

API (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
api/releases/tag/v0.9)
Client (hEps://github.com/AnomalyInnova)ons/serverless-stack-demo-
client/releases/tag/v0.9)

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-for-
changelog/17)
Staying up to date
We made this guide open source to make sure that the content is kept up to date and accurate
with the help of the community. We are also adding new chapters based on the needs of the
community and the feedback we receive.

To help people stay up to date with the changes, we run the Serverless Stack newsle=er
(h=ps://emailoctopus.com/lists/1c11b9a8-1500-11e8-a3c9-06b79b628af2/forms/subscribe).
The newsle=er is a:

Short plain text email


Outlines the recent updates to Serverless Stack
Never sent out more than once a week
One click unsubscribe
And you get the enPre guide as a 400 page PDF

You can also follow us on Twi=er (h=ps://twi=er.com/anomaly_inv).

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/staying-up-to-
date/28)
Organizing Serverless Projects
Once your serverless projects start to grow, you are faced with some choices on how to
organize your growing projects. In this chapter we’ll examine some of the most common ways
to structure your projects at a services and applica>on (mul>ple services) level.

First let’s start by quickly looking at the common terms used when talking about Serverless
Framework projects.

Service

A service is what you might call a Serverless project. It has a single serverless.yml file
driving it.

Applica+on

An applica>on or app is a collec>on of mul>ple services.

Now let’s look at the most common paIern for organizing serverless projects.

Microservices + Mono-Repo
Mono-repo, as the term suggests is the idea of a single repository. This means that your en>re
applica>on and all its services are in a single repository.

The microservice paIern on the other hand is a concept of keeping each of your services
modular and lightweight. So for example; if your app allows users to create profiles and submit
posts; you could have a service that deals with user profiles and one that deals with posts.

The directory structure of your en>re applica>on under the microservice + mono-repo paIern
would look something like this.

|- services/
|--- posts/
|----- get.js
|----- list.js
|----- create.js
|----- update.js
|----- delete.js
|----- serverless.yml
|--- users/
|----- get.js
|----- list.js
|----- create.js
|----- update.js
|----- delete.js
|----- serverless.yml
|- lib/
|- package.json

A couple of things to no>ce here:

1. We are going over a Node.js project here but this paIern applies to other languages as
well.
2. The services/ dir at the root is made up of a collec>on of services. Where a service
contains a single serverless.yml file.
3. Each service deals with a rela>vely small and self-contained func>on. So for example, the
posts service deals with everything from crea>ng to dele>ng posts. Of course, the
degree to which you want to separate your applica>on is en>rely up to you.
4. The package.json (and the node_modules/ dir) are at the root of the repo.
However, it is fairly common to have a separate package.json inside each service
directory.
5. The lib/ dir is just to illustrate that any common code that might be used across all
services can be placed in here.
6. To deploy this applica>on you are going to need to run serverless deploy separately
in each of the services.
7. Environments (or stages) (/chapters/stages-in-serverless-framework.html) need to be co-
ordinated across all the different services. So if your team is using a dev , staging , and
prod environment, then you are going to need to define the specifics of this in each of
the services.

Advantages of Mono-Repo
The microservice + mono-repo paIern has grown in popularity for a couple of reasons:

1. Lambda func>ons are a natural fit for a microservice based architecture. This is due to a
few of reasons. Firstly, the performance of Lambda func>ons is related to the size of the
func>on. Secondly, debugging a Lambda func>on that deals with a specific event is much
easier. Finally, it is just easier to conceptually relate a Lambda func>on with a single event.

2. The easiest way to share code between services is by having them all together in a single
repository. Even though your services end up dealing with separate por>ons of your app,
they s>ll might need to share some code between them. Say for example; you have some
code that formats your requests and responses in your Lambda func>ons. This would
ideally be used across the board and it would not make sense to replicate this code in all
the services.

Disadvantages of Mono-Repo

Before we go through alterna>ve paIerns, let’s quickly look at the drawbacks of the
microservice + mono-repo paIern.

1. Microservices can grow out of control and each added service increases the complexity of
your applica>on.
2. This also means that you can end up with hundreds of Lambda func>ons.
3. Managing deployments for all these services and func>ons can get complicated.

Most of the issues described above start to appear when your applica>on begins to grow.
However, there are services that help you deal with some these issues. Services like IOpipe
(hIps://www.iopipe.com), Epsagon (hIps://epsagon.com), and Dashbird (hIps://dashbird.io)
help you with observability of your Lambda func>ons. And our own Seed (hIps://seed.run)
helps you with managing deployments and environments of mono-repo Serverless Framework
applica>ons.

Now let’s look at some alterna>ve approaches.

Multi-Repo
The obvious counterpart to the mono-repo paIern is the mul>-repo approach. In this paIern
each of your repositories has a single Serverless Framework project.

A couple of things to watch out for with the mul>-repo paIern.


1. Code sharing across repos can be tricky since your applica>on is spread across mul>ple
repos. There are a couple of ways to deal with this. In the case of Node you can use
private NPM modules. Or you can find ways to link the common shared library of code to
each of the repos. In both of these cases your deployment process needs to accommodate
for the shared code.

2. Due to the fric>on involved in code sharing, we typically see each service (or repo) grow
in the number of Lambda func>ons. This can cause you to hit the CloudForma>on
resource limit and get a deployment error that looks like:

Error --------------------------------------------------

The CloudFormation template is invalid: Template format error:


Number of resources, 201, is greater than maximum allowed, 200

Even with the disadvantages the mul>-repo paIern does have its place. We have come across
cases where some infrastructure related pieces (seang up DynamoDB, Cognito, etc) is done in
a service that is placed in a separate repo. And since this typically doesn’t need a lot of code or
even share anything with the rest of your applica>on, it can live on it’s own. So in effect you
can run a mul>-repo setup where the standalone repos are for your infrastructure and your API
endpoints live in a microservice + mono-repo setup.

Finally, it’s worth looking at the less common monolith paIern.

Monolith
The monolith paIern involves taking advantage of API Gateway’s {proxy+} and ANY
method to route all the requests to a single Lambda func>on. In this Lambda func>on you can
poten>ally run an applica>on server like Express (hIps://expressjs.com). So as an example, all
the API requests below would be handled by the same Lambda func>on.

GET https://api.example.com/posts
POST https://api.example.com/posts
PUT https://api.example.com/posts
DELETE https://api.example.com/posts

GET https://api.example.com/users
POST https://api.example.com/users
PUT https://api.example.com/users
DELETE https://api.example.com/users

And the specific sec>on in your serverless.yml might look like the following:

handler: app.main
events:
- http:
method: any
path: /{proxy+}

Where the main func>on in your app.js is responsible for parsing the routes and figuring
out the HTTP methods to do the specific ac>on necessary.

The biggest drawback here is that the size of your func>ons keeps growing. And this can
affect the performance of your func>ons. It also makes it harder to debug your Lambda
func>ons.

And that should roughly cover the main ways to organize your Serverless Framework
applica>ons. Hopefully, this chapter has given you a good overview of the various approaches
involved along with their benefits and drawbacks.

In the next series of chapters we’ll be looking at how to work with mul>ple services in your
Serverless Framework applica>on.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/organizing-
serverless-projects/350)
Cross-Stack References in Serverless
In the previous chapter we looked at the most common pa5erns for organizing your Serverless
applica;ons (/chapters/organizing-serverless-projects.html). Now let’s look at how to work
with mul;ple services in your Serverless applica;on.

You might recall that a Serverless service is where a single serverless.yml is used to
define the project. And the serverless.yml file is converted into a CloudForma;on
template (h5ps://aws.amazon.com/cloudforma;on/aws-cloudforma;on-templates/) using
Serverless Framework. This means that in the case of mul;ple services you might need to
reference a resource that is available in a different service. For example, you might have your
DynamoDB tables created in one service and your APIs (which are in another service) need to
refer to them. Of course you don’t want to hard code this. And so over the next few chapters
we will be breaking down the note taking applica;on
(h5ps://github.com/AnomalyInnova;ons/serverless-stack-demo-api) into mul;ple resources to
illustrate how to do this.

However before we do, we need to cover the concept of cross-stack references. A cross-stack
reference is a way for one CloudForma;on template to refer to the resource in another
CloudForma;on template.

CloudFormation Cross-Stack References


To create a cross-stack reference, you need to:

1. Use the Export: flag in the Outputs: sec;on in the serverless.yml of the
service you would like to reference.

2. Then in the service where you want to use the reference; use the Fn::ImportValue
CloudForma;on func;on.

So as a quick example (we will go over this in detail shortly), say you wanted to refer to the
DynamoDB table across services.

1. First export the table name in your DynamoDB service using the Export: flag:
resources:
Resources:
NotesTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: notes

# ...

Outputs:
Value:
Ref: NotesTable
Export:
Name: NotesTableName

2. And in your API service, import it using the Fn::ImportValue func;on:

'Fn::ImportValue': NotesTableName

The Fn::ImportValue func;on takes the export name and returns the exported value. In
this case the imported value is the DynamoDB table name.

Now before we dig into the details of cross-stack references in Serverless, let’s quickly look at
some of its details.

Cross-stack references only apply within a single region. Meaning that an exported value
can be referenced by any service in that region.

Consequently, the Export: func;on needs to be unique within that region.

If a service’s export is being referenced in another stack, the service cannot be removed.
So for the above example, you won’t be able to remove the DynamoDB service if it is s;ll
being referenced in the API service.

The services need to be deployed in a specific order. The service that is expor;ng a value
needs to be deployed before the one doing the impor;ng. Using the above example again,
the DynamoDB service needs to be deployed before the API service.
Advantages of Cross-Stack References
As your applica;on grows, it can become hard to track the dependencies between the
services in the applica;on. And cross-stack references can help with that. It creates a strong
link between the services. As a comparison, if you were to refer to the linked resource by hard
coding the value, it’ll be difficult to keep track of it as your applica;on grows.

The other advantage is that you can easily recreate the en;re applica;on (say for tes;ng) with
ease. This is because none of the services of your applica;on are sta;cally linked to each
other.

Example Setup
Cross-stack references can be very useful but some aspects of it can be a li5le confusing and
the documenta;on can make it hard to follow. To illustrate the various ways to use cross-stack
references in serverless we are going to split up our note taking app
(h5ps://github.com/AnomalyInnova;ons/serverless-stack-demo-api) into a mono-repo app
with mul;ple services that are connected through cross-stack references
(h5ps://github.com/AnomalyInnova;ons/serverless-stack-demo-mono-api).

We are going to do the following:

1. Make DynamoDB a separate service.

2. Make the S3 file uploads bucket a separate service.

3. Split our API into two services.

4. In the first API service, refer to the DynamoDB service using a cross-stack reference.

5. In the second API service, do the same as the first. And addi;onally, link to the first API
service so that we can use the same API Gateway domain as the first.

6. Secure all our resources with a Cognito User Pool. And with an Iden;ty Pool create an
IAM role that gives authen;cated users permissions to the resources we created.

We are splibng up our app this way mainly to illustrate how to use cross-stack references. But
you can split it up in a way that makes more sense for you. For example, you might choose to
have all your infrastructure resources (DynamoDB and S3) in one service, your APIs in
another, and your auth in a separate service.
We’ve also created a separate GitHub repo with a working example
(h5ps://github.com/AnomalyInnova;ons/serverless-stack-demo-mono-api) of the above setup
that you can use for reference. We’ll be linking to it at the bo5om of each of the following
chapters.

In the next chapter let’s look at sebng up DynamoDB as a separate service.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/cross-stack-
references-in-serverless/405)

For reference, here is the code we are using

" Mono-repo Backend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-mono-api)
DynamoDB as a Serverless Service
While crea*ng a Serverless applica*on with mul*ple services, you might want to split the
DynamoDB por*on out separately. This can be useful because you are probably not going to
be making changes to this very frequently. Also, if you have mul*ple development
environments, it is not likely that you are going to connect them to different database
environments. For example, you might give the developers on your team their own
environment but they might all connect to the same DynamoDB environment. So it would
make sense to configure DynamoDB separately from the applica*on API services.

In the example repo (hIps://github.com/AnomalyInnova*ons/serverless-stack-demo-mono-


api), you’ll no*ce that we have a database service in the services/ directory. And the
serverless.yml in this service helps us manage our DynamoDB table.

service: notes-app-mono-database

custom:
# Our stage is based on what is passed in when running serverless
# commands. Or fallsback to what we have set in the provider
section.
stage: ${opt:stage, self:provider.stage}
# Set the table name here so we can use it while testing locally
tableName: ${self:custom.stage}-mono-notes
# Set our DynamoDB throughput for prod and all other non-prod
stages.
tableThroughputs:
prod: 5
default: 1
tableThroughput:
${self:custom.tableThroughputs.${self:custom.stage},
self:custom.tableThroughputs.default}

provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1

resources:
Resources:
NotesTable:
Type: AWS::DynamoDB::Table
Properties:
# Generate a name based on the stage
TableName: ${self:custom.tableName}
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
- AttributeName: noteId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
- AttributeName: noteId
KeyType: RANGE
# Set the capacity based on the stage
ProvisionedThroughput:
ReadCapacityUnits: ${self:custom.tableThroughput}
WriteCapacityUnits: ${self:custom.tableThroughput}

Outputs:
NotesTableArn:
Value:
Fn::GetAtt:
- NotesTable
- Arn
Export:
Name: ${self:custom.stage}-NotesTableArn

If you have followed along with Part II of our guide (/chapters/configure-dynamodb-in-


serverless.html), the Resources: sec*on should seem familiar. It is crea*ng the Notes table
that we use in our note taking applica*on (hIps://github.com/AnomalyInnova*ons/serverless-
stack-demo-api). The key addi*on here in regards to the cross-stack references is in the
Outputs: sec*on. Let’s go over them quickly.

1. We are expor*ng one value here. The NotesTableArn is the ARN (/chapters/what-is-
an-arn.html) of the DynamoDB table that we are crea*ng. And the NotesTableName
which is the name of the table being created. The ARN is necessary for any IAM roles that
are going to reference the DynamoDB table.

2. The export name is based on the stage we are using to deploy this service -
${self:custom.stage} . This is important because we want our en*re applica*on to
be easily replicable across mul*ple stages. If we don’t include the stage name the exports
will thrash when we deploy to mul*ple stages.

3. The names of the exported values is ${self:custom.stage}-NotesTableArn .

4. We get the table ARN by using the Fn::GetAtt CloudForma*on func*on. This func*on
takes a reference from the current service and the aIribute we need. The reference in this
case is NotesTable . You’ll no*ce that the table we created in the Resources:
sec*on is created using NotesTable as the name.

When we deploy this service we’ll no*ce the exported values in the output and we can
reference these cross-stack in our other services.

Next we’ll do something similar for our S3 bucket.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/dynamodb-as-a-
serverless-service/406)

For reference, here is the code we are using

" Mono-repo Backend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-mono-api)
S3 as a Serverless Service
Just as we did with DynamoDB in the last chapter (/chapters/dynamodb-as-a-serverless-
service.html), we’ll look at spli@ng S3 into a separate Serverless service. It should be noted
that for our simple note taking applicaFon, it does make too much sense to split S3 into its
own service. But it is useful to go over the case to beGer understand cross-stack references in
Serverless.

In the example repo (hGps://github.com/AnomalyInnovaFons/serverless-stack-demo-mono-


api), you’ll noFce that we have a uploads service in the services/ directory. And the
serverless.yml in this service looks like the following.

service: notes-app-mono-uploads

custom:
# Our stage is based on what is passed in when running serverless
# commands. Or falls back to what we have set in the provider
section.
stage: ${opt:stage, self:provider.stage}

provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1

resources:
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
# Set the CORS policy
CorsConfiguration:
CorsRules:
-
AllowedOrigins:
- '*'
AllowedHeaders:
- '*'
AllowedMethods:
- GET
- PUT
- POST
- DELETE
- HEAD
MaxAge: 3000

# Print out the name of the bucket that is created


Outputs:
AttachmentsBucketArn:
Value:
Fn::GetAtt:
- S3Bucket
- Arn
Export:
Name: ${self:custom.stage}-AttachmentsBucketArn

AttachmentsBucketName:
Value:
Ref: S3Bucket
Export:
Name: ${self:custom.stage}-AttachmentsBucket

Most of the Resources: secFon should be fairly straighLorward and is based on Part II of
this guide (/chapters/configure-s3-in-serverless.html). So let’s go over the cross-stack exports
in the Outputs: secFon.

1. Just as in the DynamoDB service (/chapters/dynamodb-as-a-serverless-service.html), we


are exporFng the ARN (/chapters/what-is-an-arn.html) ( AttachmentsBucketArn ) and
the name of the bucket ( AttachmentsBucketName ).

2. The names of the exported values is based on the stage: ${self:custom.stage}-


AttachmentsBucketArn and ${self:custom.stage}-AttachmentsBucket .

3. We can get the ARN by using the Fn::GetAtt funcFon by passing in the ref
( S3Bucket ) and the aGribute we need ( Arn ).

4. And finally, we can get the bucket name by just using the ref ( S3Bucket ). Note that
unlike the DynamoDB table name, the S3 bucket name is auto-generated. So while we
could get away with not exporFng the DynamoDB table name; in the case of S3, we need
to export it.

Now that we have the main infrastructure pieces created, let’s take a look at our APIs next. For
illustraFve purposes we are going to create two separate API services and look at how to
group them under the same API Gateway domain.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/s3-as-a-
serverless-service/407)

For reference, here is the code we are using

" Mono-repo Backend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-mono-api)
API Gateway Domains Across
Services
So to summarize so far, we are looking at how to create a Serverless applica8on with mul8ple
services and to link them together using cross-stack references (/chapters/cross-stack-
references-in-serverless.html). We’ve created a separate services for DynamoDB
(/chapters/dynamodb-as-a-serverless-service.html) and our S3 file uploads bucket
(/chapters/s3-as-a-serverless-service.html).

In this chapter we will look at how to work with API Gateway across mul8ple services. A
challenge that you run into when spliKng your APIs into mul8ple services, is sharing the same
domain for them. You might recall that APIs that are created as a part of the Serverless service
get their own unique URL that looks something like:

https://z6pv80ao4l.execute-api.us-east-1.amazonaws.com/dev

When you aRach a custom domain for your API, it is aRached to a specific endpoint like the
one above. This means that if you create mul8ple API services, they will all have unique
endpoints.

You can assign different base paths for your custom domains. For example,
api.example.com/notes can point to one service while api.example.com/users can
point to another. But if you try to split your notes service up, you’ll face the challenge of
sharing the custom domain across them.

In this chapter we will look at how to share the API Gateway project across mul8ple services.
For this we will create two separate Serverless services for our APIs. The first is the notes
service. This is the same one we’ve used in our note taking app (hRps://demo2.serverless-
stack.com) so far. But for this chapter we will simplify the number of endpoints to focus on the
cross-stack aspects of it. For the second service, we’ll create a simple users service. This
service isn’t a part of our note taking app. We just need it to demonstrate the concepts in this
chapter.
Multiple API Services
We are going to be crea8ng a notes and a users service using the following setup.

The notes service is going to be our main API service and the users service is going to link
to it. This means that the users service will refer to the notes service.

The notes service will be under /notes dir and the users service will be under the
/users dir.

Notes Service
First let’s look at the notes service. We need to connect it to the DynamoDB service that we
previously created (/chapters/dynamodb-as-a-serverless-service.html). In the example repo
(hRps://github.com/AnomalyInnova8ons/serverless-stack-demo-mono-api), you’ll no8ce that
we have a notes service in the services/ directory with a serverless.yml .

service: notes-app-mono-notes

custom:
# Our stage is based on what is passed in when running serverless
# commands. Or fallsback to what we have set in the provider
section.
stage: ${opt:stage, self:provider.stage}

provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1

# These environment variables are made available to our functions


# under process.env.
environment:
tableName:
${file(../database/serverless.yml):custom.tableName}

iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
# Restrict our IAM role permissions to
# the specific table for the stage
Resource:
- 'Fn::ImportValue': ${self:custom.stage}-NotesTableArn

functions:
# Defines an HTTP API endpoint that calls the main function in
create.js
# - path: url path is /notes
# - method: POST request
# - cors: enabled CORS (Cross-Origin Resource Sharing) for browser
cross
# domain api call
# - authorizer: authenticate using the AWS IAM role
get:
# Defines an HTTP API endpoint that calls the main function in
get.js
# - path: url path is /notes/{id}
# - method: GET request
handler: handler.main
events:
- http:
path: notes
method: get
cors: true
authorizer: aws_iam

resources:
Outputs:
ApiGatewayRestApiId:
Value:
Ref: ApiGatewayRestApi
Export:
Name: ${self:custom.stage}-ApiGatewayRestApiId

ApiGatewayRestApiRootResourceId:
Value:
Fn::GetAtt:
- ApiGatewayRestApi
- RootResourceId
Export:
Name: ${self:custom.stage}-ApiGatewayRestApiRootResourceId

Let’s go over some of the details of this service.

1. The Lambda func8ons in our service need to know which DynamoDB table to connect to.
To do this we are impor8ng the table name we use from the serverless.yml of that
service. We do this using
${file(../database/serverless.yml):custom.tableName} . This is basically
telling Serverless Framework to look for the serverless.yml file in the
services/database/ directory. And in that file look for the custom variable called
tableName . We set this value as an environment variable so that we can use
process.env.tableName in our Lambda func8on to find the generated name of our
notes table.

2. Next, we need to give our Lambda func8on permission to talk to this table by adding an
IAM policy. The IAM policy needs the ARN (/chapters/what-is-an-arn.html) of the table.
This is the first 8me we are using the import por8on of our cross-stack reference. Back in
the chapter where we created the DynamoDB service (/chapters/dynamodb-as-a-
serverless-service.html), we exported ${self:custom.stage}-NotesTableArn . And
we can refer to it by 'Fn::ImportValue': ${self:custom.stage}-
NotesTableArn .

3. We are going to export a couple values in this service to be able to share this API
Gateway resource in our users service.

4. The first cross-stack reference that needs to be shared is the API Gateway Id that is
created as a part of this service. We are going to export it with the name
${self:custom.stage}-ApiGatewayRestApiId . Again, we want the exports to
work across all our environments/stages and so we include the stage name as a part of it.
The value of this export is available as a reference in our current stack called
ApiGatewayRestApi .

5. Finally, we also need to export the RootResourceId . This is a reference to the / path
of this API Gateway project. To this Id we use the Fn::GetAtt CloudForma8on
func8on and pass in the current ApiGatewayRestApi and look up the aRribute
RootResourceId . We export this using the name ${self:custom.stage}-
ApiGatewayRestApiRootResourceId .

Users Service
In the example repo (hRps://github.com/AnomalyInnova8ons/serverless-stack-demo-mono-
api), open the users service in the services/ directory.

service: notes-app-mono-users

custom:
# Our stage is based on what is passed in when running serverless
# commands. Or fallsback to what we have set in the provider
section.
stage: ${opt:stage, self:provider.stage}

provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1

apiGateway:
restApiId:
'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId
restApiRootResourceId:
'Fn::ImportValue': ${self:custom.stage}-
ApiGatewayRestApiRootResourceId
# These environment variables are made available to our functions
# under process.env.
environment:
tableName:
${file(../database/serverless.yml):custom.tableName}

iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
# Restrict our IAM role permissions to
# the specific table for the stage
Resource:
- 'Fn::ImportValue': ${self:custom.stage}-NotesTableArn

functions:
# Defines an HTTP API endpoint that calls the main function in
create.js
# - path: url path is /users
# - method: POST request
# - cors: enabled CORS (Cross-Origin Resource Sharing) for browser
cross
# domain api call
# - authorizer: authenticate using the AWS IAM role
get:
# Defines an HTTP API endpoint that calls the main function in
get.js
# - path: url path is /users/{id}
# - method: GET request
handler: handler.main
events:
- http:
path: users
method: get
cors: true
authorizer: aws_iam

Let’s go over this quickly.

Just as the notes service we are referencing our DynamoDB table name using
${file(../database/serverless.yml):custom.tableName} and the table ARN
using 'Fn::ImportValue': ${self:custom.stage}-NotesTableArn .

To share the same API Gateway domain as our notes service, we are adding a
apiGateway: sec8on to the provider: block.

1. Here we state that we want to use the restApiId of our notes service. We do this
by using the cross-stack reference 'Fn::ImportValue':
${self:custom.stage}-ApiGatewayRestApiId that we had exported above.

2. We also state that we want all the APIs in our service to be linked under the root path
of our notes service. We do this by seKng the restApiRootResourceId to the
cross-stack reference 'Fn::ImportValue': ${self:custom.stage}-
ApiGatewayRestApiRootResourceId from above.

Finally, we don’t need to export anything in this service since we aren’t crea8ng any new
resources that need to be referenced.

The key thing to note in this setup is that API Gateway needs to know where to aRach the
routes that are created in this service. We want the /users path to be aRached to the root
of our API Gateway project. Hence the restApiRootResourceId points to the root
resource of our notes service. Of course we don’t have to do it this way. We can organize our
service such that the /users path is created in our main API service and we link to it here.

Next let’s 8e our en8re stack together and secure it using Cognito User Pool and Iden8ty Pool.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/api-gateway-
domains-across-services/408)

For reference, here is the code we are using

" Mono-repo Backend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-mono-api)
Cognito as a Serverless Service
Now that we have all of our resources created (API (/chapters/api-gateway-domains-across-
services.html), uploads (/chapters/s3-as-a-serverless-service.html), database
(/chapters/dynamodb-as-a-serverless-service.html)), let’s secure them using Cognito User Pool
as an authenEcaEon provider and Cognito Federated IdenEEes to control access. In this
chapter we are going to create a Serverless service that will use cross-stack references to Ee
all of our resources together.

In the example repo (hJps://github.com/AnomalyInnovaEons/serverless-stack-demo-mono-


api), open the auth service in the services/ directory.

service: notes-app-mono-auth

custom:
# Our stage is based on what is passed in when running serverless
# commands. Or fallsback to what we have set in the provider
section.
stage: ${opt:stage, self:provider.stage}

provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1

resources:
Resources:
CognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
# Generate a name based on the stage
UserPoolName: ${self:custom.stage}-mono-user-pool
# Set email as an alias
UsernameAttributes:
- email
AutoVerifiedAttributes:
- email

CognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
# Generate an app client name based on the stage
ClientName: ${self:custom.stage}-mono-user-pool-client
UserPoolId:
Ref: CognitoUserPool
ExplicitAuthFlows:
- ADMIN_NO_SRP_AUTH
GenerateSecret: false

# The federated identity for our user pool to auth with


CognitoIdentityPool:
Type: AWS::Cognito::IdentityPool
Properties:
# Generate a name based on the stage
IdentityPoolName: ${self:custom.stage}MonoIdentityPool
# Don't allow unathenticated users
AllowUnauthenticatedIdentities: false
# Link to our User Pool
CognitoIdentityProviders:
- ClientId:
Ref: CognitoUserPoolClient
ProviderName:
Fn::GetAtt: [ "CognitoUserPool", "ProviderName" ]

# IAM roles
CognitoIdentityPoolRoles:
Type: AWS::Cognito::IdentityPoolRoleAttachment
Properties:
IdentityPoolId:
Ref: CognitoIdentityPool
Roles:
authenticated:
Fn::GetAtt: [CognitoAuthRole, Arn]

# IAM role used for authenticated users


CognitoAuthRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Federated: 'cognito-identity.amazonaws.com'
Action:
- 'sts:AssumeRoleWithWebIdentity'
Condition:
StringEquals:
'cognito-identity.amazonaws.com:aud':
Ref: CognitoIdentityPool
'ForAnyValue:StringLike':
'cognito-identity.amazonaws.com:amr': authenticated
Policies:
- PolicyName: 'CognitoAuthorizedPolicy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action:
- 'mobileanalytics:PutEvents'
- 'cognito-sync:*'
- 'cognito-identity:*'
Resource: '*'

# Allow users to invoke our API


- Effect: 'Allow'
Action:
- 'execute-api:Invoke'
Resource:
Fn::Join:
- ''
-
- 'arn:aws:execute-api:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':'
- 'Fn::ImportValue': ${self:custom.stage}-
ApiGatewayRestApiId
- '/*'

# Allow users to upload attachments to their


# folder inside our S3 bucket
- Effect: 'Allow'
Action:
- 's3:*'
Resource:
- Fn::Join:
- ''
-
- 'Fn::ImportValue': ${self:custom.stage}-
AttachmentsBucketArn
- '/private/'
- '$'
- '{cognito-identity.amazonaws.com:sub}/*'

# Print out the Id of the User Pool and Identity Pool that are
created
Outputs:
UserPoolId:
Value:
Ref: CognitoUserPool

UserPoolClientId:
Value:
Ref: CognitoUserPoolClient
IdentityPoolId:
Value:
Ref: CognitoIdentityPool

This can seem like a lot but both the CognitoUserPool: and the
CognitoUserPoolClient: secEon are simply creaEng our Cognito User Pool. And you’ll
noEce that both these secEons are not using any cross-stack references. They are effecEvely
standalone. If you are looking for more details on this, refer to the Part II of this guide
(/chapters/configure-cognito-user-pool-in-serverless.html).

Cognito Identity Pool


The Cognito IdenEty Pool on the other hand needs to reference all the resources created thus
far. It can be a liJle inEmidaEng to start but let’s break it down into its various parts:

CognitoIdentityPool: creates the role and states that the Cognito User Pool that we
created above is going to be our auth provider.

The IdenEty Pool has an IAM role aJached to its authenEcated and unauthenEcated
users. Since, we only allow authenEcated users to our note taking app; we only have one
role. The CognitoIdentityPoolRoles: secEon states that we have an authenEcated
user role that we are going to create below and we are referencing it here by doing
Fn::GetAtt: [CognitoAuthRole, Arn] .

Finally, the CognitoAuthRole: secEon creates the IAM role that will allow access to
our API and S3 file uploads bucket.

Let’s look at the Cognito auth IAM role in detail.

Cognito Auth IAM Role


The IAM role that our authenEcated users are going to use needs to allow access to our API
Gateway resource and our S3 file uploads bucket.

This is the relevant secEon from the above serverless.yml .

# Allow users to invoke our API


- Effect: 'Allow'
Action:
- 'execute-api:Invoke'
Resource:
Fn::Join:
- ''
-
- 'arn:aws:execute-api:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':'
- 'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId
- '/*'

# Allow users to upload attachments to their


# folder inside our S3 bucket
- Effect: 'Allow'
Action:
- 's3:*'
Resource:
- Fn::Join:
- ''
-
- 'Fn::ImportValue': ${self:custom.stage}-AttachmentsBucketArn
- '/private/'
- '$'
- '{cognito-identity.amazonaws.com:sub}/*'

The API Gateway resource in our IAM role looks something like:

arn:aws:execute-api:us-east-1:12345678:qwe123rty456/*

Where us-east-1 is the region, 12345678 is the AWS account Id, and qwe123rty456 is
the API Gateway Resource Id. To construct this dynamically we need the cross-stack reference
of the API Gateway Resource Id that we exported in the API Gateway chapter (/chapters/api-
gateway-domains-across-services.html). And we can import it like so:
'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId

Again, all of our references are based on the stage we are deploying to.

The S3 bucket on the other hand has a resource that looks something like:

"arn:aws:s3:::my_s3_bucket/private/${cognito-
identity.amazonaws.com:sub}/*"

Where my_s3_bucket is the name of the bucket. We are going to use the generated name
that we exported back in the S3 bucket chapter (/chapters/s3-as-a-serverless-service.html).
And we can import it using:

'Fn::ImportValue': ${self:custom.stage}-AttachmentsBucketArn

And finally, you’ll noEce that we are outpuUng a couple of things in this service. We need the
Ids of the Cognito resources created in our frontend. But we don’t have to export any cross-
stack values.

Now that all of our resources are complete, we’ll look at how to deploy them. There is a bit of
a wrinkle here since we have some dependencies between our services.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/cognito-as-a-
serverless-service/409)

For reference, here is the code we are using

" Mono-repo Backend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-mono-api)
Deploying Multiple Services in
Serverless
Over the last few chapters we have looked at how to:

Link mul8ple Serverless services using CloudForma8on cross-stack references


(/chapters/cross-stack-references-in-serverless.html)
Create our DynamoDB table as a Serverless service (/chapters/dynamodb-as-a-
serverless-service.html)
Create an S3 bucket as a Serverless service (/chapters/s3-as-a-serverless-service.html)
Use the same API Gateway domain and resources across mul8ple Serverless services
(/chapters/api-gateway-domains-across-services.html)
Create a Serverless service for Cognito to authen8cate and authorize our users
(/chapters/cognito-as-a-serverless-service.html)

All this is available in a sample repo that you can deploy and test
(hMps://github.com/AnomalyInnova8ons/serverless-stack-demo-mono-api).

Now we can finally look at how to deploy our services. The addi8on of cross-stack references
to our services means that we have some built-in dependencies. This means that we need to
deploy some services before we deploy certain others.

Service Dependencies
Following is a list of the services we created:

database
uploads
notes
users
auth

And based on our cross-stack references the dependencies look roughly like:
database > notes > users

uploads > auth


notes

Where the a > b symbolizes that service a needs to be deployed before service b . To
break it down in detail:

The users API service relies on the notes API service for the API Gateway cross-
stack reference.

The users and notes API services rely on the database service for the DynamoDB
cross-stack reference.

And the auth service relies on the uploads and notes service for the S3 bucket and
API Gateway cross-stack references respec8vely.

Hence to deploy all of our services we need to follow this order:

1. database
2. uploads
3. notes
4. users
5. auth

Now there are some intricacies here but that is the general idea.

Multi-Service Deployments
Given the rough dependency graph above, you can script your CI/CD pipeline to ensure that
your automa8c deployments follow these rules. There are a few ways to simplify this process.

It is very likely that your auth , database , and uploads service don’t change very oYen.
You might also need to follow some strict policies across your team to make sure no
haphazard changes are made to it. So by separa8ng out these resources into their own
services (like we have done in the past few chapters) you can carry out updates to these
services by using a manual approval step as a part of the deployment process. This leaves the
API services. These need to be deployed manually once and can later be automated.
Service Dependencies in Seed
Seed (hMps://seed.run) has a concept of Deploy Phases (hMps://seed.run/docs/configuring-
deploy-phases) to handle service dependencies.

You can configure this by heading to the app se[ngs and hi[ng Manage Deploy Phases.

Here you’ll no8ce that by default all the services are deployed concurrently.
Note that, you’ll need to add your services first. To do this, head over to the app Se0ngs and
hit Add a Service.
We can configure our service dependencies by adding the necessary deploy phases and
moving the services around.
And when you deploy your app, the deployments are carried out according to the deploy
phases specified.
Environments
A quick word of handling environments across these services. The services that we have
created can be easily re-created for mul8ple environments or stages (/chapters/stages-in-
serverless-framework.html). A good standard prac8ce is to have a dev, staging, and prod
environment. And it makes sense to replicate all your services across these three
environments.

However, when you are working on a new feature or you want to give a developer on your
team their own environment, it might not make sense to replicate all of your services across
them. It is more common to only replicate the API services as you create mul8ple dev
environments.

Mono-Repo vs Multi-Repo
Finally, when considering how to house these services in your repository, it is worth looking at
how much code is shared across them. Typically, your infrastructure services ( database ,
uploads and auth ) don’t share any code between them. In fact they probably don’t have
any code in them to begin with. These services can be put in their own repos. Whereas the
API services that might share some code (request and response handling) can be placed in the
same repo and follow the mono-repo approach outlined in the Organizing Serverless Projects
chapter (/chapters/organizing-serverless-projects.html).

This combined way of using the mul8-repo and mono-repo strategy also makes sense when
you think about how we deploy them. As we stated above, the infrastructure services are
probably going to be deployed manually and with cau8on. While the API services can be
automated (using Seed (hMps://seed.run) or your own CI) for the mono-repo services and
handle the others ones as a special case.

Conclusion
Hopefully these series of chapters have given you a sense of how to structure large Serverless
applica8ons using CloudForma8on cross-stack references. And the example repo
(hMps://github.com/AnomalyInnova8ons/serverless-stack-demo-mono-api) gives you a clear
working demonstra8on of the concepts we’ve covered. Give the above setup a try and leave
us your feedback in the comments.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/deploying-
multiple-services-in-serverless/410)

For reference, here is the code we are using

" Mono-repo Backend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-mono-api)
API Gateway and Lambda Logs
Logging is an essen*al part of building backends and it is no different for a serverless API. It
gives us visibility into how we are processing and responding to incoming requests.

Types of Logs
There are 2 types of logs we usually take for granted in a monolithic environment.

Server logs

Web server logs maintain a history of requests, in the order they took place. Each log
entry contains the informa*on about the request, including client IP address, request
date/*me, request path, HTTP code, bytes served, user agent, etc.

Applica*on logs

Applica*on logs are a file of events that are logged by the web applica*on. It usually
contains errors, warnings, and informa*onal events. It could contain everything from
unexpected func*on failures, to key events for understanding how users behave.

In the serverless environment, we have lesser control over the underlying infrastructure,
logging is the only way to acquire knowledge on how the applica*on is performing. Amazon
CloudWatch (hMps://aws.amazon.com/cloudwatch/) is a monitoring service to help you collect
and track metrics for your resources. Using the analogy of server logs and applica*on logs, you
can roughly think of the API Gateway logs as your server logs and Lambda logs as your
applica*on logs.

In the chapter we are going to look to do the following:

Enable API Gateway CloudWatch Logs (#enable-api-gateway-cloudwatch-logs)


Enable Lambda CloudWatch Logs (#enable-lambda-cloudwatch-logs)
Viewing API Gateway CloudWatch Logs (#viewing-api-gateway-cloudwatch-logs)
Viewing Lambda CloudWatch Logs (#viewing-lambda-cloudwatch-logs)

Let’s get started.


Enable API Gateway CloudWatch Logs
This is a two step process. First, we need to create an IAM role that allows API Gateway to
write logs in CloudWatch. Then we need to turn on logging for our API project.

First, log in to your AWS Console (hMps://console.aws.amazon.com) and select IAM from the
list of services.

Select Roles on the leY menu.


Select Create Role.
Under AWS service, select API Gateway.

Click Next: Permissions.


Click Next: Review.
Enter a Role name and select Create role. In our case, we called our role
APIGatewayCloudWatchLogs .

Click on the role we just created.


Take a note of the Role ARN. We will be needing this soon.
Now that we have created our IAM role, let’s turn on logging for our API Gateway project.

Go back to your AWS Console (hMps://console.aws.amazon.com) and select API Gateway from
the list of services.

Select Se;ngs from the leY panel.


Enter the ARN of the IAM role we just created in the CloudWatch log role ARN field and hit
Save.
Select your API project from the leY panel, select Stages, then pick the stage you want to
enable logging for. For the case of our Notes App API
(hMps://github.com/AnomalyInnova*ons/serverless-stack-demo-api), we deployed to the
prod stage.
In the Logs tab:

Check Enable CloudWatch Logs.


Select INFO for Log level to log every request.
Check Log full requests/responses data to include en*re request and response body in
the log.
Check Enable Detailed CloudWatch Metrics to track latencies and errors in CloudWatch
metrics.
Scroll to the boMom of the page and click Save Changes. Now our API Gateway requests
should be logged via CloudWatch.

Note that, the execu*on logs can generate a ton of log data and it’s not recommended to
leave them on. They are much beMer for debugging. API Gateway does have support for
access logs, which we recomend leaving on. Here is how to enable access logs for your API
Gateway project (hMps://seed.run/blog/how-to-enable-access-logs-for-api-gateway).

Enable Lambda CloudWatch Logs


Lambda CloudWatch logs are enabled by default. It tracks the dura*on and max memory
usage for each execu*on. You can write addi*onal informa*on to CloudWatch via
console.log . For example:

export function main(event, context, callback) {


console.log('Hello world');
callback(null, { body: '' });
}
Viewing API Gateway CloudWatch Logs
CloudWatch groups log entries into Log Groups and then further into Log Streams. Log
Groups and Log Streams can mean different things for different AWS services. For API
Gateway, when logging is first enabled in an API project’s stage, API Gateway creates 1 log
group for the stage, and 300 log streams in the group ready to store log entries. API Gateway
picks one of these streams when there is an incoming request.

To view API Gateway logs, log in to your AWS Console (hMps://console.aws.amazon.com) and
select CloudWatch from the list of services.

Select Logs from the leY panel.


Select the log group prefixed with API-Gateway-ExecuLon-Logs_ followed by the API
Gateway id.
You should see 300 log streams ordered by the last event *me. This is the last *me a request
was recorded. Select the first stream.
This shows you the log entries grouped by request.
Note that two consecu*ve groups of logs are not necessarily two consecu*ve requests in real
*me. This is because there might be other requests that are processed in between these two
that were picked up by one of the other log streams.

Viewing Lambda CloudWatch Logs


For Lambda, each func*on has its own log group. And the log stream rotates if a new version
of the Lambda func*on has been deployed or if it has been idle for some *me.

To view Lambda logs, select Logs again from the leY panel. Then select the first log group
prefixed with /aws/lambda/ followed by the func*on name.

Select the first stream.


You should see START, END and REPORT with basic execu*on informa*on for each func*on
invoca*on. You can also see content logged via console.log in your Lambda code.
You can also use the Serverless CLI to view CloudWatch logs for a Lambda func*on.

From your project root run the following.

$ serverless logs -f <func-name>

Where the <func-name> is the name of the Lambda func*on you are looking for.
Addi*onally, you can use the --tail flag to stream the logs automa*cally to your console.

$ serverless logs -f <func-name> --tail

This can be very helpful during development when trying to debug your func*ons using the
console.log call.

Hopefully, this has helped you set up CloudWatch logging for your API Gateway and Lambda
projects. And given you a quick idea of how to read your serverless logs using the AWS
Console.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/api-gateway-
and-lambda-logs/31)
Debugging Serverless API Issues
In this chapter we are going to take a brief look at some common API Gateway and Lambda
issues we come across and how to debug them.

We’ve also compiled a list of some of the most common Serverless errors over on Seed
(hBps://seed.run). Check out Common Serverless Errors (hBps://seed.run/docs/serverless-
errors/) and do a quick search for your error message and see if it has a soluJon.

When a request is made to your serverless API, it starts by hiLng API Gateway and makes its
way through to Lambda and invokes your funcJon. It takes quite a few hops along the way
and each hop can be a point of failure. And since we don’t have great visibility over each of the
specific hops, pinpoinJng the issue can be a bit tricky. We are going to take a look at the
following issues:

Invalid API Endpoint (#invalid-api-endpoint)


Missing IAM Policy (#missing-iam-policy)
Lambda FuncJon Error (#lambda-funcJon-error)
Lambda FuncJon Timeout (#lambda-funcJon-Jmeout)

This chapter assumes you have turned on CloudWatch logging for API Gateway and that you
know how to read both the API Gateway and Lambda logs. If you have not done so, start by
taking a look at the chapter on API Gateway and Lambda Logs (/chapters/api-gateway-and-
lambda-logs.html).

Invalid API Endpoint


The first and most basic issue we see is when the API Gateway endpoint that is requested is
invalid. An API Gateway endpoint usually looks something like this:

https://API_ID.execute-api.REGION.amazonaws.com/STAGE/PATH

API_ID - a unique idenJfier per API Gateway project


REGION - the AWS region in which the API Gateway project is deployed to
STAGE - the stage of the project (defined in your serverless.yml or passed in through the
serverless deploy –stage command)
PATH - the path of an API endpoint (defined in your serverless.yml for each funcJon)

An API request will fail if:

The API_ID is not found in the specified REGION


The API Gateway project does not have the specified STAGE
API endpoint invoked does not match a pre-defined PATH

In all of these cases, the error does not get logged to CloudWatch since the request does not
hit your API Gateway project.

Missing IAM Policy


This happens when your API endpoint uses aws_iam as the authorizer, and the IAM role
assigned to the Cognito IdenJty Pool has not been granted the execute-api:Invoke permission
for your API Gateway resource.

This is a tricky issue to debug because the request sJll has not reached API Gateway, and
hence the error is not logged in the API Gateway CloudWatch logs. But we can perform a
check to ensure that our Cognito IdenJty Pool users have the required permissions, using the
IAM policy Simulator (hBps://policysim.aws.amazon.com).

Before we can use the simulator we first need to find out the name of the IAM role that we
are using to connect to API Gateway. We had created this role back in the Create a Cognito
idenJty pool (/chapters/create-a-cognito-idenJty-pool.html) chapter.

Select Cognito from your AWS Console (hBps://console.aws.amazon.com).


Next hit the Manage Federated IdenJJes buBon.
And select your IdenJty Pool. In our case it’s called notes identity pool .

Click Edit idenJty pool at the top right.


Here make a note of the name of the AuthenJcated role. In our case it is
Cognito_notesidentitypoolAuth_Role .
Now that we know the IAM role we are tesJng, let’s open up the IAM Policy Simulator
(hBps://policysim.aws.amazon.com).
Select Roles.
Select the IAM role that we made a note of in the steps above. In our case it is
Cognito_notesidentitypoolAuth_Role .

Select API Gateway as the service and select the Invoke acJon.
Expand the service and enter the API Gateway endpoint ARN, then select Run SimulaJon.
The format here is the same one we used back in the Create a Cognito idenJty pool
(/chapters/create-a-cognito-idenJty-pool.html) chapter; arn:aws:execute-
api:YOUR_API_GATEWAY_REGION:*:YOUR_API_GATEWAY_ID/* . In our case this looks like
arn:aws:execute-api:us-east-1:*:ly55wbovq4/* .
If your IAM role is configured properly you should see allowed under Permission.
But if something is off, you’ll see denied.

To fix this and edit the role we need to go back to the AWS Console
(hBps://console.aws.amazon.com) and select IAM from the list of services.
Select Roles on the le[ menu.
And select the IAM role that our IdenJty Pool is using. In our case it’s called
Cognito_notesidentitypoolAuth_Role .

Expand the policy under the list of policies.


Click Edit policy.
Here you can edit the policy to ensure that it has the right permission to invoke API Gateway.
Ensure that there is a block in your policy like the one below.

...
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-
api:YOUR_API_GATEWAY_REGION:*:YOUR_API_GATEWAY_ID/*"
]
}
...

Finally, hit Save to update the policy.

Now if you test your policy, it should show that you are allowed to invoke your API Gateway
endpoint.
Lambda Function Error
Now if you are able to invoke your Lambda funcJon but it fails to execute properly due to
uncaught excepJons, it’ll error out. These are preBy straigh\orward to debug. When this
happens, AWS Lambda will aBempt to convert the error object to a string, and then send it to
CloudWatch along with the stacktrace. This can be observed in both Lambda and API
Gateway CloudWatch log groups.

Lambda Function Timeout


SomeJmes we might run into a case where the Lambda funcJon just Jmes out. Normally, a
Lambda funcJon will end its execuJon by invoking the callback funcJon that was passed in.
By default, the callback will wait unJl the Node.js runJme event loop is empty before
returning the results to the caller. If the Lambda funcJon has an open connecJon to, let’s say a
database server, the event loop is not empty, and the callback will wait indefinitely unJl the
connecJon is closed or the Lambda funcJon Jmes out.

To get around this issue, you can set this callbackWaitsForEmptyEventLoop property to false
to request AWS Lambda to freeze the process as soon as the callback is called, even if there
are events in the event loop.

export async function handler(event, context, callback) {

context.callbackWaitsForEmptyEventLoop = false;

...
};

This effecJvely allows a Lambda funcJon to return its result to the caller without requiring
that the database connecJon be closed. This allows the Lambda funcJon to reuse the same
connecJon across calls, and it reduces the execuJon Jme as well.

These are just a few of the common issues we see folks running into while working with
serverless APIs. Feel free to let us know via the comments if there are any other issues you’d
like us to cover.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/debugging-
serverless-api-issues/143)
Serverless Environment Variables
In Node.js we use the process.env to get access to environment variables of the current
process. In AWS Lambda, we can set environment variables that we can access via the
process.env object.

Let’s take a quick look at how to do that.

Defining Environment Variables


We can define our environment variables in our serverless.yml in two separate places.
The first is in the functions secDon:

service: service-name

provider:
name: aws
stage: dev

functions:
hello:
handler: handler.hello
environment:
SYSTEM_URL: http://example.com/api/v1

Here SYSTEM_URL is the name of the environment variable we are defining and
http://example.com/api/v1 is its value. We can access this in our hello Lambda
funcDon using process.env.SYSTEM_URL , like so:

export function hello(event, context, callback) {


callback(null, { body: process.env.SYSTEM_URL });
}
We can also define our environment variables globally in the provider secDon:

service: service-name

provider:
name: aws
stage: dev
environment:
SYSTEM_ID: jdoe

functions:
hello:
handler: handler.hello
environment:
SYSTEM_URL: http://example.com/api/v1

Just as before we can access the environment variable SYSTEM_ID in our hello Lambda
funcDon using process.env.SYSTEM_ID . The difference being that it is available to all the
Lambda funcDons defined in our serverless.yml .

In the case where both the provider and functions secDon has an environment variable
with the same name, the funcDon specific environment variable takes precedence. As in, we
can override the environment variables described in the provider secDon with the ones
defined in the functions secDon.

Custom Variables in Serverless Framework


Serverless Framework builds on these ideas to make it easier to define and work with
environment variables in our serverless.yml by generalizing the idea of variables
(hMps://serverless.com/framework/docs/providers/aws/guide/variables/).

Let’s take a quick look at how these work using an example. Say you had the following
serverless.yml .

service: service-name

provider:
name: aws
stage: dev

functions:
helloA:
handler: handler.helloA
environment:
SYSTEM_URL: http://example.com/api/v1/pathA

helloB:
handler: handler.helloB
environment:
SYSTEM_URL: http://example.com/api/v1/pathB

In the case above we have the environment variable SYSTEM_URL defined in both the
helloA and helloB Lambda funcDons. But the only difference between them is that the
url ends with pathA or pathB . We can merge these two using the idea of variables.

A variable allows you to replace values in your serverless.yml dynamically. It uses the
${variableName} syntax, where the value of variableName will be inserted.

Let’s see how this works in pracDce. We can rewrite our example and simplify it by doing the
following:

service: service-name

custom:
systemUrl: http://example.com/api/v1/

provider:
name: aws
stage: dev

functions:
helloA:
handler: handler.helloA
environment:
SYSTEM_URL: ${self:custom.systemUrl}pathA
helloB:
handler: handler.helloB
environment:
SYSTEM_URL: ${self:custom.systemUrl}pathB

This should be preMy straighRorward. We started by adding this secDon first:

custom:
systemUrl: http://example.com/api/v1/

This defines a variable called systemUrl under the secDon custom . We can then
reference the variable using the syntax ${self:custom.systemUrl} .

We do this in the environment variables SYSTEM_URL:


${self:custom.systemUrl}pathA . Serverless Framework parses this and inserts the value
of self:custom.systemUrl and that combined with pathA at the end gives us the
original value of http://example.com/api/v1/pathA .

Variables can be referenced from a lot of different sources including CLI opDons, external
YAML files, etc. You can read more about using variables in your serverless.yml here
(hMps://serverless.com/framework/docs/providers/aws/guide/variables/).

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/serverless-
environment-variables/25)
Stages in Serverless Framework
Serverless Framework allows you to create stages for your project to deploy to. Stages are
useful for crea8ng environments for tes8ng and development. Typically you create a staging
environment that is an independent clone of your produc8on environment. This allows you to
test and ensure that the version of code that you are about to deploy is good to go.

In this chapter we will take a look at how to configure stages in Serverless. Let’s first start by
looking at how stages can be implemented.

How Is Staging Implemented?


There are a couple of ways to set up stages for your project:

Using the API Gateway built-in stages

You can create mul8ple stages within a single API Gateway project. Stages within the
same project share the same endpoint host, but have a different path. For example, say
you have a stage called prod with the endpoint:

https://abc12345.execute-api.us-east-1.amazonaws.com/prod

If you were to add a stage called dev to the same API Gateway API, the new stage will
have the endpoint:

https://abc12345.execute-api.us-east-1.amazonaws.com/dev

The downside is that both stages are part of the same project. You don’t have the same
level of flexibility to fine tune the IAM policies for stages of the same API, when compared
to tuning different APIs. This leads to the next setup, each stage being its own API.

Separate APIs for each stage

You create an API Gateway project for each stage. Let’s take the same example, your
prod stage has the endpoint:
https://abc12345.execute-api.us-east-1.amazonaws.com/prod

To create the dev stage, you create a new API Gateway project and add the dev stage
to the new project. The new endpoint will look something like:

https://xyz67890.execute-api.us-east-1.amazonaws.com/dev

Note that the dev stage carries a different endpoint host since it belongs to a different
project. This is the approach Serverless Framework takes when configuring stages for
your Serverless project. We will look at this in detail below.

Separate AWS account for each stage

Just like how having each stage being separate APIs give us more flexibility to fine tune
the IAM policy. We can take it a step further and create the API project in a different AWS
account. Most companies don’t keep their produc8on infrastructure in the same account
as their development infrastructure. This helps reduce any cases where developers
accidentally edit/delete produc8on resources. We go in to more detail on how to deploy
to mul8ple AWS accounts using different AWS profiles in the Configure Mul8ple AWS
Profiles (/chapters/configure-mul8ple-aws-profiles.html) chapter.

Deploying to a Stage
Let’s look at how the Serverless Framework helps us work with stages. As men8oned above, a
new stage is a new API Gateway project. To deploy to a specific stage, you can either specify
the stage in the serverless.yml .

service: service-name

provider:
name: aws
stage: dev

Or you can specify the stage by passing the --stage op8on to the serverless deploy
command.

$ serverless deploy --stage dev


Stage Variables in Serverless Framework
Deploying to stages can be preWy simple but now let’s look at how to configure our
environment variables so that they work with our various stages. We went over the concept of
environment variables in the chapter on Serverless Environment Variables
(/chapters/serverless-environment-variables.html). Let’s extend that to specify variables based
on the stage we are deploying to.

Let’s take a look at a sample serverless.yml below.

service: service-name

custom:
myStage: ${opt:stage, self:provider.stage}
myEnvironment:
MESSAGE:
prod: "This is production environment"
dev: "This is development environment"

provider:
name: aws
stage: dev
environment:
MESSAGE:
${self:custom.myEnvironment.MESSAGE.${self:custom.myStage}}

There are a couple of things happening here. We first defined the custom.myStage variable
as ${opt:stage, self:provider.stage} . This is telling Serverless Framework to use the
--stage CLI op8on if it exists. And if it does not, then use the default stage specified by
provider.stage . We also define the custom.myEnvironment sec8on. This contains the
value for MESSAGE defined for each stage. Finally, we set the environment variable
MESSAGE as ${self:custom.myEnvironment.MESSAGE.${self:custom.myStage}} .
This sets the variable to pick the value of self:custom.myEnvironment depending on the
current stage defined in custom.myStage .

You can easily extend this format to create separate sets of environment variables for the
stages you are deploying to.
And we can access the MESSAGE in our Lambda func8ons via process.env object like so.

export function main(event, context, callback) {


callback(null, { body: process.env.MESSAGE });
}

Hopefully, this chapter gives you a quick idea on how to set up stages in your Serverless
project.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/stages-in-
serverless-framework/35)
Backups in DynamoDB
An important (yet overlooked) aspect of having a database powering your web applica;on are,
backups! In this chapter we are going to take a look at how to configure backups for your
DynamoDB tables.

For our demo notes app (hDps://demo.serverless-stack.com), we are using a DynamoDB table
to store all our user’s notes. DynamoDB achieves a high degree of data availability and
durability by replica;ng your data across three different facili;es within a given region.
However, DynamoDB does not provide an SLA for the data durability. This means that you
should backup your database tables.

Let’s start by geNng a quick background on how backups work in DynamoDB.

Backups in DynamoDB
There are two types of backups in DynamoDB:

1. On-demand backups

This creates a full backup on-demand of your DynamoDB tables. It’s useful for long-term
data reten;on and archival. The backup is retained even if the table is deleted. You can
use the backup to restore to a different table name. And this can make it useful for
replica;ng tables.

2. Point-in-3me recovery

This type of backup on the other hand allows you to perform point-in-;me restore. It’s
really helpful in protec;ng against accidental writes or delete opera;ons. So for example,
if you ran a script to transform the data within a table and it accidentally removed or
corrupted your data; you could simply restore your table to any point in the last 35 days.
DynamoDB does this by maintaining an incremental backup of your table. It even does
this automa;cally, so you don’t have to worry about crea;ng, maintaining, or scheduling
on-demand backups.
Let’s look at how to use the two backup types.

On-Demand Backup
Head over to your table and click on the Backups tab.

And just hit Create backup.


Give your backup a name and hit Create.
You should now be able to see your newly created backup.

Restore Backup
Now to restore your backup, simply select the backup and hit Restore backup.
Here you can type in the name of the new table you want to restore to and hit Restore table.
Depending on the size of the table, this might take some ;me. But you should no;ce a new
table being created from the backup.

DynamoDB makes it easy to create and restore on-demand backups. You can also read more
about on-demand backups here
(hDps://docs.aws.amazon.com/amazondynamodb/latest/developerguide/BackupRestore.html)
.

Point-in-Time Recovery
To enable Point-in-;me Recovery once again head over to the Backups tab.
And hit Enable in the Point-in-;me Recovery sec;on.
This will no;fy you that addi;onal charges will apply for this seNng. Click Enable to confirm.

Restore to Point-in-Time
Once enabled, you can click Restore to point-in-3me to restore to an older point.
Here you can type in the name of the new table to be restored to and select the ;me you
want to recover to.
And hit Restore table.
You should see your new table being restored.

You can read more about the details of Point-in-;me Recovery here
(hDps://docs.aws.amazon.com/amazondynamodb/latest/developerguide/PointInTimeRecovery
.html).

Conclusion
Given, the two above types; a good strategy is to enable Point-in-;me recovery and maintain
a schedule of longer term On-demand backups. There are quite a few plugins and scripts that
can help you with scheduling On-demand backups, here is one created by one of our readers -
hDps://github.com/UnlyEd/serverless-plugin-dynamodb-backups.

Also worth no;ng, DynamoDB’s backup and restore ac;ons have no impact on the table
performance or availability. No worrying about long backup processes that slow down
performance for your ac;ve users.

So make sure to configure backups for the DynamoDB tables in your applica;ons.
For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/backups-in-
dynamodb/705)
Configure Multiple AWS Profiles
When we configured our AWS CLI in the Configure the AWS CLI (/chapters/configure-the-
aws-cli.html) chapter, we used the aws configure command to set the IAM credenAals of
the AWS account we wanted to use to deploy our serverless applicaAon to.

These credenAals are stored in ~/.aws/credentials and are used by the Serverless
Framework when we run serverless deploy . Behind the scenes Serverless uses these
credenAals and the AWS SDK to create the necessary resources on your behalf to the AWS
account specified in the credenAals.

There are cases where you might have mulAple credenAals configured in your AWS CLI. This
usually happens if you are working on mulAple projects or if you want to separate the
different stages of the same project.

In this chapter let’s take a look at how you can work with mulAple AWS credenAals.

Create a New AWS Profile


Let’s say you want to create a new AWS profile to work with. Follow the steps outlined in the
Create an IAM User (/chapters/create-an-iam-user.html) chapter to create an IAM user in
another AWS account and take a note of the Access key ID and Secret access key.

To configure the new profile in your AWS CLI use:

$ aws configure --profile newAccount

Where newAccount is the name of the new profile you are creaAng. You can leave the
Default region name and Default output format the way they are.

Set a Profile on Local


We menAoned how the Serverless Framework uses your AWS profile to deploy your resources
on your behalf. But while developing on your local using the serverless invoke local
command things are a liRle different.

In this case your Lambda funcAon is run locally and has not been deployed yet. So any calls
made in your Lambda funcAon to any other AWS resources on your account will use the
default AWS profile that you have. You can check your default AWS profile in
~/.aws/credentials under the [default] tag.

To switch the default AWS profile to a new profile for the serverless invoke local
command, you can run the following:

$ AWS_PROFILE=newAccount serverless invoke local --function hello

Here newAccount is the name of the profile you want to switch to and hello is the name
of the funcAon that is being invoked locally. By adding AWS_PROFILE=newAccount at the
beginning of our serverless invoke local command we are seTng the variable that the
AWS SDK will use to figure out what your default AWS profile is.

If you want to set this so that you don’t add it to each of your commands, you can use the
following command:

$ export AWS_PROFILE=newAccount

Where newAccount is the profile you want to switch to. Now for the rest of your shell
session, newAccount will be your default profile.

You can read more about this in the AWS Docs here
(hRp://docs.aws.amazon.com/cli/latest/userguide/cli-mulAple-profiles.html).

Set a Profile While Deploying


Now if we want to deploy using this newly created profile we can use the --aws-profile
opAon for the serverless deploy command.

$ serverless deploy --aws-profile newAccount

Again, newAccount is the AWS profile Serverless Framework will be using to deploy.

If you don’t want to set the profile every Ame you run serverless deploy , you can add it
to your serverless.yml .

service: service-name

provider:
name: aws
stage: dev
profile: newAccount

Note the profile: newAccount line here. This is telling Serverless to use the
newAccount profile while running serverless deploy .

Set Profiles per Stage


There are cases where you would like to specify a different AWS profile per stage. A common
scenario for this is when you have a completely separate staging environment than your
producAon one. Each environment has its own API endpoint, database tables, and more
importantly, the IAM policies to secure the environment. A simple yet effecAve way to achieve
this is to keep the environments in separate AWS accounts. AWS OrganizaAons
(hRps://aws.amazon.com/organizaAons/) was in fact introduced to help teams to create and
manage these accounts and consolidate the usage charges into a single bill.

Let’s look at a quick example of how to work with mulAple profiles per stage. So following the
examples from before, if you wanted to deploy to your producAon environment, you would:

$ serverless deploy --stage prod --aws-profile prodAccount

And to deploy to the staging environment you would:

$ serverless deploy --stage dev --aws-profile devAccount

Here, prodAccount and devAccount are the AWS profiles for the producAon and staging
environment respecAvely.

To simplify this process you can add the profiles to your serverless.yml . So you don’t
have to specify them in your serverless deploy commands.
service: service-name

custom:
myStage: ${opt:stage, self:provider.stage}
myProfile:
prod: prodAccount
dev: devAccount

provider:
name: aws
stage: dev
profile: ${self:custom.myProfile.${self:custom.myStage}}

There are a couple of things happening here.

We first defined custom.myStage as ${opt:stage, self:provider.stage} . This


is telling Serverless Framework to use the value from the --stage CLI opAon if it exists.
If not, use the default stage specified in provider.stage .
We also defined custom.myProfile , which contains the AWS profiles we want to use
to deploy for each stage. Just as before we want to use the prodAccount profile if we
are deploying to stage prod and the devAccount profile if we are deploying to stage
dev .
Finally, we set the provider.profile to
${self:custom.myProfile.${self:custom.myStage}} . This picks the value of our
profile depending on the current stage defined in custom.myStage .

We used the concept of variables in Serverless Framework in this example. You can read more
about this in the chapter on Serverless Environment Variables (/chapters/serverless-
environment-variables.html).

Now, when you deploy to producAon, Serverless Framework is going to use the
prodAccount profile. And the resources will be provisioned inside prodAccount profile
user’s AWS account.

$ serverless deploy --stage prod

And when you deploy to staging, the exact same set of AWS resources will be provisioned
inside devAccount profile user’s AWS account.

$ serverless deploy --stage dev

NoAce that we did not have to set the --aws-profile opAon. And that’s it, this should give
you a good understanding of how to work with mulAple AWS profiles and credenAals.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/configure-
multiple-aws-profiles/21)
Customize the Serverless IAM Policy
Serverless Framework deploys using the policy a8ached to the IAM creden<als in your AWS
CLI profile. Back in the Create an IAM User (/chapters/create-an-iam-user.html) chapter we
created a user that the Serverless Framework will use to deploy our project. This user was
assigned AdministratorAccess. This means that Serverless Framework and your project has
complete access to your AWS account. This is fine in trusted environments but if you are
working as a part of a team you might want to fine-tune the level of access based on who is
using your project.

In this chapter we will take a look at how to customize the IAM Policy that Serverless
Framework is going to use.

The permissions required can be categorized into the following areas:

Permissions required by Serverless Framework


Permissions required by your Serverless Framework plugins
Permissions required by your Lambda code

Gran<ng AdministratorAccess policy ensures that your project will always have the necessary
permissions. But if you want to create an IAM policy that grants the minimal set of
permissions, you need to customize your IAM policy.

A basic Serverless project needs permissions to the following AWS services:

CloudForma1on to create change set and update stack


S3 to upload and store Serverless ar<facts and Lambda source code
CloudWatch Logs to store Lambda execu<on logs
IAM to manage policies for the Lambda IAM Role
API Gateway to manage API endpoints
Lambda to manage Lambda func<ons
EC2 to execute Lambda in VPC
CloudWatch Events to manage CloudWatch event triggers

A simple IAM Policy template


These can be defined and granted using a simple IAM policy.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cloudformation:*",
"s3:*",
"logs:*",
"iam:*",
"apigateway:*",
"lambda:*",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs",
"events:*"
],
"Resource": [
"*"
]
}
]
}

We can a8ach this policy to the IAM user we are crea<ng by con<nuing from the ACach
exis1ng policies directly step in the Create an IAM User (/chapters/create-an-iam-user.html)
chapter.

Hit the Create policy bu8on.


And hit Select in the Create Your Own Policy sec<on.
Here pick a name for your new policy and paste the policy created above in the Policy
Document field.

Finally, hit Create Policy. You can now chose this policy while crea<ng your IAM user instead
of the AdministratorAccess one that we had used before.

This policy grants your Serverless Framework project access to all the resources listed above.
But we can narrow this down further by restric<ng them to specific Ac1ons for the specific
Resources in each AWS service.

An advanced IAM Policy template


Below is a more nuanced policy template that restricts access to the Serverless project that is
being deployed. Make sure to replace <region> , <account_no> and <service_name>
for your specific project.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cloudformation:Describe*",
"cloudformation:List*",
"cloudformation:Get*",
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"cloudformation:DeleteStack"
],
"Resource": "arn:aws:cloudformation:<region>:
<account_no>:stack/<service_name>*/*"
},
{
"Effect": "Allow",
"Action": [
"cloudformation:ValidateTemplate"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:Get*",
"s3:List*"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*/*"
]
},
{
"Effect": "Allow",
"Action": [
"logs:DescribeLogGroups"
],
"Resource": "arn:aws:logs:<region>:<account_no>:log-group::log-
stream:*"
},
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:DeleteLogGroup",
"logs:DeleteLogStream",
"logs:DescribeLogStreams",
"logs:FilterLogEvents"
],
"Resource": "arn:aws:logs:<region>:<account_no>:log-
group:/aws/lambda/<service_name>*:log-stream:*",
"Effect": "Allow"
},
{
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:PassRole",
"iam:CreateRole",
"iam:DeleteRole",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:AttachRolePolicy",
"iam:DeleteRolePolicy"
],
"Resource": [
"arn:aws:iam::<account_no>:role/<service_name>*-lambdaRole"
]
},
{
"Effect": "Allow",
"Action": [
"apigateway:GET",
"apigateway:POST",
"apigateway:PUT",
"apigateway:DELETE"
],
"Resource": [
"arn:aws:apigateway:<region>::/restapis"
]
},
{
"Effect": "Allow",
"Action": [
"apigateway:GET",
"apigateway:POST",
"apigateway:PUT",
"apigateway:DELETE"
],
"Resource": [
"arn:aws:apigateway:<region>::/restapis/*"
]
},
{
"Effect": "Allow",
"Action": [
"lambda:GetFunction",
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:UpdateFunctionConfiguration",
"lambda:UpdateFunctionCode",
"lambda:ListVersionsByFunction",
"lambda:PublishVersion",
"lambda:CreateAlias",
"lambda:DeleteAlias",
"lambda:UpdateAlias",
"lambda:GetFunctionConfiguration",
"lambda:AddPermission",
"lambda:RemovePermission",
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:*:<account_no>:function:<service_name>*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"events:Put*",
"events:Remove*",
"events:Delete*",
"events:Describe*"
],
"Resource": "arn:aws:events::<account_no>:rule/<service_name>*"
}
]
}

The <account_no> is your AWS Account ID and you can follow these instruc<ons
(h8p://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html) to look it up.

Also, recall that the <region> and <service_name> are defined in your
serverless.yml like so.
service: my-service

provider:
name: aws
region: us-east-1

In the above serverless.yml , the <region> is us-east-1 and the <service_name>


is my-service .

The above IAM policy template restricts access to the AWS services based on the name of
your Serverless project and the region it is deployed in.

It provides sufficient permissions for a minimal Serverless project. However, if you provision
any addi<onal resources in your serverless.yml, or install Serverless plugins, or invoke any
AWS APIs in your applica<on code; you would need to update the IAM policy to
accommodate for those changes. If you are looking for details on where this policy comes
from; here is an in-depth discussion on the minimal Serverless IAM Deployment Policy
(h8ps://github.com/serverless/serverless/issues/1439) required for a Serverless project.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/customize-the-
serverless-iam-policy/18)
Mapping Cognito Identity Id and
User Pool Id
If you are using the Cognito User Pool to manage your users while using the Iden7ty Pool to
secure your AWS resources; you might run into an interes7ng issue. How do you find the
user’s User Pool User Id in your Lambda func7on?

Identity Pool User Id vs User Pool User Id


You might recall (from the chapters where we work with our Lambda func7ons
(/chapters/add-a-create-note-api.html)), that we used the
event.requestContext.identity.cognitoIdentityId as the user Id. This is the Id
that a user is assigned through the Iden7ty Pool. However, you cannot use this Id to look up
informa7on for this user from the User Pool. This is because to access your Lambda func7on,
your user needs to:

1. Authen7cate through your User Pool


2. And then federate their iden7ty through the Iden7ty Pool

At this second step, their User Pool informa7on is no longer available to us. To beQer
understand this flow you can take a look at the Cognito user pool vs iden7ty pool
(/chapters/cognito-user-pool-vs-iden7ty-pool.html) chapter. But in a nutshell, you can have
mul7ple authen7ca7on providers at step 1 and the Iden7ty Pool just ensures that they are all
given a global user id that you can use.

Finding the User Pool User Id


However, you might find yourself looking for a user’s User Pool user id in your Lambda
func7on. While the process below isn’t documented, it is something we have been using and it
solves this problem preQy well.

Below is a sample Lambda func7on where we find the user’s User Pool user id.
export async function main(event, context, callback) {
const authProvider =
event.requestContext.identity.cognitoAuthenticationProvider;
// Cognito authentication provider looks like:
// cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx,cognito-
idp.us-east-1.amazonaws.com/us-east-
1_aaaaaaaaa:CognitoSignIn:qqqqqqqq-1111-2222-3333-rrrrrrrrrrrr
// Where us-east-1_aaaaaaaaa is the User Pool id
// And qqqqqqqq-1111-2222-3333-rrrrrrrrrrrr is the User Pool User Id
const parts = authProvider.split(':');
const userPoolIdParts = parts[parts.length - 3].split('/');

const userPoolId = userPoolIdParts[userPoolIdParts.length - 1];


const userPoolUserId = parts[parts.length - 1];

...
}

The event.requestContext.identity.cognitoAuthenticationProvider gives us a


string that contains the authen7ca7on details from the User Pool. Note that this info will be
different depending on the authen7ca7on provider you are using. This string has the following
format:

cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx,cognito-
idp.us-east-1.amazonaws.com/us-east-
1_aaaaaaaaa:CognitoSignIn:qqqqqqqq-1111-2222-3333-rrrrrrrrrrrr

Where us-east-1_aaaaaaaaa is the User Pool id and qqqqqqqq-1111-2222-3333-


rrrrrrrrrrrr is the User Pool User Id. We can extract these out with some simple
JavaScript as we detailed above.

And that’s it! You now have access to a user’s User Pool user Id even though we are using
AWS IAM and Federated Iden77es to secure our Lambda func7on.

For help and discussion


! Comments on this chapter
(https://discourse.serverless-stack.com/t/mapping-
cognito-identity-id-and-user-pool-id/500)
Connect to API Gateway with IAM
Auth
Connec&ng to an API Gateway endpoint secured using AWS IAM can be challenging. You
need to sign your requests using Signature Version 4
(hCp://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). You can use:

Generated API Gateway SDK


(hCps://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-
sdk.html)
AWS Amplify (hCps://github.com/aws/aws-amplify)

The generated SDK can be hard to use since you need to re-generate it every &me a change is
made. And we cover how to configure your app using AWS Amplify in the Configure AWS
Amplify (/chapters/configure-aws-amplify.html) chapter.

However if you are looking to simply connect to API Gateway using the AWS JS SDK, we’ve
create a standalone sigV4Client.js (hCps://github.com/AnomalyInnova&ons/sigV4Client)
that you can use. It is based on the client that comes pre-packaged with the generated SDK.

In this chapter we’ll go over how to use the the sigV4Client.js . The basic flow looks like
this:

1. Authen&cate a user with Cognito User Pool and acquire a user token.
2. With the user token get temporary IAM creden&als from the Iden&ty Pool.
3. Use the IAM creden&als to sign our API request with Signature Version 4
(hCp://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).

Authenticate a User with Cognito User Pool


The following method can authen&cate a user to Cognito User Pool.

function login(username, password) {


const userPool = new CognitoUserPool({
UserPoolId: USER_POOL_ID,
ClientId: APP_CLIENT_ID
});
const user = new CognitoUser({ Username: username, Pool: userPool
});
const authenticationData = { Username: username, Password: password
};
const authenticationDetails = new
AuthenticationDetails(authenticationData);

return new Promise((resolve, reject) =>


user.authenticateUser(authenticationDetails, {
onSuccess: result => resolve(),
onFailure: err => reject(err)
})
);
}

Ensure to use your USER_POOL_ID and APP_CLIENT_ID . And given their Cognito
username and password you can log a user in by calling:

await login('my_username', 'my_password');

Generate Temporary IAM Credentials


Once your user is authen&cated you can generate a set of temporary creden&als. To do so you
need to first get their JWT user token using the following:

function getUserToken(currentUser) {
return new Promise((resolve, reject) => {
currentUser.getSession(function(err, session) {
if (err) {
reject(err);
return;
}
resolve(session.getIdToken().getJwtToken());
});
});
}

Where you can get the current logged in user using:

function getCurrentUser() {
const userPool = new CognitoUserPool({
UserPoolId: config.cognito.USER_POOL_ID,
ClientId: config.cognito.APP_CLIENT_ID
});
return userPool.getCurrentUser();
}

And with the JWT token you can generate their temporary IAM creden&als using:

function getAwsCredentials(userToken) {
const authenticator = `cognito-idp.${config.cognito
.REGION}.amazonaws.com/${config.cognito.USER_POOL_ID}`;

AWS.config.update({ region: config.cognito.REGION });

AWS.config.credentials = new AWS.CognitoIdentityCredentials({


IdentityPoolId: config.cognito.IDENTITY_POOL_ID,
Logins: {
[authenticator]: userToken
}
});

return AWS.config.credentials.getPromise();
}

Sign API Gateway Requests with Signature Version 4


The sigV4Client.js needs crypto-js (hCps://github.com/brix/crypto-js) installed.

Install it by running the following in your project root.


$ npm install crypto-js --save

And to use the sigV4Client.js simply copy it over to your project.

→ sigV4Client.js
(hCps://raw.githubusercontent.com/AnomalyInnova&ons/sigV4Client/master/sigV4Client.js)

This file can look a bit in&mida&ng at first but it is just using the temporary creden&als and the
request parameters to create the necessary signed headers. To create a new sigV4Client
we need to pass in the following:

// Pseudocode

sigV4Client.newClient({
// Your AWS temporary access key
accessKey,
// Your AWS temporary secret key
secretKey,
// Your AWS temporary session token
sessionToken,
// API Gateway region
region,
// API Gateway URL
endpoint
});

And to sign a request you need to use the signRequest method and pass in:

// Pseudocode

const signedRequest = client.signRequest({


// The HTTP method
method,
// The request path
path,
// The request headers
headers,
// The request query parameters
queryParams,
// The request body
body
});

And signedRequest.headers should give you the signed headers that you need to make
the request.

Call API Gateway with the sigV4Client


Let’s put it all together. The following gives you a simple helper func&on to call an API
Gateway endpoint.

function invokeApig({
path,
method = "GET",
headers = {},
queryParams = {},
body
}) {

const currentUser = getCurrentUser();

const userToken = await getUserToken(currentUser);

await getAwsCredentials(userToken);

const signedRequest = sigV4Client


.newClient({
accessKey: AWS.config.credentials.accessKeyId,
secretKey: AWS.config.credentials.secretAccessKey,
sessionToken: AWS.config.credentials.sessionToken,
region: YOUR_API_GATEWAY_REGION,
endpoint: YOUR_API_GATEWAY_URL
})
.signRequest({
method,
path,
headers,
queryParams,
body
});

body = body ? JSON.stringify(body) : body;


headers = signedRequest.headers;

const results = await fetch(signedRequest.url, {


method,
headers,
body
});

if (results.status !== 200) {


throw new Error(await results.text());
}

return results.json();
}

Make sure to replace YOUR_API_GATEWAY_URL and YOUR_API_GATEWAY_REGION . Post in


the comments if you have any ques&ons.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/comments-
signup-with-aws-cognito/113)
Serverless Node.js Starter
Based on what we have gone through in this guide, it makes sense that we have a good
star4ng point for our future projects. For this we created a couple of Serverless starter
projects that you can use called, Serverless Node.js Starter
(h@ps://github.com/AnomalyInnova4ons/serverless-nodejs-starter). We also have a Python
version called Serverless Python Starter (h@ps://github.com/AnomalyInnova4ons/serverless-
python-starter). Our starter projects also work really well with Seed (h@ps://seed.run); a fully-
configured CI/CD pipeline for Serverless Framework.

Serverless Node.js Starter (h@ps://github.com/AnomalyInnova4ons/serverless-nodejs-starter)


uses the serverless-bundle (h@ps://github.com/AnomalyInnova4ons/serverless-bundle) plugin
(an extension of the serverless-webpack (h@ps://github.com/serverless-heaven/serverless-
webpack) plugin) and the serverless-offline (h@ps://github.com/dherault/serverless-offline)
plugin. It supports:

Genera&ng op&mized Lambda packages with Webpack


Use ES7 syntax in your handler func&ons
Run API Gateway locally
Use serverless offline start
Support for unit tests
Run npm test to run your tests
Sourcemaps for proper error messages
Error message show the correct line numbers
Works in produc4on with CloudWatch
Add environment variables for your stages
No need to manage Webpack or Babel configs

Demo
A demo version of this service is hosted on AWS - https://z6pv80ao4l.execute-
api.us-east-1.amazonaws.com/dev/hello (h@ps://z6pv80ao4l.execute-api.us-east-
1.amazonaws.com/dev/hello).
And here is the ES7 source behind it.

export const hello = async (event, context, callback) => {


const response = {
statusCode: 200,
body: JSON.stringify({
message: `Go Serverless v1.0! ${(await message({ time: 1, copy:
'Your function executed successfully!'}))}`,
input: event,
}),
};

callback(null, response);
};

const message = ({ time, ...rest }) => new Promise((resolve, reject)


=>
setTimeout(() => {
resolve(`${rest.copy} (with a delay)`);
}, time * 1000)
);

Requirements
Configure your AWS CLI (/chapters/configure-the-aws-cli.html)
Install the Serverless Framework npm install serverless -g

Installation
To create a new Serverless project.

$ serverless install --url


https://github.com/AnomalyInnovations/serverless-nodejs-starter --name
my-project

Enter the new directory.


$ cd my-project

Install the Node.js packages.

$ npm install

Usage
To run a func4on on your local

$ serverless invoke local --function hello

To simulate API Gateway locally using serverless-offline


(h@ps://github.com/dherault/serverless-offline)

$ serverless offline start

Run your tests

$ npm test

We use Jest to run our tests. You can read more about se`ng up your tests here
(h@ps://facebook.github.io/jest/docs/en/ge`ng-started.html#content).

Deploy your project

$ serverless deploy

Deploy a single func4on

$ serverless deploy function --function hello

To add environment variables to your project

1. Rename env.example to env.yml .


2. Add environment variables for the various stages to env.yml .
3. Uncomment environment: ${file(env.yml):${self:provider.stage}} in the
serverless.yml .
4. Make sure to not commit your env.yml .

So give it a try and send us an email (mailto:[email protected]) if you have any ques4ons or
open a new issue (h@ps://github.com/AnomalyInnova4ons/serverless-nodejs-
starter/issues/new) if you’ve found a bug.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/serverless-
node-js-starter/22)
Package Lambdas with serverless-
bundle
AWS Lambda func.ons are stored as zip files in an S3 bucket. They are loaded up onto a
container when the func.on is invoked. The .me it takes to do this is called the cold start
.me. If a func.on has been recently invoked, the container is kept around. In this case, your
func.ons get invoked a lot quicker and this delay is referred to as the warm start .me. One of
the factors that affects cold starts, is the size of your Lambda func.on package. The larger the
package, the longer it takes to invoke your Lambda func.on.

Optimizing Lambda Packages


Serverless Framework (hIps://github.com/serverless/serverless) handles all the packaging and
deployments for our Lambda func.ons. By default, it will create one package per service and
use that for all the Lambda func.ons in that service. This means that each Lambda func.on in
your service loads the code that is used by all the other func.ons as well! Fortunately there is
an op.on to override this.

# Create an individual package for our functions


package:
individually: true

By adding the above to your serverless.yml , you are telling Serverless Framework to
generate individual packages for each of your Lambda func.ons. Note that, this isn’t the
default behavior because individual packaging takes a lot longer. However, the performance
benefit makes this well worth it.

While individual packaging is a good start, for Node.js apps, Serverless Framework will add
your node_modules/ directory in the package. This can balloon the size of your Lambda
func.on packages astronomically. To fix this you can op.mize your packages further by using
the serverless-webpack (hIps://github.com/serverless-heaven/serverless-webpack) plugin to
apply Webpack’s tree shaking algorithm (hIps://webpack.js.org/guides/tree-shaking/) to only
include the relevant bits of code needed for your Lambda func.on. Also, with the serverless-
webpack plugin, you can use Babel (hIps://babeljs.io) to transpile your JavaScript func.ons so
that you can use a more modern syntax including import/export statements.

However, using Webpack and Babel require you to manage their respec.ve configs, plugins,
and NPM packages in your Serverless app. Addi.onally, you might want to lint your code
before your func.ons get packaged. This means that your projects can end up with a long list
of packages and config files before you even write your first line of code! And they need to be
updated over .me. Which can be really hard to do across mul.ple projects.

"eslint"
"webpack"
"@babel/core"
"babel-eslint"
"babel-loader"
"eslint-loader"
"@babel/runtime"
"@babel/preset-env"
"serverless-webpack"
"source-map-support"
"webpack-node-externals"
"eslint-config-strongloop"
"@babel/plugin-transform-runtime"
"babel-plugin-source-map-support"

We created a new plugin to solve all of these issues.

Optimized Lambda packages without any configuration


Enter serverless-bundle (hIps://github.com/AnomalyInnova.ons/serverless-bundle); a plugin
that will generate an op.mized Lambda func.on package using serverless-webpack without
you having to manage any Webpack, Babel, or ESLint configs!

- "eslint"
- "webpack"
- "@babel/core"
- "babel-eslint"
- "babel-loader"
- "eslint-loader"
- "@babel/runtime"
- "@babel/preset-env"
- "serverless-webpack"
- "source-map-support"
- "webpack-node-externals"
- "eslint-config-strongloop"
- "@babel/plugin-transform-runtime"
- "babel-plugin-source-map-support"

+ "serverless-bundle": "^1.2.2"

The serverless-bundle plugin supports:

Lin.ng via ESLint (hIps://eslint.org)


Caching for faster builds
Use ES6 import/export
Transpiling unit tests with babel-jest
(hIps://github.com/facebook/jest/tree/master/packages/babel-jest)
Adding source maps for proper error messages

To get started with serverless-bundle, simply install it:

$ npm install --save-dev serverless-bundle

Then add it to your serverless.yml .

plugins:
- serverless-bundle

And to run your tests using the same Babel config used in the plugin add the following to your
package.json :

"scripts": {
"test": "serverless-bundle test"
}

You can read more on the advanced op.ons over on the GitHub README
(hIps://github.com/AnomalyInnova.ons/serverless-bundle/blob/master/README.md).

Our ever popular Serverless Node.js Starter


(hIps://github.com/AnomalyInnova.ons/serverless-nodejs-starter) has now been updated to
use the serverless-bundle plugin.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/package-
lambdas-with-serverless-bundle/1150)
Manage User Accounts in AWS
Amplify
If you’ve followed along with Part I of the Serverless Stack (/#part-1) guide, you might be
looking to add ways your users can beCer manage their accounts. This includes the ability to:

Reset their password in case they forget it


Change their password once they are logged in
And change the email they are logging in with

As a quick refresher, we are using AWS Cognito (hCps://aws.amazon.com/cognito/) as our


authenMcaMon and user management provider. And on the frontend we are using AWS
Amplify (hCps://aws-amplify.github.io/) with our Create React App
(hCps://github.com/facebook/create-react-app).

In the next few chapters we are going to look at how to add the above funcMonality to our
Serverless notes app (hCps://demo.serverless-stack.com). For these chapters we are going to
use a forked version of the notes app. You can view the hosted version here (hCps://demo-
user-mgmt.serverless-stack.com) and the source is available in a repo here
(hCps://github.com/AnomalyInnovaMons/serverless-stack-demo-user-mgmt-client).

Let’s get started by allowing users to reset their password in case they have forgoCen it.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/manage-user-
accounts-in-aws-amplify/505)

For reference, here is the code we are using

" User Management Frontend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-user-mgmt-client)
Handle Forgot and Reset Password
In our Serverless notes app (h1ps://demo.serverless-stack.com) we’ve used Cognito User Pool
(h1ps://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-idenCty-
pools.html) to sign up and login our users. In the frontend we’ve used AWS Amplify
(h1ps://aws-amplify.github.io/) in our React app. However, if our users have forgo1en their
passwords, we need to have a way for them to reset their password. In this chapter we will
look at how to do this.

The version of the notes app used in this chapter is hosted in a:

Separate GitHub repository: h"ps://github.com/AnomalyInnova7ons/serverless-stack-


demo-user-mgmt-client (h1ps://github.com/AnomalyInnovaCons/serverless-stack-demo-
user-mgmt-client)
And can be accessed through: h"ps://demo-user-mgmt.serverless-stack.com
(h1ps://demo-user-mgmt.serverless-stack.com)

Let’s look at the main changes we need to make to allow users to reset their password.

Add a Reset Password Form


We are going to create a src/containers/ResetPassword.js .

import React, { Component } from "react";


import { Auth } from "aws-amplify";
import { Link } from "react-router-dom";
import {
HelpBlock,
FormGroup,
Glyphicon,
FormControl,
ControlLabel
} from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./ResetPassword.css";

export default class ResetPassword extends Component {


constructor(props) {
super(props);

this.state = {
code: "",
email: "",
password: "",
codeSent: false,
confirmed: false,
confirmPassword: "",
isConfirming: false,
isSendingCode: false
};
}

validateCodeForm() {
return this.state.email.length > 0;
}

validateResetForm() {
return (
this.state.code.length > 0 &&
this.state.password.length > 0 &&
this.state.password === this.state.confirmPassword
);
}

handleChange = event => {


this.setState({
[event.target.id]: event.target.value
});
};

handleSendCodeClick = async event => {


event.preventDefault();
this.setState({ isSendingCode: true });

try {
await Auth.forgotPassword(this.state.email);
this.setState({ codeSent: true });
} catch (e) {
alert(e.message);
this.setState({ isSendingCode: false });
}
};

handleConfirmClick = async event => {


event.preventDefault();

this.setState({ isConfirming: true });

try {
await Auth.forgotPasswordSubmit(
this.state.email,
this.state.code,
this.state.password
);
this.setState({ confirmed: true });
} catch (e) {
alert(e.message);
this.setState({ isConfirming: false });
}
};

renderRequestCodeForm() {
return (
<form onSubmit={this.handleSendCodeClick}>
<FormGroup bsSize="large" controlId="email">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={this.state.email}
onChange={this.handleChange}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
loadingText="Sending…"
text="Send Confirmation"
isLoading={this.state.isSendingCode}
disabled={!this.validateCodeForm()}
/>
</form>
);
}

renderConfirmationForm() {
return (
<form onSubmit={this.handleConfirmClick}>
<FormGroup bsSize="large" controlId="code">
<ControlLabel>Confirmation Code</ControlLabel>
<FormControl
autoFocus
type="tel"
value={this.state.code}
onChange={this.handleChange}
/>
<HelpBlock>
Please check your email ({this.state.email}) for the
confirmation
code.
</HelpBlock>
</FormGroup>
<hr />
<FormGroup bsSize="large" controlId="password">
<ControlLabel>New Password</ControlLabel>
<FormControl
type="password"
value={this.state.password}
onChange={this.handleChange}
/>
</FormGroup>
<FormGroup bsSize="large" controlId="confirmPassword">
<ControlLabel>Confirm Password</ControlLabel>
<FormControl
type="password"
onChange={this.handleChange}
value={this.state.confirmPassword}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
text="Confirm"
loadingText="Confirm…"
isLoading={this.state.isConfirming}
disabled={!this.validateResetForm()}
/>
</form>
);
}

renderSuccessMessage() {
return (
<div className="success">
<Glyphicon glyph="ok" />
<p>Your password has been reset.</p>
<p>
<Link to="/login">
Click here to login with your new credentials.
</Link>
</p>
</div>
);
}

render() {
return (
<div className="ResetPassword">
{!this.state.codeSent
? this.renderRequestCodeForm()
: !this.state.confirmed
? this.renderConfirmationForm()
: this.renderSuccessMessage()}
</div>
);
}
}

Let’s quickly go over the flow here:

We ask the user to put in the email address for their account in the
this.renderRequestCodeForm() .
Once the user submits this form, we start the process by calling
Auth.forgotPassword(this.state.email) . Where Auth is a part of the AWS
Amplify library.
This triggers Cognito to send a verificaCon code to the specified email address.
Then we present a form where the user can input the code that Cognito sends them. This
form is rendered in this.renderConfirmationForm() . And it also allows the user to
put in their new password.
Once they submit this form with the code and their new password, we call
Auth.forgotPasswordSubmit(this.state.email, this.state.code,
this.state.password) . This resets the password for the account.
Finally, we show the user a sign telling them that their password has been successfully
reset. We also link them to the login page where they can login using their new details.

Let’s also add a couple of styles.

Add the following to src/containers/ResetPassword.css .

@media all and (min-width: 480px) {


.ResetPassword {
padding: 60px 0;
}

.ResetPassword form {
margin: 0 auto;
max-width: 320px;
}

.ResetPassword .success {
max-width: 400px;
}
}

.ResetPassword .success {
margin: 0 auto;
text-align: center;
}
.ResetPassword .success .glyphicon {
color: grey;
font-size: 30px;
margin-bottom: 30px;
}

Add the Route


Finally, let’s link this up with the rest of our app.

Add the route to src/Routes.js .

<UnauthenticatedRoute
path="/login/reset"
exact
component={ResetPassword}
props={childProps}
/>

And import it in the header.


import ResetPassword from "./containers/ResetPassword";

Link from the Login Page


Now we want to make sure that our users are directed to this page when they are trying to
login.

So let’s add a link in our src/containers/Login.js . Add it above our login


bu1on.

<Link to="/login/reset">Forgot password?</Link>

And import the Link component in the header.

import { Link } from "react-router-dom";

That’s it! We should now be able to navigate to /login/reset or go to it from the login
page in case we need to reset our password.
And from there they can put in their email to reset their password.

Next, let’s look at how our logged in users can change their password.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/handle-forgot-
and-reset-password/506)

For reference, here is the code we are using

" User Management Frontend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-user-mgmt-client)
Allow Users to Change Passwords
For our Serverless notes app (h1ps://demo.serverless-stack.com), we want to allow our users
to change their password. Recall that we are using Cognito to manage our users and AWS
Amplify in our React app. In this chapter we will look at how to do that.

For reference, we are using a forked version of the notes app with:

A separate GitHub repository: h"ps://github.com/AnomalyInnova7ons/serverless-stack-


demo-user-mgmt-client (h1ps://github.com/AnomalyInnovaIons/serverless-stack-demo-
user-mgmt-client)
And it can be accessed through: h"ps://demo-user-mgmt.serverless-stack.com
(h1ps://demo-user-mgmt.serverless-stack.com)

Let’s start by creaIng a seLngs page that our users can use to change their password.

Add a Settings Page


Add the following to src/containers/Settings.js .

import React, { Component } from "react";


import { LinkContainer } from "react-router-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./Settings.css";

export default class Settings extends Component {


constructor(props) {
super(props);

this.state = {
};
}

render() {
return (
<div className="Settings">
<LinkContainer to="/settings/email">
<LoaderButton
block
bsSize="large"
text="Change Email"
/>
</LinkContainer>
<LinkContainer to="/settings/password">
<LoaderButton
block
bsSize="large"
text="Change Password"
/>
</LinkContainer>
</div>
);
}
}

All this does is add two links to a page that allows our users to change their password and
email.

Let’s also add a couple of styles for this page.

@media all and (min-width: 480px) {


.Settings {
padding: 60px 0;
margin: 0 auto;
max-width: 320px;
}
}
.Settings .LoaderButton:last-child {
margin-top: 15px;
}

Add a link to this seLngs page to the navbar of our app by changing src/App.js .
<Navbar fluid collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Scratch</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav pullRight>
{this.state.isAuthenticated
? <Fragment>
<LinkContainer to="/settings">
<NavItem>Settings</NavItem>
</LinkContainer>
<NavItem onClick={this.handleLogout}>Logout</NavItem>
</Fragment>
: <Fragment>
<LinkContainer to="/signup">
<NavItem>Signup</NavItem>
</LinkContainer>
<LinkContainer to="/login">
<NavItem>Login</NavItem>
</LinkContainer>
</Fragment>
}
</Nav>
</Navbar.Collapse>
</Navbar>

Also, add the route to our src/Routes.js .

<AuthenticatedRoute
path="/settings"
exact
component={Settings}
props={childProps}
/>
And don’t forget to import it.

import Settings from "./containers/Settings";

This should give us a seLngs page that our users can get to from the app navbar.

Change Password Form


Now let’s create the form that allows our users to change their password.

Add the following to src/containers/ChangePassword.js .

import React, { Component } from "react";


import { Auth } from "aws-amplify";
import { FormGroup, FormControl, ControlLabel } from "react-
bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./ChangePassword.css";
export default class ChangePassword extends Component {
constructor(props) {
super(props);

this.state = {
password: "",
oldPassword: "",
isChanging: false,
confirmPassword: ""
};
}

validateForm() {
return (
this.state.oldPassword.length > 0 &&
this.state.password.length > 0 &&
this.state.password === this.state.confirmPassword
);
}

handleChange = event => {


this.setState({
[event.target.id]: event.target.value
});
};

handleChangeClick = async event => {


event.preventDefault();

this.setState({ isChanging: true });

try {
const currentUser = await Auth.currentAuthenticatedUser();
await Auth.changePassword(
currentUser,
this.state.oldPassword,
this.state.password
);
this.props.history.push("/settings");
} catch (e) {
alert(e.message);
this.setState({ isChanging: false });
}
};

render() {
return (
<div className="ChangePassword">
<form onSubmit={this.handleChangeClick}>
<FormGroup bsSize="large" controlId="oldPassword">
<ControlLabel>Old Password</ControlLabel>
<FormControl
type="password"
onChange={this.handleChange}
value={this.state.oldPassword}
/>
</FormGroup>
<hr />
<FormGroup bsSize="large" controlId="password">
<ControlLabel>New Password</ControlLabel>
<FormControl
type="password"
value={this.state.password}
onChange={this.handleChange}
/>
</FormGroup>
<FormGroup bsSize="large" controlId="confirmPassword">
<ControlLabel>Confirm Password</ControlLabel>
<FormControl
type="password"
onChange={this.handleChange}
value={this.state.confirmPassword}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
text="Change Password"
loadingText="Changing…"
disabled={!this.validateForm()}
isLoading={this.state.isChanging}
/>
</form>
</div>
);
}
}

Most of this should be very straighPorward. The key part of the flow here is that we ask the
user for their current password along with their new password. Once they enter it, we can call
the following:

const currentUser = await Auth.currentAuthenticatedUser();


await Auth.changePassword(
currentUser,
this.state.oldPassword,
this.state.password
);

The above snippet uses the Auth module from Amplify to get the current user. And then
uses that to change their password by passing in the old and new password. Once the
Auth.changePassword method completes, we redirect the user to the seLngs page.

Let’s also add a couple of styles.

@media all and (min-width: 480px) {


.ChangePassword {
padding: 60px 0;
}

.ChangePassword form {
margin: 0 auto;
max-width: 320px;
}
}

Let’s add our new page to src/Routes.js .

<AuthenticatedRoute
path="/settings/password"
exact
component={ChangePassword}
props={childProps}
/>

And import it.

import ChangePassword from "./containers/ChangePassword";

That should do it. The /settings/password page should allow us to change our password.
Next, let’s look at how to implement a change email form for our users.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/allow-users-
to-change-passwords/507)

For reference, here is the code we are using

" User Management Frontend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-user-mgmt-client)
Allow Users to Change Their Email
We want the users of our Serverless notes app (h3ps://demo.serverless-stack.com) to be able
to change their email. Recall that we are using Cognito to manage our users and AWS Amplify
in our React app. In this chapter we will look at how to do that.

For reference, we are using a forked version of the notes app with:

A separate GitHub repository: h"ps://github.com/AnomalyInnova7ons/serverless-stack-


demo-user-mgmt-client (h3ps://github.com/AnomalyInnovaIons/serverless-stack-demo-
user-mgmt-client)
And it can be accessed through: h"ps://demo-user-mgmt.serverless-stack.com
(h3ps://demo-user-mgmt.serverless-stack.com)

In the previous chapter we created a seJngs page that links to /settings/email . Let’s
implement that.

Change Email Form


Add the following to src/containers/ChangeEmail.js .

import React, { Component } from "react";


import { Auth } from "aws-amplify";
import {
HelpBlock,
FormGroup,
FormControl,
ControlLabel
} from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./ChangeEmail.css";

export default class ChangeEmail extends Component {


constructor(props) {
super(props);

this.state = {
code: "",
email: "",
codeSent: false,
isConfirming: false,
isSendingCode: false
};
}

validatEmailForm() {
return this.state.email.length > 0;
}

validateConfirmForm() {
return this.state.code.length > 0;
}

handleChange = event => {


this.setState({
[event.target.id]: event.target.value
});
};

handleUpdateClick = async event => {


event.preventDefault();

this.setState({ isSendingCode: true });

try {
const user = await Auth.currentAuthenticatedUser();
await Auth.updateUserAttributes(user, { email: this.state.email
});

this.setState({ codeSent: true });


} catch (e) {
alert(e.message);
this.setState({ isSendingCode: false });
}
};

handleConfirmClick = async event => {


event.preventDefault();

this.setState({ isConfirming: true });

try {
await Auth.verifyCurrentUserAttributeSubmit("email",
this.state.code);

this.props.history.push("/settings");
} catch (e) {
alert(e.message);
this.setState({ isConfirming: false });
}
};

renderUpdateForm() {
return (
<form onSubmit={this.handleUpdateClick}>
<FormGroup bsSize="large" controlId="email">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={this.state.email}
onChange={this.handleChange}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
text="Update Email"
loadingText="Updating…"
disabled={!this.validatEmailForm()}
isLoading={this.state.isSendingCode}
/>
</form>
);
}

renderConfirmationForm() {
return (
<form onSubmit={this.handleConfirmClick}>
<FormGroup bsSize="large" controlId="code">
<ControlLabel>Confirmation Code</ControlLabel>
<FormControl
autoFocus
type="tel"
value={this.state.code}
onChange={this.handleChange}
/>
<HelpBlock>
Please check your email ({this.state.email}) for the
confirmation
code.
</HelpBlock>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
text="Confirm"
loadingText="Confirm…"
isLoading={this.state.isConfirming}
disabled={!this.validateConfirmForm()}
/>
</form>
);
}

render() {
return (
<div className="ChangeEmail">
{!this.state.codeSent
? this.renderUpdateForm()
: this.renderConfirmationForm()}
</div>
);
}
}

The flow for changing a user’s email is pre3y similar to how we sign a user up.

1. We ask a user to put in their new email.


2. Cognito sends them a verificaIon code.
3. They enter the code and we confirm that their email has been changed.

We start by rendering a form that asks our user to enter their new email in
this.renderUpdateForm() . Once the user submits this form, we call:

const user = await Auth.currentAuthenticatedUser();


Auth.updateUserAttributes(user, { email: this.state.email });

This gets the current user and updates their email using the Auth module from Amplify.
Next we render the form where they can enter the code in
this.renderConfirmationForm() . Upon submiJng this form we call:

Auth.verifyCurrentUserAttributeSubmit("email", this.state.code);

This confirms the change on Cognito’s side. Finally, we redirect the user to the seJngs page.

Let’s add a couple of styles to src/containers/ChangeEmail.css .

@media all and (min-width: 480px) {


.ChangeEmail {
padding: 60px 0;
}

.ChangeEmail form {
margin: 0 auto;
max-width: 320px;
}
}

Finally, let’s add our new page to src/Routes.js .

<AuthenticatedRoute
path="/settings/email"
exact
component={ChangeEmail}
props={childProps}
/>

And import it in the header.

import ChangeEmail from "./containers/ChangeEmail";

That should do it. Our users should now be able to change their email.
Finer Details
You might noIce that the change email flow is interrupted if the user does not confirm the
new email. In this case, the email appears to have been changed but Cognito marks it as not
being verified. We will let you handle this case on your own but here are a couple of hints on
how to do so.

You can get the current user’s Cognito a3ributes by calling


Auth.userAttributes(currentUser) . Looking for the email a3ribute and checking if
it is not verified using attributes["email_verified"] !== "false" .

In this case show a simple sign that allows users to resend the verificaIon code. You can
do this by calling Auth.verifyCurrentUserAttribute("email") .

Next you can simply display the confirm code form from above and follow the same flow
by calling Auth.verifyCurrentUserAttributeSubmit("email",
this.state.code) .

This can make your change email flow more robust and handle the case where a user forgets
to verify their new email.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/allow-users-
to-change-their-email/508)

For reference, here is the code we are using

" User Management Frontend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-user-mgmt-client)
Code Splitting in Create React App
Code Spli*ng is not a necessary step for building React apps. But feel free to follow along if
you are curious about what Code Spli*ng is and how it can help larger React apps.

Code Splitting
While working on React.js single page apps, there is a tendency for apps to grow quite large. A
secAon of the app (or route) might import a large number of components that are not
necessary when it first loads. This hurts the iniAal load Ame of our app.

You might have noAced that Create React App will generate one large .js file while we are
building our app. This contains all the JavaScript our app needs. But if a user is simply loading
the login page to sign in; it doesn’t make sense that we load the rest of the app with it. This
isn’t a concern early on when our app is quite small but it becomes an issue down the road. To
address this, Create React App has a very simple built-in way to split up our code. This feature
unsurprisingly, is called Code Spli*ng.

Create React App (from 1.0 onwards) allows us to dynamically import parts of our app using
the import() proposal. You can read more about it here
(hOps://facebook.github.io/react/blog/2017/05/18/whats-new-in-create-react-
app.html#code-spli*ng-with-dynamic-import).

While, the dynamic import() can be used for any component in our React app; it works
really well with React Router. Since, React Router is figuring out which component to load
based on the path; it would make sense that we dynamically import those components only
when we navigate to them.

Code Splitting and React Router v4


The usual structure used by React Router to set up rouAng for your app looks something like
this.

/* Import the components */


import Home from "./containers/Home";
import Posts from "./containers/Posts";
import NotFound from "./containers/NotFound";

/* Use components to define routes */


export default () =>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/posts/:id" exact component={Posts} />
<Route component={NotFound} />
</Switch>;

We start by imporAng the components that will respond to our routes. And then use them to
define our routes. The Switch component renders the route that matches the path.

However, we import all of the components in the route staAcally at the top. This means, that
all these components are loaded regardless of which route is matched. To implement Code
Spli*ng here we are going to want to only load the component that responds to the matched
route.

Create an Async Component


To do this we are going to dynamically import the required component.

Add the following to src/components/AsyncComponent.js .

import React, { Component } from "react";

export default function asyncComponent(importComponent) {


class AsyncComponent extends Component {
constructor(props) {
super(props);

this.state = {
component: null
};
}
async componentDidMount() {
const { default: component } = await importComponent();

this.setState({
component: component
});
}

render() {
const C = this.state.component;

return C ? <C {...this.props} /> : null;


}
}

return AsyncComponent;
}

We are doing a few things here:

1. The asyncComponent funcAon takes an argument; a funcAon ( importComponent )


that when called will dynamically import a given component. This will make more sense
below when we use asyncComponent .
2. On componentDidMount , we simply call the importComponent funcAon that is
passed in. And save the dynamically loaded component in the state.
3. Finally, we condiAonally render the component if it has completed loading. If not we
simply render null . But instead of rendering null , you could render a loading spinner.
This would give the user some feedback while a part of your app is sAll loading.

Use the Async Component


Now let’s use this component in our routes. Instead of staAcally imporAng our component.

import Home from "./containers/Home";

We are going to use the asyncComponent to dynamically import the component we want.
const AsyncHome = asyncComponent(() => import("./containers/Home"));

It’s important to note that we are not doing an import here. We are only passing in a funcAon
to asyncComponent that will dynamically import() when the AsyncHome component is
created.

Also, it might seem weird that we are passing a funcAon here. Why not just pass in a string
(say ./containers/Home ) and then do the dynamic import() inside the
AsyncComponent ? This is because we want to explicitly state the component we are
dynamically imporAng. Webpack splits our app based on this. It looks at these imports and
generates the required parts (or chunks). This was pointed out by @wSokra
(hOps://twiOer.com/wSokra/status/866703557323632640) and @dan_abramov
(hOps://twiOer.com/dan_abramov/status/866646657437491201).

We are then going to use the AsyncHome component in our routes. React Router will create
the AsyncHome component when the route is matched and that will in turn dynamically
import the Home component and conAnue just like before.

<Route path="/" exact component={AsyncHome} />

Now let’s go back to our Notes project and apply these changes.

Your src/Routes.js should look like this ader the changes.

import React from "react";


import { Route, Switch } from "react-router-dom";
import asyncComponent from "./components/AsyncComponent";
import AppliedRoute from "./components/AppliedRoute";
import AuthenticatedRoute from "./components/AuthenticatedRoute";
import UnauthenticatedRoute from "./components/UnauthenticatedRoute";

const AsyncHome = asyncComponent(() => import("./containers/Home"));


const AsyncLogin = asyncComponent(() => import("./containers/Login"));
const AsyncNotes = asyncComponent(() => import("./containers/Notes"));
const AsyncSignup = asyncComponent(() =>
import("./containers/Signup"));
const AsyncNewNote = asyncComponent(() =>
import("./containers/NewNote"));
const AsyncNotFound = asyncComponent(() =>
import("./containers/NotFound"));

export default ({ childProps }) =>


<Switch>
<AppliedRoute
path="/"
exact
component={AsyncHome}
props={childProps}
/>
<UnauthenticatedRoute
path="/login"
exact
component={AsyncLogin}
props={childProps}
/>
<UnauthenticatedRoute
path="/signup"
exact
component={AsyncSignup}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/new"
exact
component={AsyncNewNote}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/:id"
exact
component={AsyncNotes}
props={childProps}
/>
{/* Finally, catch all unmatched routes */}
<Route component={AsyncNotFound} />
</Switch>
;

It is preOy cool that with just a couple of changes, our app is all set up for code spli*ng. And
without adding a whole lot more complexity either! Here is what our src/Routes.js
looked like before.

import React from "react";


import { Route, Switch } from "react-router-dom";
import AppliedRoute from "./components/AppliedRoute";
import AuthenticatedRoute from "./components/AuthenticatedRoute";
import UnauthenticatedRoute from "./components/UnauthenticatedRoute";

import Home from "./containers/Home";


import Login from "./containers/Login";
import Notes from "./containers/Notes";
import Signup from "./containers/Signup";
import NewNote from "./containers/NewNote";
import NotFound from "./containers/NotFound";

export default ({ childProps }) =>


<Switch>
<AppliedRoute
path="/"
exact
component={Home}
props={childProps}
/>
<UnauthenticatedRoute
path="/login"
exact
component={Login}
props={childProps}
/>
<UnauthenticatedRoute
path="/signup"
exact
component={Signup}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/new"
exact
component={NewNote}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/:id"
exact
component={Notes}
props={childProps}
/>
{/* Finally, catch all unmatched routes */}
<Route component={NotFound} />
</Switch>
;

NoAce that instead of doing the staAc imports for all the containers at the top, we are creaAng
these funcAons that are going to do the dynamic imports for us when necessary.

Now if you build your app using npm run build ; you’ll see the code spli*ng in acAon.
Each of those .chunk.js files are the different dynamic import() calls that we have. Of
course, our app is quite small and the various parts that are split up are not significant at all.
However, if the page that we use to edit our note included a rich text editor; you can imagine
how that would grow in size. And it would unfortunately affect the iniAal load Ame of our app.

Now if we deploy our app using npm run deploy ; you can see the browser load the
different chunks on-demand as we browse around in the demo (hOps://demo.serverless-
stack.com).
That’s it! With just a few simple changes our app is completely set up to use the code spli*ng
feature that Create React App has.

Next Steps
Now this seems really easy to implement but you might be wondering what happens if the
request to import the new component takes too long, or fails. Or maybe you want to preload
certain components. For example, a user is on your login page about to login and you want to
preload the homepage.

It was menAoned above that you can add a loading spinner while the import is in progress. But
we can take it a step further and address some of these edge cases. There is an excellent
higher order component that does a lot of this well; it’s called react-loadable
(hOps://github.com/thejameskyle/react-loadable).

All you need to do to use it is install it.

$ npm install --save react-loadable

Use it instead of the asyncComponent that we had above.


const AsyncHome = Loadable({
loader: () => import("./containers/Home"),
loading: MyLoadingComponent
});

And AsyncHome is used exactly as before. Here the MyLoadingComponent would look
something like this.

const MyLoadingComponent = ({isLoading, error}) => {


// Handle the loading state
if (isLoading) {
return <div>Loading...</div>;
}
// Handle the error state
else if (error) {
return <div>Sorry, there was a problem loading the page.</div>;
}
else {
return null;
}
};

It’s a simple component that handles all the different edge cases gracefully.

To add preloading and to further customize this; make sure to check out the other opAons and
features that react-loadable (hOps://github.com/thejameskyle/react-loadable) has. And have
fun code spli*ng!

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/code-
splitting-in-create-react-app/98)

For reference, here is the code we are using

" Frontend Source :code-splitting-in-create-react-app


(https://github.com/AnomalyInnovations/serverless-stack-
demo-client/tree/code-splitting-in-create-react-app)
Environments in Create React App
While developing your frontend React app and working with an API backend, you’ll o=en need
to create mul?ple environments to work with. For example, you might have an environment
called dev that might be connected to the dev stage of your serverless backend. This is to
ensure that you are working in an environment that is isolated from your produc?on version.

Aside from isola?ng the resources used, having a separate environment that mimics your
produc?on version can really help with tes?ng your changes before they go live. You can take
this idea of environments further by having a staging environment that can even have
snapshots of the live database to give you as close to a produc?on setup as possible. This type
of setup can some?mes help track down bugs and issues that you might run into only on our
live environment and not on local.

In this chapter we will look at some simple ways to configure mul?ple environments in our
React app. There are many different ways to do this but here is a simple one based on what
we have built in Part 1 of this guide (/#part-1).

Custom Environment Variables


Create React App (hOps://github.com/facebookincubator/create-react-
app/blob/master/packages/react-scripts/template/README.md#adding-custom-
environment-variables) has support for custom environment variables baked into the build
system. To set a custom environment variable, simply set it while star?ng the Create React
App build process.

$ REACT_APP_TEST_VAR=123 npm start

Here REACT_APP_TEST_VAR is the custom environment variable and we are seUng it to the
value 123 . In our app we can access this variable as process.env.REACT_APP_TEST_VAR .
So the following line in our app:

console.log(process.env.REACT_APP_TEST_VAR);
Will print out 123 in our console.

Note that, these variables are embedded during build ?me. Also, only the variables that start
with REACT_APP_ are embedded in our app. All the other environment variables are ignored.

Configuring Environments
We can use this idea of custom environment variables to configure our React app for specific
environments. Say we used a custom environment variable called REACT_APP_STAGE to
denote the environment our app is in. And we wanted to configure two environments for our
app:

One that we will use for our local development and also to test before pushing it to live.
Let’s call this one dev .
And our live environment that we will only push to, once we are comfortable with our
changes. Let’s call it production .

The first thing we can do is to configure our build system with the REACT_APP_STAGE
environment variable. Currently the scripts por?on of our package.json looks
something like this:

"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"predeploy": "npm run build",
"deploy": "aws s3 sync build/ s3://YOUR_S3_DEPLOY_BUCKET_NAME",
"postdeploy": "aws cloudfront create-invalidation --distribution-id
YOUR_CF_DISTRIBUTION_ID --paths '/*' && aws cloudfront create-
invalidation --distribution-id YOUR_WWW_CF_DISTRIBUTION_ID --paths
'/*'",
"eject": "react-scripts eject"
}

Recall that the YOUR_S3_DEPLOY_BUCKET_NAME is the S3 bucket we created to host our


React app back in the Create an S3 bucket (/chapters/create-an-s3-bucket.html) chapter. And
YOUR_CF_DISTRIBUTION_ID and YOUR_WWW_CF_DISTRIBUTION_ID are the CloudFront
Distribu?ons for the apex (/chapters/create-a-cloudfront-distribu?on.html) and www
(/chapters/setup-www-domain-redirect.html) domains.

Here we only have one environment and we use it for our local development and on live. The
npm start command runs our local server and npm run deploy command deploys our
app to live.

To set our two environments we can change this to:

"scripts": {
"start": "REACT_APP_STAGE=dev react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",

"predeploy": "REACT_APP_STAGE=dev npm run build",


"deploy": "aws s3 sync build/ s3://YOUR_DEV_S3_DEPLOY_BUCKET_NAME",
"postdeploy": "aws cloudfront create-invalidation --distribution-id
YOUR_DEV_CF_DISTRIBUTION_ID --paths '/*' && aws cloudfront create-
invalidation --distribution-id YOUR_DEV_WWW_CF_DISTRIBUTION_ID --paths
'/*'",

"predeploy:prod": "REACT_APP_STAGE=production npm run build",


"deploy:prod": "aws s3 sync build/
s3://YOUR_PROD_S3_DEPLOY_BUCKET_NAME",
"postdeploy:prod": "aws cloudfront create-invalidation --
distribution-id YOUR_PROD_CF_DISTRIBUTION_ID --paths '/*' && aws
cloudfront create-invalidation --distribution-id
YOUR_PROD_WWW_CF_DISTRIBUTION_ID --paths '/*'",

"eject": "react-scripts eject"


}

We are doing a few things of note here:

1. We use the REACT_APP_STAGE=dev for our npm start command.


2. We also have dev versions of our S3 and CloudFront Distribu?ons called
YOUR_DEV_S3_DEPLOY_BUCKET_NAME , YOUR_DEV_CF_DISTRIBUTION_ID , and
YOUR_DEV_WWW_CF_DISTRIBUTION_ID .
3. We default npm run deploy to the dev environment and dev versions of our S3 and
CloudFront Distribu?ons. We also build using the REACT_APP_STAGE=dev environment
variable.
4. We have produc?on versions of our S3 and CloudFront Distribu?ons called
YOUR_PROD_S3_DEPLOY_BUCKET_NAME , YOUR_PROD_CF_DISTRIBUTION_ID , and
YOUR_PROD_WWW_CF_DISTRIBUTION_ID .
5. Finally, we create a specific version of the deploy script for the produc?on environment
with npm run deploy:prod . And just like the dev version of this command, it builds
using the REACT_APP_STAGE=production environment variable and the produc?on
versions of the S3 and CloudFront Distribu?ons.

Note that you don’t have to replicate the S3 and CloudFront Distribu?ons for the dev version.
But it does help if you want to mimic the live version as much as possible.

Using Environment Variables


Now that we have our build commands set up with the custom environment variables, we are
ready to use them in our app.

Currently, our src/config.js looks something like this:

export default {
MAX_ATTACHMENT_SIZE: 5000000,
s3: {
BUCKET: "YOUR_S3_UPLOADS_BUCKET_NAME"
},
apiGateway: {
REGION: "YOUR_API_GATEWAY_REGION",
URL: "YOUR_API_GATEWAY_URL"
},
cognito: {
REGION: "YOUR_COGNITO_REGION",
USER_POOL_ID: "YOUR_COGNITO_USER_POOL_ID",
APP_CLIENT_ID: "YOUR_COGNITO_APP_CLIENT_ID",
IDENTITY_POOL_ID: "YOUR_IDENTITY_POOL_ID"
}
};
To use the REACT_APP_STAGE variable, we are just going to set the config condi?onally.

const dev = {
s3: {
BUCKET: "YOUR_DEV_S3_UPLOADS_BUCKET_NAME"
},
apiGateway: {
REGION: "YOUR_DEV_API_GATEWAY_REGION",
URL: "YOUR_DEV_API_GATEWAY_URL"
},
cognito: {
REGION: "YOUR_DEV_COGNITO_REGION",
USER_POOL_ID: "YOUR_DEV_COGNITO_USER_POOL_ID",
APP_CLIENT_ID: "YOUR_DEV_COGNITO_APP_CLIENT_ID",
IDENTITY_POOL_ID: "YOUR_DEV_IDENTITY_POOL_ID"
}
};

const prod = {
s3: {
BUCKET: "YOUR_PROD_S3_UPLOADS_BUCKET_NAME"
},
apiGateway: {
REGION: "YOUR_PROD_API_GATEWAY_REGION",
URL: "YOUR_PROD_API_GATEWAY_URL"
},
cognito: {
REGION: "YOUR_PROD_COGNITO_REGION",
USER_POOL_ID: "YOUR_PROD_COGNITO_USER_POOL_ID",
APP_CLIENT_ID: "YOUR_PROD_COGNITO_APP_CLIENT_ID",
IDENTITY_POOL_ID: "YOUR_PROD_IDENTITY_POOL_ID"
}
};

const config = process.env.REACT_APP_STAGE === 'production'


? prod
: dev;
export default {
// Add common config values here
MAX_ATTACHMENT_SIZE: 5000000,
...config
};

This is preOy straigh`orward. We simply have a set of configs for dev and for produc?on. The
configs point to a separate set of resources for our dev and produc?on environments. And
using process.env.REACT_APP_STAGE we decide which one to use.

Again, it might not be necessary to replicate the resources for each of the environments. But it
is preOy important to separate your live resources from your dev ones. You do not want to be
tes?ng your changes directly on your live database.

So to recap:

The REACT_APP_STAGE custom environment variable is set to either dev or


production .
While working locally we use the npm start command which uses our dev
environment.
The npm run deploy command then deploys by default to dev.
Once we are comfortable with the dev version, we can deploy to produc?on using the
npm run deploy:prod command.

This en?re setup is fairly straigh`orward and can be extended to mul?ple environments. You
can read more on custom environment variables in Create React App here
(hOps://github.com/facebookincubator/create-react-app/blob/master/packages/react-
scripts/template/README.md#adding-custom-environment-variables).

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/environments-
in-create-react-app/30)
Facebook Login with Cognito using
AWS Amplify
In our guide so far we have used the Cognito User Pool
(h7ps://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-iden@ty-
pools.html) to sign up users to our demo notes app (h7ps://demo.serverless-stack.com). This
means that our users have to sign up for an account with their email and password. But you
might want your users to use their Facebook or Google account to sign up for your app. It also
means that your users won’t have to remember another email and password combina@on for
the sites they use. In this chapter we will look at how to add a “Login with Facebook” op@on to
our demo app.

The version of the notes app used in this chapter is hosted in :

A separate GitHub repository: h"ps://github.com/AnomalyInnova7ons/serverless-stack-


demo-=-login-client (h7ps://github.com/AnomalyInnova@ons/serverless-stack-demo-L-
login-client)
And can be accessed through: h"ps://demo-=-login.serverless-stack.com (h7ps://demo-
L-login.serverless-stack.com)

The main ideas and code for this chapter have been contributed by our long @me reader and
contributor Peter Eman Paver Abas@llas (h7ps://github.com/jatazoulja).

To get started let’s create a Facebook app that our users will use to login.

Creating a Facebook App


Head over to h7p://developers.facebook.com/ (h7p://developers.facebook.com/) and create a
new app by clicking My Apps > Add New App.
Under Facebook Login, select Set Up.
And select Web.
In the first step of the Quickstart, set the URL for your app to be http://localhost:3000 .
Or https://localhost:3000 if you use the HTTPS op@on in Create React App
(h7ps://github.com/facebook/create-react-app/blob/master/packages/react-
scripts/template/README.md#using-h7ps-in-development). Hit Save.
You can hit Con7nue to go through the rest of the Quickstart.
Finally, head over to SeHngs > Basic and make a note of your App ID.
We are going to need this when we configure the AWS and React por@on of our app.

Next we are going to use Cognito Iden@ty Pool


(h7ps://docs.aws.amazon.com/cognito/latest/developerguide/[email protected]) to
federate our iden@@es. This means that when a user signs up with their Facebook account,
they will get added to our Iden@ty Pool. And our Serverless Backend API will get an Id that we
can use. This Id will remain the same if the user signs in later at any point. If you are a li7le
confused about how the Iden@ty Pool is different from the User Pool, you can take a quick
look at our Cognito user pool vs iden@ty pool (/chapters/cognito-user-pool-vs-iden@ty-
pool.html) chapter.

Add Facebook as an Authentication Provider


Head over to your AWS Console (h7ps://console.aws.amazon.com), and go to Cognito and
click Manage Iden7ty Pools.
Select the Iden@ty Pool that you are using for your app.
Hit Edit iden7ty pool from the top.
Scroll down and expand the Authen7ca7on providers.
You’ll no@ce that you have Cognito as the default op@on. Select the Facebook tab. And Hit
Unlock and paste your Facebook App ID from above.
And scroll down and hit Save Changes.
Now that we have the AWS side configured, let’s head over to our React app.

Configure Facebook Login with AWS Amplify


In our React app we are going to use the Facebook JS SDK and AWS Amplify to configure our
Facebook login. A working version of our app is available in the GitHub repo here
(h7ps://github.com/AnomalyInnova@ons/serverless-stack-demo-L-login-client).

Let’s take a quick look at the key changes that were made.

To start we add our Facebook App ID to our src/config.js . So it should look


something like this.

export default {
s3: {
REGION: "YOUR_S3_UPLOADS_BUCKET_REGION",
BUCKET: "YOUR_S3_UPLOADS_BUCKET_NAME"
},
apiGateway: {
REGION: "YOUR_API_GATEWAY_REGION",
URL: "YOUR_API_GATEWAY_URL"
},
cognito: {
REGION: "YOUR_COGNITO_REGION",
USER_POOL_ID: "YOUR_COGNITO_USER_POOL_ID",
APP_CLIENT_ID: "YOUR_COGNITO_APP_CLIENT_ID",
IDENTITY_POOL_ID: "YOUR_IDENTITY_POOL_ID"
},
social: {
FB: "YOUR_FACEBOOK_APP_ID"
}
};

Make sure to replace YOUR_FACEBOOK_APP_ID with the one from above.

Next we load the Facebook JS SDK in the our src/App.js in the


componentDidMount method.

async componentDidMount() {
this.loadFacebookSDK();

try {
await Auth.currentAuthenticatedUser();
this.userHasAuthenticated(true);
} catch (e) {
if (e !== "not authenticated") {
alert(e);
}
}

this.setState({ isAuthenticating: false });


}

loadFacebookSDK() {
window.fbAsyncInit = function() {
window.FB.init({
appId : config.social.FB,
autoLogAppEvents : true,
xfbml : true,
version : 'v3.1'
});
};

(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "https://connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
}

And we also load the current authen@cated user using the


Auth.currentAuthenticatedUser method. Where Auth is a part of the AWS Amplify
package.

Make sure to import the config at the top of src/App.js .

import config from "./config";

Now we’ll create a Facebook login bu7on component in


src/components/FacebookButton.js .

import React, { Component } from "react";


import { Auth } from "aws-amplify";
import LoaderButton from "./LoaderButton";

function waitForInit() {
return new Promise((res, rej) => {
const hasFbLoaded = () => {
if (window.FB) {
res();
} else {
setTimeout(hasFbLoaded, 300);
}
};
hasFbLoaded();
});
}

export default class FacebookButton extends Component {


constructor(props) {
super(props);

this.state = {
isLoading: true
};
}

async componentDidMount() {
await waitForInit();
this.setState({ isLoading: false });
}

statusChangeCallback = response => {


if (response.status === "connected") {
this.handleResponse(response.authResponse);
} else {
this.handleError(response);
}
};

checkLoginState = () => {
window.FB.getLoginStatus(this.statusChangeCallback);
};

handleClick = () => {
window.FB.login(this.checkLoginState, {scope:
"public_profile,email"});
};

handleError(error) {
alert(error);
}

async handleResponse(data) {
const { email, accessToken: token, expiresIn } = data;
const expires_at = expiresIn * 1000 + new Date().getTime();
const user = { email };

this.setState({ isLoading: true });

try {
const response = await Auth.federatedSignIn(
"facebook",
{ token, expires_at },
user
);
this.setState({ isLoading: false });
this.props.onLogin(response);
} catch (e) {
this.setState({ isLoading: false });
this.handleError(e);
}
}

render() {
return (
<LoaderButton
block
bsSize="large"
bsStyle="primary"
className="FacebookButton"
text="Login with Facebook"
onClick={this.handleClick}
disabled={this.state.isLoading}
/>
);
}
}
Let’s look at what we are doing here very quickly.

1. We first wait for the Facebook JS SDK to load in the waitForInit method. Once it has
loaded, we enable the Login with Facebook bu7on.

2. Once our user clicks the bu7on, we kick off the login process using FB.login and listen
for the login status to change in the statusChangeCallback . While calling this
method, we are specifying that we want the user’s public profile and email address by
sedng {scope: "public_profile,email"} .

3. If the user has given our app the permissions, then we use the informa@on we receive
from Facebook (the user’s email) and call the Auth.federatedSignIn AWS Amplify
method. This effec@vely logs the user in.

Finally, we can use the FacebookButton.js in our


src/containers/Login.js and src/containers/Signup.js .

<FacebookButton
onLogin={this.handleFbLogin}
/>
<hr />

Add the bu7on above our login and signup form. And don’t forget to import it using import
FacebookButton from "../components/FacebookButton"; .

Also, add the handler method as well.

handleFbLogin = () => {
this.props.userHasAuthenticated(true);
};

The above logs the user in to our React app, once the Facebook sign up process is complete.
Make sure to add these to src/containers/Signup.js as well.

And that’s it, if you head over to your app you should see the login with Facebook op@on.
Clicking on it should bring up the Facebook dialog asking you to login with your app.
Once you are logged in, you should be able to interact with the app just as before.
A final note on deploying your app. You might recall from above that we are telling Facebook
to use the https://localhost:3000 URL. This needs to be changed to the live URL once
you deploy your React app. A good prac@ce here is to create two Facebook apps, one for your
live users and one for your local tes@ng. That way you won’t need to change the URL and you
will have an environment where you can test your changes.

For help and discussion

! Comments on this chapter


(https://discourse.serverless-stack.com/t/facebook-
login-with-cognito-using-aws-amplify/466)

For reference, here is the code we are using

" Facebook Login Frontend Source


(https://github.com/AnomalyInnovations/serverless-stack-
demo-fb-login-client)

You might also like