diff --git a/.bundle/config b/.bundle/config
index 2369228816..9bc01b4c32 100644
--- a/.bundle/config
+++ b/.bundle/config
@@ -1,2 +1,3 @@
---
BUNDLE_PATH: "vendor/bundle"
+BUNDLE_DISABLE_SHARED_GEMS: "true"
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 5dcf83668b..f0cfb5b45e 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,4 +1,5 @@
# These are supported funding model platforms
# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+github: jayair
patreon: serverless_stack
diff --git a/.github/workflows/ebook.yml b/.github/workflows/ebook.yml
new file mode 100644
index 0000000000..1d081ec3e7
--- /dev/null
+++ b/.github/workflows/ebook.yml
@@ -0,0 +1,89 @@
+name: Ebook
+
+on:
+ push:
+ branches: [ master ]
+ tags: [ v* ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.0.7'
+ - name: Get git tag
+ id: get_ref
+ run: echo ::set-output name=REF::${GITHUB_REF/refs\/tags\//}
+
+ - name: Assign book version
+ id: get_book_version
+ run: echo ::set-output name=BOOK_VERSION::${{ steps.get_ref.outputs.REF }}
+
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: us-east-1
+
+ - uses: actions/checkout@v2
+ - run: sudo apt-get update
+ - run: sudo apt-get install -y wget texlive-xetex texlive-fonts-extra
+ - run: sudo wget https://github.com/jgm/pandoc/releases/download/2.10.1/pandoc-2.10.1-linux-amd64.tar.gz
+ - run: sudo tar xvzf pandoc-2.10.1-linux-amd64.tar.gz --strip-components 1 -C /usr
+ - run: sudo mkdir -p ~/.fonts
+ - run: sudo cp etc/ebook/fonts/SourceSansPro/*.ttf ~/.fonts
+ - run: sudo cp etc/ebook/fonts/RobotoSlab/*.ttf ~/.fonts
+ - run: sudo cp etc/ebook/fonts/SourceCodePro/*.ttf ~/.fonts
+ - run: cd etc/ebook && make prepare
+
+ # Merge PDF
+ - run: cd etc/ebook && make merge-pdf
+ env:
+ BOOK_VERSION: ${{ steps.get_book_version.outputs.BOOK_VERSION }}
+ - name: Archive pdf md
+ uses: actions/upload-artifact@v4
+ with:
+ name: pdf.md
+ path: etc/ebook/output/pdf.md
+
+ # Merge EPUB
+ - run: cd etc/ebook && make merge-epub
+ env:
+ BOOK_VERSION: ${{ steps.get_book_version.outputs.BOOK_VERSION }}
+ - name: Archive epub md
+ uses: actions/upload-artifact@v4
+ with:
+ name: epub.md
+ path: etc/ebook/output/epub.md
+
+ # Generate PDF
+ - run: cd etc/ebook && make build-pdf
+ env:
+ BOOK_VERSION: ${{ steps.get_book_version.outputs.BOOK_VERSION }}
+ - name: Archive pdf
+ uses: actions/upload-artifact@v4
+ with:
+ name: ebook.pdf
+ path: etc/ebook/output/ebook.pdf
+
+ # Generate EPUB
+ - run: cd etc/ebook && make build-epub
+ env:
+ BOOK_VERSION: ${{ steps.get_book_version.outputs.BOOK_VERSION }}
+ - name: Archive epub
+ uses: actions/upload-artifact@v4
+ with:
+ name: ebook.epub
+ path: etc/ebook/output/ebook.epub
+
+ # Upload PDF
+ - name: Upload book to S3
+ run: sudo apt-get install -y awscli && aws s3 cp etc/ebook/output/ebook.pdf s3://anomaly/SST/guide/ebook-${{ steps.get_book_version.outputs.BOOK_VERSION }}.pdf --acl public-read --content-disposition 'attachment; filename="sst-guide-${{ steps.get_book_version.outputs.BOOK_VERSION }}.pdf"'
+ if: ${{ startsWith(steps.get_ref.outputs.REF, 'v' ) }}
+
+ # Upload EPUB
+ - name: Upload book to S3
+ run: sudo apt-get install -y awscli && aws s3 cp etc/ebook/output/ebook.epub s3://anomaly/SST/guide/ebook-${{ steps.get_book_version.outputs.BOOK_VERSION }}.epub --acl public-read --content-disposition 'attachment; filename="sst-guide-${{ steps.get_book_version.outputs.BOOK_VERSION }}.epub"'
+ if: ${{ startsWith(steps.get_ref.outputs.REF, 'v' ) }}
diff --git a/.gitignore b/.gitignore
index 10b5574a3e..bef1f633c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,12 @@
Session.vim
+.sst/
+node_modules/
_site
.sw*
.*.sw*
.sass-cache
.jekyll-metadata
+.jekyll-cache/
.DS_Store
-vendor/
\ No newline at end of file
+vendor/
+.idea/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1890a7ae93..b772a8ed4e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,12 +1,12 @@
# Contributing
-Thank you for considering to contribute. Here is what Serverless Stack is trying to accomplish and how you can help. We use our [Gitter chat room][Gitter] for our contributors, feel free to join us there.
+Thank you for considering to contribute. Here is what SST Guide is trying to accomplish and how you can help. We use [Discord][discord] for our contributors, feel free to join us there.
## Project Goals
We are trying to create a really comprehensive and up to date guide to help people build production ready full-stack serverless applications. To start, we are focussing on building a CRUD API backend with Serverless Framework on AWS and a single-page app web frontend using React.
-We want Serverless Stack to cover a very broad collection of technologies, services, and concepts that gives people the confidence to use this guide to build their next project. Too often we come across tutorials or blog posts that sound very promising but leave us hanging once we get to the complicated bits.
+We want SST Guide to cover a very broad collection of technologies, services, and concepts that gives people the confidence to use this guide to build their next project. Too often we come across tutorials or blog posts that sound very promising but leave us hanging once we get to the complicated bits.
However while we want to be as comprehensive as possible, we also want to make sure that you can get started easily. To achieve this we are using the following structure and layout.
@@ -14,18 +14,22 @@ However while we want to be as comprehensive as possible, we also want to make s
The guide is split up in the following way:
-The Core:
-- Part I: The basic aspects of creating a full-stack serverless app
-- Part II: Some advanced concepts when you are getting your app ready for production
+The Basics:
+
+- The basic aspects of creating a full-stack serverless app. And deploying your app to production.
+
+Best Practices:
+
+- Everything you need to run your app in production.
The Extensions
-- Extra Credits: Standalone chapters/articles intended to supplement the first two parts and to extend some functionality of the demo app
+
+- Extra Credits: Standalone chapters/articles intended to supplement the first two parts and to extend some functionality of the demo app.
Additionally, the demo app that people build as a part of the tutorial is split into the backend (a Serverless Framework project) and the frontend (a React app). Both these are in their separate Git repos.
Key chapters in the Core part of the guide are accompanied with branches in their respective demo app repos. This is used to track progress and as a reference as you work through the tutorial.
-
## How to Help
There are quite a few ways to help.
@@ -39,10 +43,9 @@ There are quite a few ways to help.
Additionally, you are welcome to provide general feedback and suggestions via our forums.
-
### Keep the core guide updated
-Serverless Stack is reliant on a large number of services and open source libraries and projects. Here is what needs updating:
+The SST Guide is reliant on a large number of services and open source libraries and projects. Here is what needs updating:
### Updating Screenshots
@@ -69,12 +72,11 @@ For the steps in the tutorial:
Once all the PRs are merged, we'll tag the repo (tutorial & demo app) with the new version number and update the Changelog chapter.
-
### 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. Additionally, there are cases that we just don't handle in the core chapters. [Here is a rough list of topics that have been requested](https://github.com/AnomalyInnovations/serverless-stack-com/projects/1#column-2785572). This is not an exhaustive list. If you have some ideas to extend some of the demo app functionality, feel free to get in touch with us. Here is how to go about adding a new extra credit chapter:
+The core chapters are missing some extra details (for the sake of simplicity) that are necessary once you start customizing SST setup. Additionally, there are cases that we just don't handle in the core chapters. [Here is a rough list of topics that have been requested](https://github.com/AnomalyInnovations/serverless-stack-com/projects/1#column-2785572). This is not an exhaustive list. If you have some ideas to extend some of the demo app functionality, feel free to get in touch with us. Here is how to go about adding a new extra credit chapter:
-- Let us know via [Gitter][Gitter] that you are planning to work on it
+- Let us know via [Discord][discord] that you are planning to work on it
- Create a new issue in GitHub to track progress
- Fork the tutorial repo
- Copy the `_drafts/template.md` as a starting point for the chapter
@@ -93,23 +95,18 @@ For any changes to the demo app:
Finally, submit a PR to the tutorial repo with the new changes. We'll review it, maybe suggest some edits or give you some feedback. Once everything looks okay we'll merge with master and publish it. We'll also create comments threads for your chapter in the forums and link to it.
-
### Improve Tooling
Currently we do a lot of manual work to publish updates and maintain the tutorial. You can help by contributing to improve the process. Feel free to get in touch if you're interested in helping out. Here is roughly what we need help with:
-- Generating the Ebook
-
- The PDF version of Serverless Stack is very popular. Unfortunately it is generated manually using a set of AppleScripts stored in the `etc/` directory. It opens up Safari and prints to PDF. It would be much better if we could use a headless Chrome script to generate this. In addition to the PDF we need to figure out how to generate the EPUB format.
-
-- Creating a pipeline
-
- We would like to create a Circle CI setup that automatically generates the PDF and uploads the latest version to S3 (where it is hosted) every time a new release is created to the tutorial. We would also like to run a simple suite of tests to ensure that the changes to the demo app repos are correct.
-
- Compress screenshots
The images for the screenshots are quite large. It would be ideal if they can be compressed as a part of the build process.
+- ~~Generating the Ebook~~
+
+- ~~Creating a pipeline~~
+
### Translating to Other Languages
We currently have translation efforts for Spanish and Portuguese underway. If you'd like to get involved [refer to this thread](https://github.com/AnomalyInnovations/serverless-stack-com/issues/271).
@@ -118,12 +115,12 @@ To translate a chapter follow these steps:
1. Add the following to the frontmatter of the chapter you intend to translate.
- ``` yml
+ ```yml
ref: uri-of-the-chapter
lang: en
```
-
- Here `uri-of-the-chapter` is the part of the url that represents the name of the chapter. For example, the [What is Serverless](https://serverless-stack.com/chapters/what-is-serverless.html) has a URI `what-is-serverless`.
+
+ Here `uri-of-the-chapter` is the part of the url that represents the name of the chapter. For example, the [What is Serverless](https://sst.dev/chapters/what-is-serverless.html) has a URI `what-is-serverless`.
2. Copy the file to `_chapters/[language-code]/[filename].md`
@@ -131,21 +128,19 @@ To translate a chapter follow these steps:
3. Change the frontmatter to.
- ``` yml
+ ```yml
lang: language-code
```
-
+
Again the `language-code` is either `pt` or `es`.
-
Note that the only thing linking the translation with the original is the `ref:` attribute in the frontmatter. Make sure that it is the same for both the files.
-As an example, compare the [What is Serverless](https://serverless-stack.com/chapters/what-is-serverless.html) chapter:
+As an example, compare the [What is Serverless](https://sst.dev/chapters/what-is-serverless.html) chapter:
- English version: https://github.com/AnomalyInnovations/serverless-stack-com/blob/master/_chapters/what-is-serverless.md
- Spanish version: https://github.com/AnomalyInnovations/serverless-stack-com/blob/master/_chapters/es/what-is-serverless.md
Feel free to [contact us](mailto:contact@anoma.ly) if you have any questions.
-
-[Gitter]: https://gitter.im/serverless-stack/Lobby
+[discord]: https://sst.dev/discord
diff --git a/Gemfile b/Gemfile
index 7bdeaba10d..ce74304526 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,7 +9,7 @@ ruby RUBY_VERSION
#
# This will help ensure the proper Jekyll version is running.
# Happy Jekylling!
-gem "jekyll", "3.8.1"
+gem "jekyll", "4.3.2"
# This is the default theme for new Jekyll sites. You may change this to anything you like.
gem "minima", "~> 2.0"
@@ -21,7 +21,8 @@ gem "minima", "~> 2.0"
# If you have any plugins, put them here!
group :jekyll_plugins do
- gem "jekyll-seo-tag"
gem "jekyll-sitemap"
gem "jekyll-redirect-from"
end
+
+gem "webrick", "~> 1.7"
diff --git a/Gemfile.lock b/Gemfile.lock
index 9a3cb10438..7f561fa3a6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,77 +1,94 @@
GEM
remote: https://rubygems.org/
specs:
- addressable (2.5.2)
- public_suffix (>= 2.0.2, < 4.0)
+ addressable (2.8.5)
+ public_suffix (>= 2.0.2, < 6.0)
colorator (1.1.0)
- concurrent-ruby (1.0.5)
- em-websocket (0.5.1)
+ concurrent-ruby (1.2.2)
+ em-websocket (0.5.3)
eventmachine (>= 0.12.9)
- http_parser.rb (~> 0.6.0)
+ http_parser.rb (~> 0)
eventmachine (1.2.7)
- ffi (1.9.23)
+ ffi (1.16.3)
forwardable-extended (2.6.0)
- http_parser.rb (0.6.0)
- i18n (0.9.5)
+ google-protobuf (3.24.4-arm64-darwin)
+ google-protobuf (3.24.4-x86_64-darwin)
+ http_parser.rb (0.8.0)
+ i18n (1.14.1)
concurrent-ruby (~> 1.0)
- jekyll (3.8.1)
+ jekyll (4.3.2)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
- i18n (~> 0.7)
- jekyll-sass-converter (~> 1.0)
+ i18n (~> 1.0)
+ jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
- kramdown (~> 1.14)
+ kramdown (~> 2.3, >= 2.3.1)
+ kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
- mercenary (~> 0.3.3)
+ mercenary (>= 0.3.6, < 0.5)
pathutil (~> 0.9)
- rouge (>= 1.7, < 4)
+ rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
- jekyll-redirect-from (0.11.0)
- jekyll (>= 2.0)
- jekyll-sass-converter (1.5.2)
- sass (~> 3.4)
- jekyll-seo-tag (2.1.0)
- jekyll (~> 3.3)
- jekyll-sitemap (0.12.0)
- jekyll (~> 3.3)
- jekyll-watch (2.0.0)
+ terminal-table (>= 1.8, < 4.0)
+ webrick (~> 1.7)
+ jekyll-feed (0.16.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-redirect-from (0.16.0)
+ jekyll (>= 3.3, < 5.0)
+ jekyll-sass-converter (3.0.0)
+ sass-embedded (~> 1.54)
+ jekyll-seo-tag (2.8.0)
+ jekyll (>= 3.8, < 5.0)
+ jekyll-sitemap (1.4.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-watch (2.2.1)
listen (~> 3.0)
- kramdown (1.16.2)
- liquid (4.0.0)
- listen (3.1.5)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
- ruby_dep (~> 1.2)
- mercenary (0.3.6)
- minima (2.0.0)
- pathutil (0.16.1)
+ kramdown (2.4.0)
+ rexml
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
+ liquid (4.0.4)
+ listen (3.8.0)
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
+ mercenary (0.4.0)
+ minima (2.5.1)
+ jekyll (>= 3.5, < 5.0)
+ jekyll-feed (~> 0.9)
+ jekyll-seo-tag (~> 2.1)
+ pathutil (0.16.2)
forwardable-extended (~> 2.6)
- public_suffix (3.0.2)
- rb-fsevent (0.10.3)
- rb-inotify (0.9.10)
- ffi (>= 0.5.0, < 2)
- rouge (3.1.1)
- ruby_dep (1.5.0)
- safe_yaml (1.0.4)
- sass (3.5.6)
- sass-listen (~> 4.0.0)
- sass-listen (4.0.0)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
+ public_suffix (5.0.3)
+ rb-fsevent (0.11.2)
+ rb-inotify (0.10.1)
+ ffi (~> 1.0)
+ rexml (3.2.6)
+ rouge (4.1.3)
+ safe_yaml (1.0.5)
+ sass-embedded (1.69.4-arm64-darwin)
+ google-protobuf (~> 3.23)
+ sass-embedded (1.69.4-x86_64-darwin)
+ google-protobuf (~> 3.23)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ unicode-display_width (2.5.0)
+ webrick (1.7.0)
PLATFORMS
- ruby
+ arm64-darwin-21
+ arm64-darwin-23
+ x86_64-darwin-22
DEPENDENCIES
- jekyll (= 3.8.1)
+ jekyll (= 4.3.2)
jekyll-redirect-from
- jekyll-seo-tag
jekyll-sitemap
minima (~> 2.0)
+ webrick (~> 1.7)
RUBY VERSION
- ruby 2.4.0p0
+ ruby 3.0.7p220
BUNDLED WITH
- 1.15.4
+ 2.3.20
diff --git a/README.md b/README.md
index 38ddc6d88a..43934f9a9b 100644
--- a/README.md
+++ b/README.md
@@ -1,69 +1,71 @@
- Learn to Build Full-Stack Apps with Serverless and React
-
-
-
-
+
+
+
-------------------------------------------------------------------------------------
+---
+
+## SST Guide
+
+This repo for [**guide.sst.dev**](https://guide.sst.dev), the SST Guide. If you are looking for the SST repo, [head over here](https://github.com/sst/sst).
-[Serverless Stack](https://serverless-stack.com) is an open source guide for building and deploying full-stack apps using Serverless and React on AWS.
+SST.dev is built with [SST](https://sst.dev) and deployed with [Seed](https://seed.run).
-We are going to create a [note taking app](https://demo2.serverless-stack.com) from scratch using React.js, AWS Lambda, API Gateway, DynamoDB, and Cognito.
+## About the Guide
+
+The guide is a comprehensive open source tutorial for building and deploying full-stack apps using Serverless and React on AWS.
+
+We are going to create a [note taking app](https://demo.sst.dev) from scratch using React.js, AWS Lambda, API Gateway, DynamoDB, and Cognito.

It is a single-page React app powered by a serverless CRUD API. We also cover how add user authentication and handle file uploads.
-The entire guide is hosted on GitHub and we use [Discourse][Discourse] for our comments. With the help of the community we add more detail to the guide and keep it up to date.
-
-## Project Goals
+### Project Goals
- Provide a free comprehensive resource
- Add more content to build on core concepts
- Keep the content accurate and up to date
- Help people resolve their issues
-## Getting Help
+### Getting Help
-- If you are running into issues with a specific chapter, post in the comments for that [chapter][Discourse].
+- If you are running into issues with a specific chapter, join us on [Discord][discord].
- Open a [new issue](../../issues/new) if you've found a bug
-- Or if you have a suggestion create a [new topic][Discourse] in our forums
-- If you've found a typo, edit the chapter and submit a [pull request][PR].
+- If you've found a typo, edit the chapter and submit a [pull request][pr].
-## Source for the Demo App
+#### Source for the Demo App
-- [Backend Serverless API](https://github.com/AnomalyInnovations/serverless-stack-demo-api)
-- [Frontend React app](https://github.com/AnomalyInnovations/serverless-stack-demo-client)
+- [Demo Notes App](https://github.com/sst/demo-notes-app)
## Contributing
-Thank you for your considering to contribute. [Read more about how you can contribute to Serverless Stack][Contributing].
+[Read more about how you can contribute to the guide][contributing].
-## Running Locally
+### Running Locally
-Serverless Stack is built using [Jekyll](https://jekyllrb.com). [Follow these steps to install Jekyll](https://jekyllrb.com/docs/installation/).
+SST.dev is a [Jekyll](https://jekyllrb.com) site. [Follow these steps to install Jekyll](https://jekyllrb.com/docs/installation/).
-#### Viewing Locally
+### Viewing Locally
To install, run the following in the root of the project.
-``` bash
+```bash
+$ pnpm install
$ bundle install
```
And to view locally.
-``` bash
+```bash
$ bundle exec jekyll serve
```
@@ -71,36 +73,42 @@ You can now view the guide locally by visiting `http://localhost:4000/`.
You can also turn on live reloading and incremental builds while editing.
-``` bash
+```bash
$ bundle exec jekyll serve --incremental --livereload
```
-#### Generating the PDF
+#### Generating the eBook
-You can generate the PDF locally on macOS by following these steps.
+We use [Pandoc](https://pandoc.org) to create the eBook. You can generate it locally by following these steps.
-1. Generate a `Cover.pdf` with latest version and date
- 1. Create an `ebook` folder in `~/Downloads` (for example).
- 2. Update the date and version in the `etc/cover.html`
- 3. Open the cover page locally in Safari by going to `file:///Users/frank/Sites/ServerlessStackCom/etc/cover.html`.
- 4. Hit the **Export to PDF…** button.
- 5. Place `Cover.pdf` in the `~/Downloads/ebook` folder.
-2. Ensure `ebook` folder is an option when hitting the **Export to PDF…** button in Safari.
-3. In the terminal, run `osascript pdf.scpt` in the `etc/` directory of this repository.
+```bash
+$ cd ~/Sites/sst.dev/etc/ebook
+$ make start
+```
+
+This'll start a Docker instance. Inside the Docker run:
+
+```bash
+$ make pdf
+$ make epub
+```
-We are looking for a better way to generate the PDF (and other eBook) formats. If you've got any ideas [consider contributing][Contributing].
+The above are run automatically through [Github Actions](https://github.com/sst/sst.dev/actions) in this repo:
-## Sponsors
+- When a new commit is pushed to master
+- And when a new tag is pushed, the generated eBook is uploaded to S3
-[**Sponsor Serverless Stack on Patreon**](https://www.patreon.com/serverless_stack) if you've found this guide useful or would like to be an official supporter. [A big thanks to our supporters](https://serverless-stack.com/sponsors.html#sponsors)!
+### Deploying Locally
-## Maintainers
+To deploy this site. Run:
-Serverless Stack is maintained by [Anomaly Innovations](https://anoma.ly/). [**Subscribe to our newsletter**](https://emailoctopus.com/lists/1c11b9a8-1500-11e8-a3c9-06b79b628af2/forms/subscribe) for updates on Serverless Stack.
+```bash
+$ npx sst deploy
+```
## Contributors
-Thanks to these folks for their contributions to the content of Serverless Stack.
+Thanks to these folks for their contributions to the content of SST.
- [Peter Eman Paver Abastillas](https://github.com/jatazoulja): Social login chapters
- [Bernardo Bugmann](https://github.com/bernardobugmann): Translating chapters to Portuguese
@@ -110,8 +118,15 @@ Thanks to these folks for their contributions to the content of Serverless Stack
- [Vieko Franetovic](https://github.com/vieko): Translating chapters to Spanish
- [Christian Kaindl](https://github.com/christiankaindl): Translating chapters to German
- [Jae Chul Kim](https://github.com/bsg-bob): Translating chapters to Korean
+- [Ben Force](https://x.com/theBenForce): Extra credit chapters
+- [Eze Sunday](https://x.com/ezesundayeze): Extra credit chapters
+- [Maniteja Pratha](https://x.com/PrataManitej): Vue.js example
+
+---
+**Join our community** [Discord][discord] | [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/SST_dev) | [Contribute][contributing]
-[Discourse]: https://discourse.serverless-stack.com
-[Contributing]: CONTRIBUTING.md
-[PR]: ../../compare
+[discourse]: https://discourse.sst.dev
+[discord]: https://sst.dev/discord
+[contributing]: CONTRIBUTING.md
+[pr]: ../../compare
diff --git a/_chapters/add-a-billing-api.md b/_archives/add-a-billing-api.md
similarity index 67%
rename from _chapters/add-a-billing-api.md
rename to _archives/add-a-billing-api.md
index d5292d0fbe..6d61fbfd2a 100644
--- a/_chapters/add-a-billing-api.md
+++ b/_archives/add-a-billing-api.md
@@ -4,29 +4,29 @@ title: Add a Billing API
date: 2018-03-07 00:00:00
lang: en
description: We are going to create a Lambda function for our serverless billing API. It will take the Stripe token that is passed in from our app and use the Stripe JS SDK to process the payment.
-context: true
ref: add-a-billing-api
comments_id: add-a-billing-api/170
+redirect_from: /chapters/add-a-billing-api.html
---
Now let's get started with creating 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.
+{%change%} Start by installing the Stripe NPM package. Run the following in the root of our project.
``` bash
$ npm install --save stripe
```
-Next, add the following to `billing.js`.
+{%change%} Create a new file called `billing.js` with the following.
``` js
import stripePackage from "stripe";
+import handler from "./libs/handler-lib";
import { calculateCost } from "./libs/billing-lib";
-import { success, failure } from "./libs/response-lib";
-export async function main(event, context) {
+export const main = handler(async (event, context) => {
const { storage, source } = JSON.parse(event.body);
const amount = calculateCost(storage);
const description = "Scratch charge";
@@ -34,18 +34,15 @@ export async function main(event, context) {
// 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 });
- }
-}
+ await stripe.charges.create({
+ source,
+ amount,
+ description,
+ currency: "usd",
+ });
+
+ return { status: true };
+});
```
Most of this is fairly straightforward but let's go over it quickly:
@@ -58,11 +55,13 @@ Most of this is fairly straightforward but let's go over it quickly:
- Finally, we use the `stripe.charges.create` method to charge the user and respond to the request if everything went through successfully.
+Note, if you are testing this from India, you'll need to add some shipping information as well. Check out the [details from our forums](https://discourse.sst.dev/t/test-the-billing-api/172/20).
+
### 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.
+{%change%} Create a `libs/billing-lib.js` and add the following.
``` js
export function calculateCost(storage) {
@@ -76,34 +75,28 @@ export function calculateCost(storage) {
}
```
-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!
+This is basically saying that if a user wants to store 10 or fewer notes, we'll charge them $4 per note. For 11 to 100 notes, we'll charge $2 and any more than 100 is $1 per note. Since Stripe expects us to provide the amount in pennies (the currency’s smallest unit) we multiply the result by 100. 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 function.
-Add the following above the `resources:` block in the `serverless.yml`.
+{%change%} Open the `serverless.yml` file and append the following to it.
``` yml
billing:
+ # Defines an HTTP API endpoint that calls the main function in billing.js
+ # - path: url path is /billing
+ # - method: POST request
handler: billing.main
events:
- http:
path: billing
- method: post
cors: true
+ method: post
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.
-
-``` bash
-$ 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.
diff --git a/_archives/add-a-create-note-api.md b/_archives/add-a-create-note-api.md
new file mode 100644
index 0000000000..4e88e67402
--- /dev/null
+++ b/_archives/add-a-create-note-api.md
@@ -0,0 +1,314 @@
+---
+layout: post
+title: Add a Create Note API
+date: 2016-12-30 00:00:00
+lang: en
+ref: add-a-create-note-api
+description: To allow users to create notes in our note taking app, we are going to add a create note POST API. To do this we are going to add a new Lambda function to our Serverless Framework project. The Lambda function will save the note to our DynamoDB table and return the newly created note.
+comments_id: add-a-create-note-api/125
+redirect_from: /chapters/add-a-create-note-api.html
+---
+
+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 function.
+
+{%change%} Create a new file called `create.js` in our project root with the following.
+
+```js
+import * as uuid from "uuid";
+import AWS from "aws-sdk";
+
+const dynamoDb = new AWS.DynamoDB.DocumentClient();
+
+export async function main(event, context) {
+ // Request body is passed in as a JSON encoded string in 'event.body'
+ const data = JSON.parse(event.body);
+
+ const params = {
+ TableName: process.env.tableName,
+ Item: {
+ // The attributes of the item to be created
+ userId: "123", // The id of the author
+ noteId: uuid.v1(), // A unique uuid
+ content: data.content, // Parsed from request body
+ attachment: data.attachment, // Parsed from request body
+ createdAt: Date.now(), // Current Unix timestamp
+ },
+ };
+
+ // Set response headers to enable CORS (Cross-Origin Resource Sharing)
+ const headers = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Credentials": true,
+ };
+
+ try {
+ await dynamoDb.put(params).promise();
+
+ return {
+ statusCode: 200,
+ headers: headers,
+ body: JSON.stringify(params.Item),
+ };
+ } catch (e) {
+ return {
+ statusCode: 500,
+ headers: headers,
+ body: JSON.stringify({ error: e.message }),
+ };
+ }
+}
+```
+
+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 function. So if your DynamoDB table is in a different region, make sure to set it by calling `AWS.config.update({ region: "my-region" });` before initializing the DynamoDB client.
+- Parse the input from the `event.body`. This represents the HTTP request body.
+- It contains the contents of the note, as a string — `content`.
+- It also contains an `attachment`, if one exists. It's the filename of file that has been uploaded to [our S3 bucket]({% link _archives/create-an-s3-bucket-for-file-uploads.md %}).
+- We read the name of our DynamoDB table from the environment variable using `process.env.tableName`. We'll be setting this in our `serverless.yml` below. We do this so we won't have to hardcode it in every function.
+- The `userId` is the id for the author of the note. For now we are hardcoding it to `123`. Later we'll be setting this based on the authenticated user.
+- Make a call to DynamoDB to put a new object with a generated `noteId` and the current date as the `createdAt`.
+- 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 function.
+
+{%change%} Open the `serverless.yml` file and replace it with the following.
+
+```yaml
+service: notes-api
+
+# Create an optimized package for our functions
+package:
+ individually: true
+
+plugins:
+ - serverless-bundle # Package our functions with Webpack
+ - serverless-offline
+ - serverless-dotenv-plugin # Load .env as environment variables
+
+provider:
+ name: aws
+ runtime: nodejs12.x
+ stage: prod
+ region: us-east-1
+
+ # These environment variables are made available to our functions
+ # under process.env.
+ environment:
+ tableName: notes
+
+ # '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:Scan
+ - dynamodb:Query
+ - dynamodb:GetItem
+ - dynamodb:PutItem
+ - dynamodb:UpdateItem
+ - dynamodb:DeleteItem
+ - dynamodb:DescribeTable
+ 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
+ create:
+ handler: create.main
+ events:
+ - http:
+ path: notes
+ cors: true
+ method: post
+```
+
+Here we are adding our newly added create function to the configuration. We specify that it handles `post` requests at the `/notes` endpoint. This pattern of using a single Lambda function to respond to a single HTTP event is very much like the [Microservices architecture](https://en.wikipedia.org/wiki/Microservices). We discuss this and a few other patterns in the chapter on [organizing Serverless Framework projects]({% link _archives/organizing-serverless-projects.md %}).
+
+The `environment:` block allows us to define environment variables for our Lambda function. These are made available under the `process.env` Node.js variable. In our specific case, we are using `process.env.tableName` to access the name of our DynamoDB table.
+
+The `iamRoleStatements` section is telling AWS which resources our Lambda functions have access to. In this case we are saying that our Lambda functions can carry out the above listed actions on DynamoDB. We specify DynamoDB using `arn:aws:dynamodb:us-east-1:*:*`. This is roughly pointing to every DynamoDB table in the `us-east-1` region. We can be more specific here by specifying the table name. We'll be doing this later in the guide when we define our infrastructure as code. For now, 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.
+
+{%change%} In our project root, create a `mocks/` directory.
+
+```bash
+$ mkdir mocks
+```
+
+{%change%} Create a `mocks/create-event.json` file and add the following.
+
+```json
+{
+ "body": "{\"content\":\"hello world\",\"attachment\":\"hello.jpg\"}"
+}
+```
+
+The `body` here corresponds to the `event.body` that we reference in our function. We are passing it in as a JSON encoded string. Note that, for the `attachment` we are just pretending that there is a file called `hello.jpg` that has already been uploaded.
+
+And to invoke our function we run the following in the root directory.
+
+```bash
+$ serverless invoke local --function create --path mocks/create-event.json
+```
+
+If you have multiple profiles for your AWS SDK credentials, you will need to explicitly pick one. Use the following command instead:
+
+```bash
+$ 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 multiple AWS profiles]({% link _archives/configure-multiple-aws-profiles.md %}) chapter.
+
+The response should look similar to this.
+
+```bash
+{
+ "statusCode": 200,
+ "body": "{\"userId\":\"123\",\"noteId\":\"bf586970-1007-11eb-a17f-a5105a0818d3\",\"content\":\"hello world\",\"attachment\":\"hello.jpg\",\"createdAt\":1602891102599}"
+}
+```
+
+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.
+
+{%change%} Start by replacing our `create.js` with the following.
+
+```js
+import * as uuid from "uuid";
+import handler from "./libs/handler-lib";
+import dynamoDb from "./libs/dynamodb-lib";
+
+export const main = handler(async (event, context) => {
+ const data = JSON.parse(event.body);
+ const params = {
+ TableName: process.env.tableName,
+ Item: {
+ // The attributes of the item to be created
+ userId: "123", // The id of the author
+ noteId: uuid.v1(), // A unique uuid
+ content: data.content, // Parsed from request body
+ attachment: data.attachment, // Parsed from request body
+ createdAt: Date.now(), // Current Unix timestamp
+ },
+ };
+
+ await dynamoDb.put(params);
+
+ return params.Item;
+});
+```
+
+This code doesn't work just yet but it shows you what we want to accomplish:
+
+- We want to make our Lambda function `async`, and simply return the results.
+- We want to simplify how we make calls to DynamoDB. We don't want to have to create a `new AWS.DynamoDB.DocumentClient()`.
+- We want to centrally handle any errors in our Lambda functions.
+- Finally, since all of our Lambda functions will be handling API endpoints, we want to handle our HTTP responses in one place.
+
+To do all of this let's first create our `dynamodb-lib`.
+
+{%change%} In our project root, create a `libs/` directory.
+
+```bash
+$ mkdir libs
+$ cd libs
+```
+
+{%change%} Create a `libs/dynamodb-lib.js` file with:
+
+```js
+import AWS from "aws-sdk";
+
+const client = new AWS.DynamoDB.DocumentClient();
+
+export default {
+ get: (params) => client.get(params).promise(),
+ put: (params) => client.put(params).promise(),
+ query: (params) => client.query(params).promise(),
+ update: (params) => client.update(params).promise(),
+ delete: (params) => client.delete(params).promise(),
+};
+```
+
+Here we are creating a convenience object that exposes the DynamoDB client methods that we are going to need in this guide.
+
+{%change%} Also create a `libs/handler-lib.js` file with the following.
+
+```js
+export default function handler(lambda) {
+ return async function (event, context) {
+ let body, statusCode;
+
+ try {
+ // Run the Lambda
+ body = await lambda(event, context);
+ statusCode = 200;
+ } catch (e) {
+ body = { error: e.message };
+ statusCode = 500;
+ }
+
+ // Return HTTP response
+ return {
+ statusCode,
+ body: JSON.stringify(body),
+ headers: {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Credentials": true,
+ },
+ };
+ };
+}
+```
+
+Let's go over this in detail.
+
+- We are creating a `handler` function that we'll use as a wrapper around our Lambda functions.
+- It takes our Lambda function as the argument.
+- We then run the Lambda function in a `try/catch` block.
+- On success, we `JSON.stringify` the result and return it with a `200` status code.
+- If there is an error then we return the error message with a `500` status code.
+
+It's **important to note** that the `handler-lib.js` needs to be **imported before we import anything else**. This is because we'll be adding some error handling to it later that needs to be initialized when our Lambda function is first invoked.
+
+### Remove Template Files
+
+{%change%} Also, let's remove the starter files by running the following command in the root of our project.
+
+```bash
+$ rm handler.js
+```
+
+Next, we are going to add 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 function, here is how to debug it. The error is generated by our code in the `catch` block. Adding a `console.log` in our `libs/handler-lib.js`, should give you a clue about what the issue is.
+
+ ```js
+ } catch (e) {
+ // Print out the full error
+ console.log(e);
+
+ body = { error: e.message };
+ statusCode = 500;
+ }
+ ```
diff --git a/_archives/add-a-delete-note-api.md b/_archives/add-a-delete-note-api.md
new file mode 100644
index 0000000000..38dda7fad9
--- /dev/null
+++ b/_archives/add-a-delete-note-api.md
@@ -0,0 +1,88 @@
+---
+layout: post
+title: Add a Delete Note API
+date: 2017-01-03 00:00:00
+lang: en
+ref: add-a-delete-note-api
+description: To allow users to delete their notes in our note taking app, we are going to add a DELETE note API. To do this we will add a new Lambda function to our Serverless Framework project. The Lambda function will delete a user’s note in the DynamoDB table.
+comments_id: add-a-delete-note-api/153
+redirect_from: /chapters/add-a-delete-note-api.html
+---
+
+Finally, we are going to create an API that allows a user to delete a given note.
+
+### Add the Function
+
+{%change%} Create a new file `delete.js` and paste the following code
+
+```js
+import handler from "./libs/handler-lib";
+import dynamoDb from "./libs/dynamodb-lib";
+
+export const main = handler(async (event, context) => {
+ const params = {
+ TableName: process.env.tableName,
+ // 'Key' defines the partition key and sort key of the item to be removed
+ Key: {
+ userId: "123", // The id of the author
+ noteId: event.pathParameters.id, // The id of the note from the path
+ },
+ };
+
+ await dynamoDb.delete(params);
+
+ return { status: true };
+});
+```
+
+This makes a DynamoDB `delete` call with the `userId` & `noteId` key to delete the note.
+
+### Configure the API Endpoint
+
+{%change%} Open the `serverless.yml` file and append the following to it.
+
+```yaml
+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}
+ cors: true
+ method: delete
+```
+
+This adds a DELETE request handler to the `/notes/{id}` endpoint.
+
+### Test
+
+{%change%} 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.
+
+```json
+{
+ "pathParameters": {
+ "id": "578eb840-f70f-11e6-9d1a-1359b3b22944"
+ }
+}
+```
+
+Invoke our newly created function from the root directory.
+
+```bash
+$ serverless invoke local --function delete --path mocks/delete-event.json
+```
+
+And the response should look similar to this.
+
+```bash
+{
+ "statusCode": 200,
+ "body": "{\"status\":true}"
+}
+```
+
+Now that our APIs are complete, let's deploy them next!
diff --git a/_archives/add-a-get-note-api.md b/_archives/add-a-get-note-api.md
new file mode 100644
index 0000000000..10b2b61654
--- /dev/null
+++ b/_archives/add-a-get-note-api.md
@@ -0,0 +1,94 @@
+---
+layout: post
+title: Add a Get Note API
+date: 2016-12-31 00:00:00
+lang: en
+ref: add-a-get-note-api
+description: To allow users to retrieve a note in our note taking app, we are going to add a GET note API. To do this we will add a new Lambda function to our Serverless Framework project. The Lambda function will retrieve the note from our DynamoDB table.
+comments_id: add-a-get-note-api/132
+redirect_from: /chapters/add-a-get-note-api.html
+---
+
+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
+
+{%change%} Create a new file `get.js` in your project root and paste the following code:
+
+```js
+import handler from "./libs/handler-lib";
+import dynamoDb from "./libs/dynamodb-lib";
+
+export const main = handler(async (event, context) => {
+ const params = {
+ TableName: process.env.tableName,
+ // 'Key' defines the partition key and sort key of the item to be retrieved
+ Key: {
+ userId: "123", // The id of the author
+ noteId: event.pathParameters.id, // The id of the note from the path
+ },
+ };
+
+ const result = await dynamoDb.get(params);
+ if (!result.Item) {
+ throw new Error("Item not found.");
+ }
+
+ // Return the retrieved item
+ return result.Item;
+});
+```
+
+This follows exactly the same structure as our previous `create.js` function. The major difference here is that we are doing a `dynamoDb.get(params)` to get a note object given the `userId` (still hardcoded) and `noteId` that is passed in through the request.
+
+### Configure the API Endpoint
+
+{%change%} Open the `serverless.yml` file and append the following to it.
+
+```yaml
+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}
+ cors: true
+ method: get
+```
+
+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}`. The `{id}` here translates to the `event.pathParameters.id` that we used in our function above.
+
+### 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` function.
+
+{%change%} Create a `mocks/get-event.json` file and add the following.
+
+```json
+{
+ "pathParameters": {
+ "id": "a63c5450-1274-11eb-81db-b9d1e2c85f15"
+ }
+}
+```
+
+And invoke our newly created function from the root directory of the project.
+
+```bash
+$ serverless invoke local --function get --path mocks/get-event.json
+```
+
+The response should look similar to this.
+
+```bash
+{
+ "statusCode": 200,
+ "body": "{\"attachment\":\"hello.jpg\",\"content\":\"hello world\",\"createdAt\":1603157777941,\"noteId\":\"a63c5450-1274-11eb-81db-b9d1e2c85f15\",\"userId\":\"123\"}"
+}
+```
+
+Next, let's create an API to list all the notes a user has.
diff --git a/_archives/add-a-list-all-the-notes-api.md b/_archives/add-a-list-all-the-notes-api.md
new file mode 100644
index 0000000000..2cd4df56b8
--- /dev/null
+++ b/_archives/add-a-list-all-the-notes-api.md
@@ -0,0 +1,91 @@
+---
+layout: post
+title: Add a List All the Notes API
+date: 2017-01-01 00:00:00
+lang: en
+ref: add-a-list-all-the-notes-api
+description: To allow users to retrieve their notes in our note taking app, we are going to add a list note GET API. To do this we will add a new Lambda function to our Serverless Framework project. The Lambda function will retrieve all the user’s notes from the DynamoDB table.
+comments_id: add-a-list-all-the-notes-api/147
+redirect_from: /chapters/add-a-list-all-the-notes-api.html
+---
+
+Now we are going to add an API that returns a list of all the notes a user has.
+
+### Add the Function
+
+{%change%} Create a new file called `services/functions/list.js` with the following.
+
+```js
+import handler from "./libs/handler-lib";
+import dynamoDb from "./libs/dynamodb-lib";
+
+export const main = handler(async (event, context) => {
+ const params = {
+ TableName: process.env.tableName,
+ // 'KeyConditionExpression' defines the condition for the query
+ // - 'userId = :userId': only return items with matching 'userId'
+ // partition key
+ KeyConditionExpression: "userId = :userId",
+ // 'ExpressionAttributeValues' defines the value in the condition
+ // - ':userId': defines 'userId' to be the id of the author
+ ExpressionAttributeValues: {
+ ":userId": "123",
+ },
+ };
+
+ const result = await dynamoDb.query(params);
+
+ // Return the matching list of items in response body
+ return result.Items;
+});
+```
+
+This is pretty much the same as our `get.js` except we use a condition to only return the items that have the same `userId` as the one we are passing in. In our case, it's still hardcoded to `123`.
+
+### Configure the API Endpoint
+
+{%change%} Open the `serverless.yml` file and append the following.
+
+```yaml
+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
+ cors: true
+ method: get
+```
+
+This defines the `/notes` endpoint that takes a GET request.
+
+### Test
+
+{%change%} Create a `mocks/list-event.json` file and add the following.
+
+```json
+{}
+```
+
+We are still adding an empty mock event because we are going to replace this later on in the guide.
+
+And invoke our function from the root directory of the project.
+
+```bash
+$ serverless invoke local --function list --path mocks/list-event.json
+```
+
+The response should look similar to this.
+
+```bash
+{
+ "statusCode": 200,
+ "body": "[{\"attachment\":\"hello.jpg\",\"content\":\"hello world\",\"createdAt\":1602891322039,\"noteId\":\"42244c70-1008-11eb-8be9-4b88616c4b39\",\"userId\":\"123\"}]"
+}
+```
+
+Note that this API returns an array of note objects as opposed to the `get.js` function that returns just a single note object.
+
+Next we are going to add an API to update a note.
diff --git a/_chapters/add-an-update-note-api.md b/_archives/add-an-update-note-api.md
similarity index 57%
rename from _chapters/add-an-update-note-api.md
rename to _archives/add-an-update-note-api.md
index cae08b033d..44140e9497 100644
--- a/_chapters/add-an-update-note-api.md
+++ b/_archives/add-an-update-note-api.md
@@ -5,112 +5,96 @@ date: 2017-01-02 00:00:00
lang: en
ref: add-an-update-note-api
description: To allow users to update their notes in our note taking app, we are going to add an update note PUT API. To do this we will add a new Lambda function to our Serverless Framework project. The Lambda function will update a user’s note in the DynamoDB table.
-context: true
-code: backend
comments_id: add-an-update-note-api/144
+redirect_from: /chapters/add-an-update-note-api.html
---
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
+{%change%} Create a new file `update.js` and paste the following code
-``` javascript
-import * as dynamoDbLib from "./libs/dynamodb-lib";
-import { success, failure } from "./libs/response-lib";
+```js
+import handler from "./libs/handler-lib";
+import dynamoDb from "./libs/dynamodb-lib";
-export async function main(event, context) {
+export const main = handler(async (event, context) => {
const data = JSON.parse(event.body);
const params = {
- TableName: "notes",
+ TableName: process.env.tableName,
// '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
+ userId: "123", // The id of the author
+ noteId: event.pathParameters.id, // The id of the note from the path
},
// '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
+ ":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"
+ ReturnValues: "ALL_NEW",
};
- try {
- await dynamoDbLib.call("update", params);
- return success({ status: true });
- } catch (e) {
- return failure({ status: false });
- }
-}
+ await dynamoDb.update(params);
+
+ return { status: true };
+});
```
This should look similar to the `create.js` function. 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.
-
-``` yaml
- 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
+{%change%} Open the `serverless.yml` file and append the following to it.
+
+```yaml
+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}
+ cors: true
+ method: put
```
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.
+{%change%} 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.
-``` json
+```json
{
"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 function from the root directory.
-``` bash
+```bash
$ serverless invoke local --function update --path mocks/update-event.json
```
The response should look similar to this.
-``` bash
+```bash
{
- statusCode: 200,
- headers: {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Credentials': true
- },
- body: '{"status":true}'
+ "statusCode": 200,
+ "body": "{\"status\":true}"
}
```
diff --git a/_archives/add-support-for-es6-and-typescript.md b/_archives/add-support-for-es6-and-typescript.md
new file mode 100644
index 0000000000..7a1cdf4305
--- /dev/null
+++ b/_archives/add-support-for-es6-and-typescript.md
@@ -0,0 +1,112 @@
+---
+layout: post
+title: Add Support for ES6 and TypeScript
+date: 2016-12-29 12:00:00
+lang: en
+ref: add-support-for-es6-and-typescript
+description: AWS Lambda supports Node.js v10.x and v12.x. However, to use ES 6 features or TypeScript in our Serverless Framework project we need to use Babel, Webpack 5, and a ton of other packages. We can do this by using the serverless-bundle plugin to our project.
+comments_id: add-support-for-es6-es7-javascript/128
+redirect_from:
+ - /chapters/add-support-for-es6-javascript.html
+ - /chapters/add-support-for-es6-es7-javascript.html
+ - /chapters/add-support-for-es6-and-typescript.html
+---
+
+AWS Lambda supports Node.js v10.x, v12.x, and v14.x. However, the supported syntax is a little different when compared to the more advanced ECMAScript flavor of JavaScript that our frontend React app supports. 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 functions.
+
+Additionally, our frontend React app automatically supports TypeScript, via [Create React App](https://create-react-app.dev). And while we are not using TypeScript in this guide, it makes sense to have a similar setup for your backend Lambda functions. So you can use it in your future projects.
+
+To do this we typically need to install [Babel](https://babeljs.io), [TypeScript](https://www.typescriptlang.org), [Webpack](https://webpack.js.org), and a long list of other packages. This can add a ton of extra config and complexity to your project.
+
+To help with this we created, [`serverless-bundle`](https://github.com/AnomalyInnovations/serverless-bundle). This is a Serverless Framework plugin that has a few key advantages:
+
+- Only one dependency
+- Supports ES6 and TypeScript
+- Generates optimized packages
+- Linting Lambda functions using [ESLint](https://eslint.org)
+- Supports transpiling unit tests with [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest)
+- Source map support for proper error messages
+
+It's automatically included in the starter project we used in the previous chapter — [`serverless-nodejs-starter`]({% link _archives/serverless-nodejs-starter.md %}). For TypeScript, we have a starter for that as well — [`serverless-typescript-starter`](https://github.com/AnomalyInnovations/serverless-typescript-starter).
+
+However, if you are looking to add ES6 and TypeScript support to your existing Serverless Framework projects, you can do this by installing [serverless-bundle](https://github.com/AnomalyInnovations/serverless-bundle):
+
+``` bash
+$ npm install --save-dev serverless-bundle
+```
+
+And including it in your `serverless.yml` using:
+
+``` yml
+plugins:
+ - serverless-bundle
+```
+
+To run your tests, add this to your `package.json`.
+
+``` json
+"scripts": {
+ "test": "serverless-bundle test"
+}
+```
+
+### ES6 Lambda Functions
+
+Let's look at the Lambda function that comes with our starter project.
+
+Your `handler.js` should look like this.
+
+``` js
+export const hello = async (event, context) => {
+ return {
+ statusCode: 200,
+ body: JSON.stringify({
+ message: `Go Serverless v2.0! ${(await message({ time: 1, copy: 'Your function executed successfully!'}))}`,
+ }),
+ };
+};
+
+const message = ({ time, ...rest }) => new Promise((resolve, reject) =>
+ setTimeout(() => {
+ resolve(`${rest.copy} (with a delay)`);
+ }, time * 1000)
+);
+```
+
+Let's run this. In your project root run:
+
+``` bash
+$ serverless invoke local --function hello
+```
+
+You should see something like this in your terminal.
+
+``` bash
+{
+ "statusCode": 200,
+ "body": "{\"message\":\"Go Serverless v2.0! Your function executed successfully! (with a delay)\"}"
+}
+```
+
+In the above command we are asking Serverless Framework to (locally) invoke a Lambda function called `hello`. This in turn will run the `hello` method that we are exporting in our `handler.js`.
+
+Here we are directly invoking the Lambda function. Though once deployed, we'll be invoking this function through the `/hello` API endpoint (as we [talked about in the last chapter]({% link _archives/setup-the-serverless-framework.md %})).
+
+Now we are almost ready to deploy our Lambda function and API. But before we do that let's quickly look at one of the other things that's been set up for us in this starter project.
+
+### Optimized Packages
+
+By default Serverless Framework creates a single package for all your Lambda functions. This means that when a Lambda function is invoked, it'll load all the code in your app. Including all the other Lambda functions. This negatively affects performance as your app grows in size. The larger your Lambda function packages, the longer [the cold starts]({% link _chapters/what-is-serverless.md %}#cold-starts).
+
+To turn this off and it to ensure that Serverless Framework is packaging our functions individually, add the following to your `serverless.yml`.
+
+``` yml
+package:
+ individually: true
+```
+
+This should be on by default in our starter project.
+
+Note that, with the above option enabled, serverless-bundle can use Webpack to generate optimized packages using a [tree shaking algorithm](https://webpack.js.org/guides/tree-shaking/). It'll only include the code needed to run your Lambda function and nothing else!
+
+Now we are ready to deploy our backend API.
diff --git a/_archives/allow-users-to-change-passwords.md b/_archives/allow-users-to-change-passwords.md
new file mode 100644
index 0000000000..123f80fd86
--- /dev/null
+++ b/_archives/allow-users-to-change-passwords.md
@@ -0,0 +1,222 @@
+---
+layout: post
+title: Allow Users to Change Passwords
+description: Use the AWS Amplify Auth.changePassword method to support change password functionality in our Serverless React app. This triggers Cognito to help our users change their password.
+date: 2018-04-15 00:00:00
+code: user-management
+comments_id: allow-users-to-change-passwords/507
+redirect_from: /chapters/allow-users-to-change-passwords.html
+---
+
+For our [Serverless notes app](https://demo.sst.dev), 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: [**{{ site.frontend_user_mgmt_github_repo }}**]({{ site.frontend_user_mgmt_github_repo }})
+
+Let's start by editing our settings page so that our users can use to change their password.
+
+### Add a Settings Page
+
+{%change%} Replace the `return` statement in `src/containers/Settings.js` with.
+
+```jsx
+return (
+
+);
+```
+
+{%change%} And import the following as well.
+
+```jsx
+import { LinkContainer } from "react-router-bootstrap";
+import LoaderButton from "../components/LoaderButton";
+```
+
+All this does is add two links to a page that allows our users to change their password and email.
+
+{%change%} Replace our `src/containers/Settings.css` with the following.
+
+```css
+@media all and (min-width: 480px) {
+ .Settings {
+ padding: 60px 0;
+ margin: 0 auto;
+ max-width: 480px;
+ }
+
+ .Settings > .LoaderButton:first-child {
+ margin-bottom: 15px;
+ }
+}
+```
+
+
+
+### Change Password Form
+
+Now let's create the form that allows our users to change their password.
+
+{%change%} Add the following to `src/containers/ChangePassword.js`.
+
+```jsx
+import React, { useState } from "react";
+import { Auth } from "aws-amplify";
+import { useNavigate } from "react-router-dom";
+import { FormGroup, FormControl, FormLabel } from "react-bootstrap";
+import LoaderButton from "../components/LoaderButton";
+import { useFormFields } from "../lib/hooksLib";
+import { onError } from "../lib/errorLib";
+import "./ChangePassword.css";
+
+export default function ChangePassword() {
+ const nav = useNavigate();
+ const [fields, handleFieldChange] = useFormFields({
+ password: "",
+ oldPassword: "",
+ confirmPassword: "",
+ });
+ const [isChanging, setIsChanging] = useState(false);
+
+ function validateForm() {
+ return (
+ fields.oldPassword.length > 0 &&
+ fields.password.length > 0 &&
+ fields.password === fields.confirmPassword
+ );
+ }
+
+ async function handleChangeClick(event) {
+ event.preventDefault();
+
+ setIsChanging(true);
+
+ try {
+ const currentUser = await Auth.currentAuthenticatedUser();
+ await Auth.changePassword(
+ currentUser,
+ fields.oldPassword,
+ fields.password
+ );
+
+ nav("/settings");
+ } catch (error) {
+ onError(error);
+ setIsChanging(false);
+ }
+ }
+
+ return (
+
+
+
+ );
+}
+```
+
+Most of this should be very straightforward. 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:
+
+```jsx
+const currentUser = await Auth.currentAuthenticatedUser();
+await Auth.changePassword(currentUser, fields.oldPassword, fields.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 settings page.
+
+{%change%} Let's also add a couple of styles.
+
+```css
+@media all and (min-width: 480px) {
+ .ChangePassword {
+ padding: 60px 0;
+ }
+
+ .ChangePassword form {
+ margin: 0 auto;
+ max-width: 320px;
+ }
+}
+```
+
+{%change%} Let's add our new page to `src/Routes.js`.
+
+```html
+
+
+
+ }
+/>
+```
+
+{%change%} And import it.
+
+```jsx
+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.
diff --git a/_chapters/allow-users-to-change-their-email.md b/_archives/allow-users-to-change-their-email.md
similarity index 50%
rename from _chapters/allow-users-to-change-their-email.md
rename to _archives/allow-users-to-change-their-email.md
index 97244ce4df..cc8efe3bc5 100644
--- a/_chapters/allow-users-to-change-their-email.md
+++ b/_archives/allow-users-to-change-their-email.md
@@ -3,157 +3,145 @@ layout: post
title: Allow Users to Change Their Email
description: Use the AWS Amplify Auth.updateUserAttributes method to support change email functionality in our Serverless React app. This triggers Cognito to help our users change their email.
date: 2018-04-16 00:00:00
-context: true
code: user-management
comments_id: allow-users-to-change-their-email/508
+redirect_from: /chapters/allow-users-to-change-their-email.html
---
-We want the users of our [Serverless notes app](https://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.
+We want the users of our [Serverless notes app](https://demo.sst.dev) 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: [**{{ site.frontend_user_mgmt_github_repo }}**]({{ site.frontend_user_mgmt_github_repo }})
-- And it can be accessed through: [**https://demo-user-mgmt.serverless-stack.com**](https://demo-user-mgmt.serverless-stack.com)
+- And it can be accessed through: [**https://demo-user-mgmt.sst.dev**](https://demo-user-mgmt.sst.dev)
In the previous chapter we created a settings page that links to `/settings/email`. Let's implement that.
### Change Email Form
-Add the following to `src/containers/ChangeEmail.js`.
+{%change%} Add the following to `src/containers/ChangeEmail.js`.
-``` coffee
-import React, { Component } from "react";
+```coffee
+import React, { useState } from "react";
import { Auth } from "aws-amplify";
+import { useNavigate } from "react-router-dom";
import {
- HelpBlock,
+ FormText,
FormGroup,
FormControl,
- ControlLabel
+ FormLabel,
} from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
+import { useFormFields } from "../lib/hooksLib";
+import { onError } from "../lib/errorLib";
import "./ChangeEmail.css";
-export default class ChangeEmail extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- code: "",
- email: "",
- codeSent: false,
- isConfirming: false,
- isSendingCode: false
- };
+export default function ChangeEmail() {
+ const nav = useNavigate();
+ const [codeSent, setCodeSent] = useState(false);
+ const [fields, handleFieldChange] = useFormFields({
+ code: "",
+ email: "",
+ });
+ const [isConfirming, setIsConfirming] = useState(false);
+ const [isSendingCode, setIsSendingCode] = useState(false);
+
+ function validateEmailForm() {
+ return fields.email.length > 0;
}
- validatEmailForm() {
- return this.state.email.length > 0;
+ function validateConfirmForm() {
+ return fields.code.length > 0;
}
- validateConfirmForm() {
- return this.state.code.length > 0;
- }
-
- handleChange = event => {
- this.setState({
- [event.target.id]: event.target.value
- });
- };
-
- handleUpdateClick = async event => {
+ async function handleUpdateClick(event) {
event.preventDefault();
- this.setState({ isSendingCode: true });
+ setIsSendingCode(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 });
+ await Auth.updateUserAttributes(user, { email: fields.email });
+ setCodeSent(true);
+ } catch (error) {
+ onError(error);
+ setIsSendingCode(false);
}
- };
+ }
- handleConfirmClick = async event => {
+ async function handleConfirmClick(event) {
event.preventDefault();
- this.setState({ isConfirming: true });
+ setIsConfirming(true);
try {
- await Auth.verifyCurrentUserAttributeSubmit("email", this.state.code);
+ await Auth.verifyCurrentUserAttributeSubmit("email", fields.code);
- this.props.history.push("/settings");
- } catch (e) {
- alert(e.message);
- this.setState({ isConfirming: false });
+ nav("/settings");
+ } catch (error) {
+ onError(error);
+ setIsConfirming(false);
}
- };
+ }
- renderUpdateForm() {
+ function renderUpdateForm() {
return (
-
);
}
- renderConfirmationForm() {
+ function renderConfirmationForm() {
return (
-
);
}
- render() {
- return (
-
+ );
}
```
@@ -163,24 +151,24 @@ The flow for changing a user's email is pretty similar to how we sign a user up.
2. Cognito sends them a verification 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:
+We start by rendering a form that asks our user to enter their new email in `renderUpdateForm()`. Once the user submits this form, we call:
-``` js
+```js
const user = await Auth.currentAuthenticatedUser();
-Auth.updateUserAttributes(user, { email: this.state.email });
+Auth.updateUserAttributes(user, { email: fields.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 submitting this form we call:
+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 `renderConfirmationForm()`. Upon submitting this form we call:
-``` js
-Auth.verifyCurrentUserAttributeSubmit("email", this.state.code);
+```js
+Auth.verifyCurrentUserAttributeSubmit("email", fields.code);
```
This confirms the change on Cognito's side. Finally, we redirect the user to the settings page.
-Let's add a couple of styles to `src/containers/ChangeEmail.css`.
+{%change%} Let's add a couple of styles to `src/containers/ChangeEmail.css`.
-``` css
+```css
@media all and (min-width: 480px) {
.ChangeEmail {
padding: 60px 0;
@@ -193,20 +181,17 @@ This confirms the change on Cognito's side. Finally, we redirect the user to the
}
```
-Finally, let's add our new page to `src/Routes.js`.
+{%change%} Finally, let's add our new page to `src/Routes.js`.
-``` html
-
+```html
+
+
+
```
-And import it in the header.
+{%change%} And import it in the header.
-``` coffee
+```coffee
import ChangeEmail from "./containers/ChangeEmail";
```
@@ -222,6 +207,6 @@ You might notice that the change email flow is interrupted if the user does not
- In this case show a simple sign that allows users to resend the verification 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)`.
+- Next you can simply display the confirm code form from above and follow the same flow by calling `Auth.verifyCurrentUserAttributeSubmit("email", fields.code)`.
This can make your change email flow more robust and handle the case where a user forgets to verify their new email.
diff --git a/_chapters/api-gateway-and-lambda-logs.md b/_archives/api-gateway-and-lambda-logs.md
similarity index 95%
rename from _chapters/api-gateway-and-lambda-logs.md
rename to _archives/api-gateway-and-lambda-logs.md
index 400a8b3750..874a8aae4a 100644
--- a/_chapters/api-gateway-and-lambda-logs.md
+++ b/_archives/api-gateway-and-lambda-logs.md
@@ -3,13 +3,12 @@ layout: post
title: API Gateway and Lambda Logs
description: To view logs for your serverless APIs on AWS, CloudWatch needs to be enabled for API Gateway and Lambda. CloudWatch logs are ordered by Log Groups and Log Stream. Lambda CloudWatch logs can also be viewed using the Serverless CLI with the “serverless logs” command.
date: 2018-04-03 00:00:00
-context: true
comments_id: api-gateway-and-lambda-logs/31
+redirect_from: /chapters/api-gateway-and-lambda-logs.html
---
Logging is an essential 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.
@@ -102,21 +101,19 @@ In the **Logs** tab:
Scroll to the bottom of the page and click **Save Changes**. Now our API Gateway requests should be logged via CloudWatch.
-Note that, the execution logs can generate a ton of log data and it's not recommended to leave them on. They are much better 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](https://seed.run/blog/how-to-enable-access-logs-for-api-gateway).
-
+Note that, the execution logs can generate a ton of log data and it's not recommended to leave them on. They are much better for debugging. API Gateway does have support for access logs, which we recommend leaving on. Here is [how to enable access logs for your API Gateway project](https://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 duration and max memory usage for each execution. You can write additional information to CloudWatch via `console.log`. For example:
-``` javascript
+```js
export function main(event, context, callback) {
- console.log('Hello world');
- callback(null, { body: '' });
+ 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.
@@ -129,7 +126,7 @@ Select **Logs** from the left panel.

-Select the log group prefixed with **API-Gateway-Execution-Logs_** followed by the API Gateway id.
+Select the log group prefixed with **API-Gateway-Execution-Logs\_** followed by the API Gateway id.

@@ -143,7 +140,6 @@ This shows you the log entries grouped by request.
Note that two consecutive groups of logs are not necessarily two consecutive requests in real time. 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 function has its own log group. And the log stream rotates if a new version of the Lambda function has been deployed or if it has been idle for some time.
@@ -164,17 +160,16 @@ You can also use the Serverless CLI to view CloudWatch logs for a Lambda functio
From your project root run the following.
-``` bash
+```bash
$ serverless logs -f
```
Where the `` is the name of the Lambda function you are looking for. Additionally, you can use the `--tail` flag to stream the logs automatically to your console.
-``` bash
+```bash
$ serverless logs -f --tail
```
This can be very helpful during development when trying to debug your functions 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.
diff --git a/_chapters/backups-in-dynamodb.md b/_archives/backups-in-dynamodb.md
similarity index 92%
rename from _chapters/backups-in-dynamodb.md
rename to _archives/backups-in-dynamodb.md
index eebed72339..bb5420fa30 100644
--- a/_chapters/backups-in-dynamodb.md
+++ b/_archives/backups-in-dynamodb.md
@@ -3,13 +3,13 @@ layout: post
title: Backups in DynamoDB
date: 2018-04-06 12:00:00
description: Amazon DynamoDB allows you to create On-demand backups and enable Point-in-time recovery with a single click. Backups are fully-managed, extremely fast, and do not impact performance. In this chapter we look at the two ways to backup and restore DynamoDB tables.
-context: true
comments_id: backups-in-dynamodb/705
+redirect_from: /chapters/backups-in-dynamodb.html
---
An important (yet overlooked) aspect of having a database powering your web application 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](https://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 replicating your data across three different facilities within a given region. However, DynamoDB does not provide an SLA for the data durability. This means that you should backup your database tables.
+For [our demo notes app](https://demo.sst.dev), we are using a DynamoDB table to store all our user's notes. DynamoDB achieves a high degree of data availability and durability by replicating your data across three different facilities 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 getting a quick background on how backups work in DynamoDB.
diff --git a/_archives/best-practices-for-building-serverless-apps.md b/_archives/best-practices-for-building-serverless-apps.md
new file mode 100644
index 0000000000..715477a841
--- /dev/null
+++ b/_archives/best-practices-for-building-serverless-apps.md
@@ -0,0 +1,85 @@
+---
+layout: post
+title: Best Practices for Building Serverless Apps
+description: In this section of the guide we'll be covering the best practices for developing and maintaining large Serverless applications. It builds on what we've covered so far and it extends the demo notes app that we built in the first section. It's intended for teams as opposed to individual developers. It's meant to give you a foundation that scales as your app (and team) grows.
+date: 2019-10-03 00:00:00
+comments_id: best-practices-for-building-serverless-apps/1315
+redirect_from: /chapters/best-practices-for-building-serverless-apps.html
+---
+
+In this section of the guide we'll be covering the best practices for developing and maintaining large Serverless applications. It builds on what we've covered so far and it extends the [demo notes app]({{ site.demo_url }}) that we built in the first section. It's intended for teams as opposed to individual developers. It's meant to give you a foundation that scales as your app (and team) grows.
+
+### Background
+
+SST was launched back in March 2017. Since then thousands of folks have used the guide to build their first full-stack Serverless app. Many of you have used this as a starting point to build really large applications. Applications that are made up of scores of services worked on by a team of developers.
+
+However, the challenges that teams face while developing large scale Serverless applications are very different from the one an individual faces while building his or her first app. You've to deal with architectural design decisions and questions that can be hard to answer if you haven't built and managed a large scale Serverless app before. Questions like:
+
+- How should my project be structured when I have dozens of interdependent services?
+- How should I manage my environments?
+- What is the best practice for storing secrets?
+- How do I make sure my production environments are completely secure?
+- What does the workflow look like for the developers on my team?
+- How do I debug large Serverless applications?
+
+Some of these are not exclusive to folks working on large scale apps, but they are very common once your app grows to a certain size. We hear most of these through our readers, our users, and our [Serverless Toronto Meetup](http://serverlesstoronto.org) members.
+
+While there are tons of blog posts out there that answer some of these questions, it requires you to piece them together to figure out what the best practices are.
+
+### A new perspective
+
+Now nearly 3 years into working on SST and building large scale Serverless applications, there are some common design patterns that we can confidently share with our readers. Additionally, Serverless as a technology and community has also matured to the point where there are reasonable answers for the above questions.
+
+This new addition to the guide is designed to lay out some of the best practices and give you a solid foundation to use Serverless at your company. You can be confident that as your application and team grows, you'll be on the right track for building something that scales.
+
+### Who is this for
+
+While the topics covered in this section can be applied to any project you are working on, they are far better suited for larger scale ones. For example, we talk about using multiple AWS accounts to configure your environments. This works well when you have multiple developers on your team but isn't worth the overhead when you are the only one working on the project.
+
+### What is covered in this section
+
+Here is a rough rundown of the topics covered in this section of the guide.
+
+We are covering primarily the backend Serverless portion. The frontend flow works relatively the same way as what we covered in the first section. We also found that there is a distinct lack of best practices for building Serverless backends as opposed to React apps.
+
+- [Organizing large Serverless apps]({% link _archives/organizing-serverless-projects.md %})
+ - [Sharing resources using cross-stack references]({% link _archives/cross-stack-references-in-serverless.md %})
+ - [Sharing code between services]({% link _archives/share-code-between-services.md %})
+ - [Sharing API endpoints across services]({% link _archives/share-an-api-endpoint-between-services.md %})
+- [Configuring environments]({% link _archives/environments-in-serverless-apps.md %})
+ - [Using separate AWS accounts to manage environments ]({% link _archives/structure-environments-across-aws-accounts.md %})
+ - [Parameterizing resource names]({% link _archives/parameterize-serverless-resources-names.md %})
+ - [Managing environment specific configs]({% link _archives/manage-environment-related-config.md %})
+ - [Best practices for handling secrets]({% link _archives/storing-secrets-in-serverless-apps.md %})
+ - [Sharing domains across environments]({% link _archives/share-route-53-domains-across-aws-accounts.md %})
+- [Development lifecycle]({% link _archives/working-on-serverless-apps.md %})
+ - [Working locally]({% link _archives/invoke-api-gateway-endpoints-locally.md %})
+ - [Creating feature environments]({% link _archives/creating-feature-environments.md %})
+ - [Creating pull request environments]({% link _archives/creating-pull-request-environments.md %})
+ - [Promoting to production]({% link _archives/promoting-to-production.md %})
+ - [Handling rollbacks]({% link _archives/rollback-changes.md %})
+- [Using AWS X-Ray to trace Lambda functions]({% link _archives/tracing-serverless-apps-with-x-ray.md %})
+
+We think these concepts should be a good starting point for your projects and you should be able to adapt them to fit your use case!
+
+### How this new section is structured
+
+This section of the guide has a fair bit of _theory_ when compared to the first section. However, we try to take a similar approach. We'll slowly introduce these concepts as we work through the chapters.
+
+The following repos will serve as the centerpiece of this section:
+
+1. [**Serverless Infrastructure**]({{ site.backend_ext_resources_github_repo }})
+
+ A repo containing all the main infrastructure resources of our extended notes application. We are creating a DynamoDB table to store all the notes related info, an S3 bucket for uploading attachments, and a Cognito User Pool and Identity Pool to authenticate users. We'll be using [AWS CDK]({% link _archives/what-is-aws-cdk.md %}) with [SST](https://github.com/sst/sst).
+
+2. [**Serverless Services**]({{ site.backend_ext_api_github_repo }})
+
+ A monorepo containing all the services in our extended notes application. We have three different services here. The `notes-api` service that powers the notes CRUD API endpoint, the `billing-api` service that processes payment information through Stripe and publishes a message on an SNS topic. Finally, we have a `notify-job` service that listens to the SNS topic and sends us a text message when somebody makes a purchase. We'll be using [Serverless Framework](https://github.com/serverless/serverless) for our services.
+
+We'll start by forking these repos but unlike the first section we won't be directly working on the code. Instead as we work through the sections we'll point out the key aspects of the codebase.
+
+We'll then go over step by step how to configure the environments through AWS. We'll use [Seed](https://seed.run) to illustrate how to deploy our application to our environments. Note that, you do not need Seed to configure your own setup. We'll only be using it as an example. Once you complete the guide you should be able to use your favorite CI/CD service to build a pipeline that follows the best practices. Finally, we'll go over the development workflow for you and your team.
+
+The end result of this will be that you'll have a fully functioning Serverless backend, hosted in your own GitHub repo, and deployed to your AWS environments. We want to make sure that you'll have a working setup in place, so you can always refer back to it when you need to!
+
+Let's get started.
diff --git a/_chapters/code-splitting-in-create-react-app.md b/_archives/code-splitting-in-create-react-app.md
similarity index 88%
rename from _chapters/code-splitting-in-create-react-app.md
rename to _archives/code-splitting-in-create-react-app.md
index 08c2b7e9ad..ce8723dbd7 100644
--- a/_chapters/code-splitting-in-create-react-app.md
+++ b/_archives/code-splitting-in-create-react-app.md
@@ -1,11 +1,10 @@
---
layout: post
title: Code Splitting in Create React App
-description: Code splitting in Create React App is an easy way to reduce the size of your React.js app bundle. To do this in an app using React Router v4, we can asynchronously load our routes using the dynamic import() method that Create React App supports.
+description: Code splitting in Create React App is an easy way to reduce the size of your React.js app bundle. To do this in an app using React Router v5, we can asynchronously load our routes using the dynamic import() method that Create React App supports.
date: 2018-04-17 00:00:00
-code: frontend
-context: true
comments_id: code-splitting-in-create-react-app/98
+redirect_from: /chapters/code-splitting-in-create-react-app.html
---
Code Splitting is not a necessary step for building React apps. But feel free to follow along if you are curious about what Code Splitting is and how it can help larger React apps.
@@ -20,23 +19,24 @@ Create React App (from 1.0 onwards) allows us to dynamically import parts of our
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
+### Code Splitting and React Router v5
The usual structure used by React Router to set up routing for your app looks something like this.
-``` coffee
+```jsx
/* 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 () =>
+export default () => (
- ;
+
+);
```
We start by importing 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.
@@ -47,9 +47,9 @@ However, we import all of the components in the route statically at the top. Thi
To do this we are going to dynamically import the required component.
-Add the following to `src/components/AsyncComponent.js`.
+{%change%} Add the following to `src/components/AsyncComponent.js`.
-``` coffee
+```jsx
import React, { Component } from "react";
export default function asyncComponent(importComponent) {
@@ -58,7 +58,7 @@ export default function asyncComponent(importComponent) {
super(props);
this.state = {
- component: null
+ component: null,
};
}
@@ -66,7 +66,7 @@ export default function asyncComponent(importComponent) {
const { default: component } = await importComponent();
this.setState({
- component: component
+ component: component,
});
}
@@ -91,13 +91,13 @@ We are doing a few things here:
Now let's use this component in our routes. Instead of statically importing our component.
-``` coffee
+```jsx
import Home from "./containers/Home";
```
We are going to use the `asyncComponent` to dynamically import the component we want.
-``` coffee
+```jsx
const AsyncHome = asyncComponent(() => import("./containers/Home"));
```
@@ -107,15 +107,15 @@ Also, it might seem weird that we are passing a function here. Why not just pass
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 continue just like before.
-``` coffee
+```jsx
```
Now let's go back to our Notes project and apply these changes.
-Your `src/Routes.js` should look like this after the changes.
+{%change%} Your `src/Routes.js` should look like this after the changes.
-``` coffee
+```jsx
import React from "react";
import { Route, Switch } from "react-router-dom";
import asyncComponent from "./components/AsyncComponent";
@@ -130,14 +130,9 @@ const AsyncSignup = asyncComponent(() => import("./containers/Signup"));
const AsyncNewNote = asyncComponent(() => import("./containers/NewNote"));
const AsyncNotFound = asyncComponent(() => import("./containers/NotFound"));
-export default ({ childProps }) =>
+export default ({ childProps }) => (
-
+
{/* Finally, catch all unmatched routes */}
-;
+);
```
It is pretty cool that with just a couple of changes, our app is all set up for code splitting. And without adding a whole lot more complexity either! Here is what our `src/Routes.js` looked like before.
-``` coffee
+```jsx
import React from "react";
import { Route, Switch } from "react-router-dom";
import AppliedRoute from "./components/AppliedRoute";
@@ -184,14 +179,9 @@ import Signup from "./containers/Signup";
import NewNote from "./containers/NewNote";
import NotFound from "./containers/NotFound";
-export default ({ childProps }) =>
+export default ({ childProps }) => (
-
+
{/* Finally, catch all unmatched routes */}
-;
+);
```
Notice that instead of doing the static imports for all the containers at the top, we are creating these functions that are going to do the dynamic imports for us when necessary.
@@ -228,9 +218,9 @@ Now if you build your app using `npm run build`; you'll see the code splitting i

-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 initial load time of our app.
+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 initial load time 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](https://demo.serverless-stack.com).
+Now if we deploy our app using `npx sst deploy`; you can see the browser load the different chunks on-demand as we browse around in the [demo](https://demo.sst.dev).

@@ -244,23 +234,23 @@ It was mentioned above that you can add a loading spinner while the import is in
All you need to do to use it is install it.
-``` bash
+```bash
$ npm install --save react-loadable
```
Use it instead of the `asyncComponent` that we had above.
-``` coffee
+```jsx
const AsyncHome = Loadable({
loader: () => import("./containers/Home"),
- loading: MyLoadingComponent
+ loading: MyLoadingComponent,
});
```
And `AsyncHome` is used exactly as before. Here the `MyLoadingComponent` would look something like this.
-``` coffee
-const MyLoadingComponent = ({isLoading, error}) => {
+```jsx
+const MyLoadingComponent = ({ isLoading, error }) => {
// Handle the loading state
if (isLoading) {
return
Loading...
;
@@ -268,8 +258,7 @@ const MyLoadingComponent = ({isLoading, error}) => {
// Handle the error state
else if (error) {
return
Sorry, there was a problem loading the page.
;
- }
- else {
+ } else {
return null;
}
};
diff --git a/_chapters/cognito-user-pool-vs-identity-pool.md b/_archives/cognito-user-pool-vs-identity-pool.md
similarity index 98%
rename from _chapters/cognito-user-pool-vs-identity-pool.md
rename to _archives/cognito-user-pool-vs-identity-pool.md
index 5fd9994079..3ac0a71ef3 100644
--- a/_chapters/cognito-user-pool-vs-identity-pool.md
+++ b/_archives/cognito-user-pool-vs-identity-pool.md
@@ -5,8 +5,8 @@ date: 2017-01-05 12:00:00
lang: en
ref: cognito-user-pool-vs-identity-pool
description: Amazon Cognito User Pool is a service that helps manage your users and the sign-up and sign-in functionality for your mobile or web app. Cognito Identity Pool or Cognito Federated Identities is a service that uses identity providers (like Google, Facebook, or Cognito User Pool) to secure access to other AWS resources.
-context: true
comments_id: cognito-user-pool-vs-identity-pool/146
+redirect_from: /chapters/cognito-user-pool-vs-identity-pool.html
---
We often get questions about the differences between the Cognito User Pool and the Identity Pool, so it is worth covering in detail. The two can seem a bit similar in function and it is not entirely clear what they are for. Let's first start with the official definitions.
diff --git a/_chapters/configure-cognito-identity-pool-in-serverless.md b/_archives/configure-cognito-identity-pool-in-serverless.md
similarity index 76%
rename from _chapters/configure-cognito-identity-pool-in-serverless.md
rename to _archives/configure-cognito-identity-pool-in-serverless.md
index d794984598..0211e8cba3 100644
--- a/_chapters/configure-cognito-identity-pool-in-serverless.md
+++ b/_archives/configure-cognito-identity-pool-in-serverless.md
@@ -1,19 +1,21 @@
---
layout: post
-title: Configure Cognito Identity Pool in Serverless
+title: Configure Cognito Identity Pool in serverless
date: 2018-03-02 00:00:00
lang: en
description: We can define our Cognito Identity Pool using the Infrastructure as Code pattern by using CloudFormation in our serverless.yml. We are going to set the User Pool as the Cognito Identity Provider. And define the Auth Role with a policy allowing access to our S3 Bucket and API Gateway endpoint.
-context: true
ref: configure-cognito-identity-pool-in-serverless
comments_id: configure-cognito-identity-pool-in-serverless/165
+redirect_from:
+ - /chapters/configure-cognito-identity-pool-in-serverless.html
+ - /chapters/cognito-as-a-serverless-service.html
---
-If you recall from the first part of tutorial, we use the Cognito Identity Pool as a way to control which AWS resources our logged in users will have access to. And also tie in our Cognito User Pool as our authentication provider.
+If you recall from the earlier part of this section, we used the Cognito Identity Pool as a way to control which AWS resources our logged-in users will have access to. We also tie in our Cognito User Pool as our authentication provider.
### Create the Resource
-Add the following to `resources/cognito-identity-pool.yml`.
+{%change%} Add the following to `resources/cognito-identity-pool.yml`.
``` yml
Resources:
@@ -110,19 +112,19 @@ Outputs:
Ref: CognitoIdentityPool
```
-Now it looks like there is a whole lot going on here. But it is pretty much exactly what we did back in the [Create a Cognito identity pool]({% link _chapters/create-a-cognito-identity-pool.md %}) chapter. It's just that CloudFormation can be a bit verbose and can end up looking a bit intimidating.
+While it looks like there's a whole lot going on here, it's pretty much exactly what we did back in the [Create a Cognito identity pool]({% link _archives/create-a-cognito-identity-pool.md %}) chapter. It's just that CloudFormation can be a bit verbose and can end up looking a bit intimidating.
Let's quickly go over the various sections of this configuration:
1. First we name our Identity Pool based on the stage name using `${self:custom.stage}`.
-2. We set that we only want logged in users by adding `AllowUnauthenticatedIdentities: false`.
+2. We specify 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 identity provider. We are doing this specifically using the `Ref: CognitoUserPoolClient` line. If you refer back to the [Configure Cognito User Pool in Serverless]({% link _chapters/configure-cognito-user-pool-in-serverless.md %}) chapter, you'll notice we have a block under `CognitoUserPoolClient` that we are referencing here.
+3. Next we state that we want to use our User Pool as the identity provider. We are doing this specifically using the `Ref: CognitoUserPoolClient` line. If you refer back to the [Configure Cognito User Pool in Serverless]({% link _archives/configure-cognito-user-pool-in-serverless.md %}) chapter, you'll notice we have a block under `CognitoUserPoolClient` that we are referencing here.
-4. We then attach a IAM role to our authenticated users.
+4. We then attach an IAM role to our authenticated users.
-5. We add the various parts to this role. This is exactly what we use in the [Create a Cognito identity pool]({% link _chapters/create-a-cognito-identity-pool.md %}) chapter. It just needs to be formatted this way to work with CloudFormation.
+5. We add the various parts to this role. This is exactly what we use in the [Create a Cognito identity pool]({% link _archives/create-a-cognito-identity-pool.md %}) chapter. It just needs to be formatted this way to work with CloudFormation.
6. The `ApiGatewayRestApi` ref that you might notice 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 creating.
@@ -132,13 +134,11 @@ Let's quickly go over the various sections of this configuration:
### Add the Resource
-Let's reference the resource in our `serverless.yml`. Replace your `resources:` block with the following.
+{%change%} Let's reference the resource in our `serverless.yml`. Replace your `resources:` block with the following.
``` yml
# Create our resources with separate CloudFormation templates
resources:
- # API Gateway Errors
- - ${file(resources/api-gateway-errors.yml)}
# DynamoDB
- ${file(resources/dynamodb-table.yml)}
# S3
@@ -148,13 +148,4 @@ resources:
- ${file(resources/cognito-identity-pool.yml)}
```
-### Commit Your Code
-
-Let's commit the changes we've made so far.
-
-``` bash
-$ git add .
-$ git commit -m "Adding our Cognito Identity Pool resource"
-```
-
-Next, let's quickly reference our DynamoDB table in our Lambda functions using environment variables.
+Now we are almost ready to deploy our new serverless infrastructure. We are going to add one more resource to the mix. It'll make it easier for us to debug CORS errors on the frontend.
diff --git a/_chapters/configure-cognito-user-pool-in-serverless.md b/_archives/configure-cognito-user-pool-in-serverless.md
similarity index 78%
rename from _chapters/configure-cognito-user-pool-in-serverless.md
rename to _archives/configure-cognito-user-pool-in-serverless.md
index 2fa357d14a..72336f2aaf 100644
--- a/_chapters/configure-cognito-user-pool-in-serverless.md
+++ b/_archives/configure-cognito-user-pool-in-serverless.md
@@ -1,19 +1,19 @@
---
layout: post
-title: Configure Cognito User Pool in Serverless
+title: Configure Cognito User Pool in serverless
date: 2018-03-01 00:00:00
lang: en
description: We can define our Cognito User Pool using the Infrastructure as Code pattern by using CloudFormation in our serverless.yml. We are going to set the User Pool and App Client name based on the stage we are deploying to. We will also output the User Pool and App Client Id.
-context: true
ref: configure-cognito-user-pool-in-serverless
comments_id: configure-cognito-user-pool-in-serverless/164
+redirect_from: /chapters/configure-cognito-user-pool-in-serverless.html
---
-Now let's look into setting 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]({% link _chapters/create-a-cognito-user-pool.md %}) chapter.
+Now let's look into setting 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]({% link _archives/create-a-cognito-user-pool.md %}) chapter.
### Create the Resource
-Add the following to `resources/cognito-user-pool.yml`.
+{%change%} Add the following to `resources/cognito-user-pool.yml`.
``` yml
Resources:
@@ -60,13 +60,11 @@ Let's quickly go over what we are doing here:
### Add the Resource
-Let's reference the resource in our `serverless.yml`. Replace your `resources:` block with the following.
+{%change%} Let's reference the resource in our `serverless.yml`. Replace your `resources:` block with the following.
``` yml
# Create our resources with separate CloudFormation templates
resources:
- # API Gateway Errors
- - ${file(resources/api-gateway-errors.yml)}
# DynamoDB
- ${file(resources/dynamodb-table.yml)}
# S3
@@ -75,13 +73,4 @@ resources:
- ${file(resources/cognito-user-pool.yml)}
```
-### Commit Your Code
-
-Let's commit the changes we've made so far.
-
-``` bash
-$ git add .
-$ git commit -m "Adding our Cognito User Pool resource"
-```
-
And next let's tie all of this together by configuring our Cognito Identity Pool.
diff --git a/_chapters/configure-dynamodb-in-serverless.md b/_archives/configure-dynamodb-in-serverless.md
similarity index 72%
rename from _chapters/configure-dynamodb-in-serverless.md
rename to _archives/configure-dynamodb-in-serverless.md
index e42ade215b..8e5ecbcaef 100644
--- a/_chapters/configure-dynamodb-in-serverless.md
+++ b/_archives/configure-dynamodb-in-serverless.md
@@ -1,19 +1,29 @@
---
layout: post
-title: Configure DynamoDB in Serverless
+title: Configure DynamoDB in serverless
date: 2018-02-27 00:00:00
lang: en
description: We can define our DynamoDB table using the Infrastructure as Code pattern by using CloudFormation in our serverless.yml. We are going to define the AttributeDefinitions, KeySchema, and ProvisionedThroughput.
-context: true
ref: configure-dynamodb-in-serverless
comments_id: configure-dynamodb-in-serverless/162
+redirect_from:
+ - /chapters/configure-dynamodb-in-serverless.html
+ - /chapters/dynamodb-as-a-serverless-service.html
---
-We are now going to start creating our resources through our `serverless.yml`. Starting with DynamoDB.
+For our Serverless Framework app, we had [previously created our DynamoDB table through the AWS console]({% link _archives/create-a-dynamodb-table.md %}). This can be hard to do when you are creating multiple apps or environments. Ideally, we want to be able to do this programmatically. In this section we'll look at how to use [infrastructure as code]({% link _chapters/what-is-infrastructure-as-code.md %}) to do just that.
### Create the Resource
-Add the following to `resources/dynamodb-table.yml`.
+[Serverless Framework](https://serverless.com) supports [CloudFormation](https://aws.amazon.com/cloudformation/) to help us configure our infrastructure through code. CloudFormation is a way to define our AWS resources using YAML or JSON, instead of having to use the AWS Console. We'll go into this in more detail later in this section.
+
+{%change%} Let’s create a directory to add our resources.
+
+``` bash
+$ mkdir resources/
+```
+
+{%change%} Add the following to `resources/dynamodb-table.yml`.
``` yml
Resources:
@@ -39,9 +49,9 @@ 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.
+2. We get the table name from the 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 attributes of our table as `userId` and `noteId`.
+3. We are also configuring the two attributes of our table as `userId` and `noteId`and specifying them as our primary key.
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.
@@ -49,18 +59,16 @@ Let's quickly go over what we are doing here.
Now let's add a reference to this resource in our project.
-Replace the `resources:` block at the bottom of our `serverless.yml` with the following:
+{%change%} Add the following `resources:` block to the bottom of our `serverless.yml` with the following:
``` yml
# 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.
+{%change%} Add the following `custom:` block at the top of our `serverless.yml` above the `provider:` block.
``` yml
custom:
@@ -73,24 +81,29 @@ custom:
We added a couple of things here that are worth spending some time 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.
+- 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 Framework 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` setting for the `BillingMode`. This tells DynamoDB that we want to pay per request and use the [On-Demand Capacity](https://aws.amazon.com/dynamodb/pricing/on-demand/) option. With DynamoDB in On-Demand mode, our database is now truly Serverless. This option can be very cost-effective, especially if you are just starting 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](https://aws.amazon.com/dynamodb/pricing/provisioned/) mode would work out to be cheaper.
+- Finally, we are using the `PAY_PER_REQUEST` setting for the `BillingMode`. This tells DynamoDB that we want to pay per request and use the [On-Demand Capacity](https://aws.amazon.com/dynamodb/pricing/on-demand/) option. With DynamoDB in On-Demand mode, our database is now truly serverless. This option can be very cost-effective, especially if you are just starting 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](https://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 setting it up so that we can automate and replicate our entire setup with ease. Note that, Serverless Framework (and CloudFormation 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 deleting 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](https://seed.run/blog/how-to-prevent-accidentally-deleting-serverless-resources).
We are also going to make a quick tweak to reference the DynamoDB resource that we are creating.
-Replace the `iamRoleStatements:` block in your `serverless.yml` with the following.
+{%change%} Update our environment variables with the new generated table name. Replace the `environment:` block with the following:
``` yml
# These environment variables are made available to our functions
# under process.env.
environment:
tableName: ${self:custom.tableName}
+ stripeSecretKey: ${env:STRIPE_SECRET_KEY}
+```
+
+{%change%} Replace the `iamRoleStatements:` block in your `serverless.yml` with the following.
+``` yml
iamRoleStatements:
- Effect: Allow
Action:
@@ -117,13 +130,4 @@ A couple of interesting things we are doing here:
3. For the case of our `iamRoleStatements:` we are now specifically stating which table we want to connect to. This block is telling AWS that these are the only resources that our Lambda functions have access to.
-### Commit Your Code
-
-Let's commit the changes we've made so far.
-
-``` bash
-$ git add .
-$ git commit -m "Adding our DynamoDB resource"
-```
-
Next, let's add our S3 bucket for file uploads.
diff --git a/_chapters/configure-multiple-aws-profiles.md b/_archives/configure-multiple-aws-profiles.md
similarity index 96%
rename from _chapters/configure-multiple-aws-profiles.md
rename to _archives/configure-multiple-aws-profiles.md
index 71ac31e5d2..8f8e903a9b 100644
--- a/_chapters/configure-multiple-aws-profiles.md
+++ b/_archives/configure-multiple-aws-profiles.md
@@ -1,10 +1,10 @@
---
layout: post
title: Configure Multiple AWS Profiles
-description: To use multiple IAM credentials to deploy your Serverless application you need to create a new AWS CLI profile. On local set the default AWS profile using the AWS_PROFILE bash variable. To deploy using your new profile use the "--aws-profile" option for the "serverless deploy" command. Alternatively, you can use the "profile:" setting in your serverless.yml.
+description: To use multiple IAM credentials to deploy your serverless application you need to create a new AWS CLI profile. On local set the default AWS profile using the AWS_PROFILE bash variable. To deploy using your new profile use the "--aws-profile" option for the "serverless deploy" command. Alternatively, you can use the "profile:" setting in your serverless.yml.
date: 2018-04-07 00:00:00
-context: true
comments_id: configure-multiple-aws-profiles/21
+redirect_from: /chapters/configure-multiple-aws-profiles.html
---
When we configured our AWS CLI in the [Configure the AWS CLI]({% link _chapters/configure-the-aws-cli.md %}) chapter, we used the `aws configure` command to set the IAM credentials of the AWS account we wanted to use to deploy our serverless application to.
@@ -74,7 +74,7 @@ provider:
profile: newAccount
```
-Note the `profile: newAccount` line here. This is telling Serverless to use the `newAccount` profile while running `serverless deploy`.
+Note the `profile: newAccount` line here. This is telling Serverless Framework to use the `newAccount` profile while running `serverless deploy`.
### Set Profiles per Stage
@@ -118,7 +118,7 @@ There are a couple of things happening here.
- 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]({% link _chapters/serverless-environment-variables.md %}).
+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]({% link _archives/serverless-environment-variables.md %}).
Now, when you deploy to production, Serverless Framework is going to use the `prodAccount` profile. And the resources will be provisioned inside `prodAccount` profile user's AWS account.
diff --git a/_chapters/configure-s3-in-serverless.md b/_archives/configure-s3-in-serverless.md
similarity index 64%
rename from _chapters/configure-s3-in-serverless.md
rename to _archives/configure-s3-in-serverless.md
index 5ff663e02f..e906553239 100644
--- a/_chapters/configure-s3-in-serverless.md
+++ b/_archives/configure-s3-in-serverless.md
@@ -1,19 +1,21 @@
---
layout: post
-title: Configure S3 in Serverless
+title: Configure S3 in serverless
date: 2018-02-28 00:00:00
lang: en
description: We can define our S3 Buckets using the Infrastructure as Code pattern by using CloudFormation in our serverless.yml. We are going to set the CORS policy and output the name of the bucket that's created.
-context: true
ref: configure-s3-in-serverless
comments_id: configure-s3-in-serverless/163
+redirect_from:
+ - /chapters/configure-s3-in-serverless.html
+ - /chapters/s3-as-a-serverless-service.html
---
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`.
+{%change%} Add the following to `resources/s3-bucket.yml`.
``` yml
Resources:
@@ -43,32 +45,21 @@ Outputs:
Ref: AttachmentsBucket
```
-If you recall from the [Create an S3 bucket for file uploads]({% link _chapters/create-an-s3-bucket-for-file-uploads.md %}) 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.
+If you recall from the [Create an S3 bucket for file uploads]({% link _archives/create-an-s3-bucket-for-file-uploads.md %}) 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 CloudFormation 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.
+S3 buckets (unlike DynamoDB tables) are globally named, so it is not really possible for us to know what our bucket is going to be called beforehand. Hence, we let CloudFormation 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.
+{%change%} Let's reference the resource in our `serverless.yml`. Replace your `resources:` block with the following.
``` yml
# 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.
-
-``` bash
-$ git add .
-$ git commit -m "Adding our S3 resource"
-```
-
And that's it. Next let's look into configuring our Cognito User Pool.
diff --git a/_chapters/connect-to-api-gateway-with-iam-auth.md b/_archives/connect-to-api-gateway-with-iam-auth.md
similarity index 90%
rename from _chapters/connect-to-api-gateway-with-iam-auth.md
rename to _archives/connect-to-api-gateway-with-iam-auth.md
index 78def1853f..b37b50b439 100644
--- a/_chapters/connect-to-api-gateway-with-iam-auth.md
+++ b/_archives/connect-to-api-gateway-with-iam-auth.md
@@ -3,12 +3,11 @@ layout: post
title: Connect to API Gateway with IAM Auth
description: For our React.js app to make requests to a serverless backend API secured using AWS IAM, we need to sign our requests using Signature Version 4. But to be able to do that we need to use our User Pool user token and get temporary IAM credentials from our Identity Pool. Using these temporary IAM credentials we can then generate the Signature Version 4 security headers and make a request using HTTP fetch.
date: 2018-04-11 00:00:00
-context: true
comments_id: 113
comments_id: comments-signup-with-aws-cognito/113
+redirect_from: /chapters/connect-to-api-gateway-with-iam-auth.html
---
-
Connecting to an API Gateway endpoint secured using AWS IAM can be challenging. You need to sign your requests using [Signature Version 4](http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). You can use:
- [Generated API Gateway SDK](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk.html)
@@ -18,7 +17,7 @@ The generated SDK can be hard to use since you need to re-generate it every time
However if you are looking to simply connect to API Gateway using the AWS JS SDK, we've create a standalone [**`sigV4Client.js`**](https://github.com/AnomalyInnovations/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:
+In this chapter we'll go over how to use the `sigV4Client.js`. The basic flow looks like this:
1. Authenticate a user with Cognito User Pool and acquire a user token.
2. With the user token get temporary IAM credentials from the Identity Pool.
@@ -28,11 +27,11 @@ In this chapter we'll go over how to use the the `sigV4Client.js`. The basic flo
The following method can authenticate a user to Cognito User Pool.
-``` js
+```js
function login(username, password) {
const userPool = new CognitoUserPool({
UserPoolId: USER_POOL_ID,
- ClientId: APP_CLIENT_ID
+ ClientId: APP_CLIENT_ID,
});
const user = new CognitoUser({ Username: username, Pool: userPool });
const authenticationData = { Username: username, Password: password };
@@ -40,8 +39,8 @@ function login(username, password) {
return new Promise((resolve, reject) =>
user.authenticateUser(authenticationDetails, {
- onSuccess: result => resolve(),
- onFailure: err => reject(err)
+ onSuccess: (result) => resolve(),
+ onFailure: (err) => reject(err),
})
);
}
@@ -49,18 +48,18 @@ function login(username, password) {
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:
-``` js
-await login('my_username', 'my_password');
+```js
+await login("my_username", "my_password");
```
### Generate Temporary IAM Credentials
Once your user is authenticated you can generate a set of temporary credentials. To do so you need to first get their JWT user token using the following:
-``` js
+```js
function getUserToken(currentUser) {
return new Promise((resolve, reject) => {
- currentUser.getSession(function(err, session) {
+ currentUser.getSession(function (err, session) {
if (err) {
reject(err);
return;
@@ -73,11 +72,11 @@ function getUserToken(currentUser) {
Where you can get the current logged in user using:
-``` js
+```js
function getCurrentUser() {
const userPool = new CognitoUserPool({
UserPoolId: config.cognito.USER_POOL_ID,
- ClientId: config.cognito.APP_CLIENT_ID
+ ClientId: config.cognito.APP_CLIENT_ID,
});
return userPool.getCurrentUser();
}
@@ -85,18 +84,17 @@ function getCurrentUser() {
And with the JWT token you can generate their temporary IAM credentials using:
-``` coffee
+```jsx
function getAwsCredentials(userToken) {
- const authenticator = `cognito-idp.${config.cognito
- .REGION}.amazonaws.com/${config.cognito.USER_POOL_ID}`;
+ 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
- }
+ [authenticator]: userToken,
+ },
});
return AWS.config.credentials.getPromise();
@@ -109,7 +107,7 @@ The `sigV4Client.js` needs [**crypto-js**](https://github.com/brix/crypto-js) in
Install it by running the following in your project root.
-``` bash
+```bash
$ npm install crypto-js --save
```
@@ -119,7 +117,7 @@ And to use the `sigV4Client.js` simply copy it over to your project.
This file can look a bit intimidating at first but it is just using the temporary credentials and the request parameters to create the necessary signed headers. To create a new `sigV4Client` we need to pass in the following:
-``` javascript
+```js
// Pseudocode
sigV4Client.newClient({
@@ -132,13 +130,13 @@ sigV4Client.newClient({
// API Gateway region
region,
// API Gateway URL
- endpoint
+ endpoint,
});
```
And to sign a request you need to use the `signRequest` method and pass in:
-``` javascript
+```js
// Pseudocode
const signedRequest = client.signRequest({
@@ -151,7 +149,7 @@ const signedRequest = client.signRequest({
// The request query parameters
queryParams,
// The request body
- body
+ body,
});
```
@@ -161,7 +159,7 @@ And `signedRequest.headers` should give you the signed headers that you need to
Let's put it all together. The following gives you a simple helper function to call an API Gateway endpoint.
-``` js
+```js
function invokeApig({
path,
method = "GET",
diff --git a/_chapters/create-a-cloudfront-distribution.md b/_archives/create-a-cloudfront-distribution.md
similarity index 97%
rename from _chapters/create-a-cloudfront-distribution.md
rename to _archives/create-a-cloudfront-distribution.md
index b465db71a1..aad08dcecd 100644
--- a/_chapters/create-a-cloudfront-distribution.md
+++ b/_archives/create-a-cloudfront-distribution.md
@@ -4,9 +4,9 @@ title: Create a CloudFront Distribution
date: 2017-02-08 00:00:00
lang: en
description: To server out our React.js app hosted on Amazon S3 through a CDN we are going to use CloudFront. We will create a CloudFront Distribution and point it to our S3 Bucket. We are also going to enable Gzip compression using the “Compress Objects Automatically” setting in the AWS console. And to ensure that our React.js app responds with the right HTTP headers, we will create a Custom Error Response.
-context: true
comments_id: create-a-cloudfront-distribution/104
ref: create-a-cloudfront-distribution
+redirect_from: /chapters/create-a-cloudfront-distribution.html
---
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 Distribution.
@@ -73,4 +73,4 @@ Pick **404** for the **HTTP Error Code** and select **Customize Error Response**
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. Creating a custom error response should take a couple of minutes to complete.
-Next up, let's point our domain to our CloudFront Distribution.
+Next up, let's point a custom domain to our CloudFront Distribution. And setup SSL.
diff --git a/_chapters/create-a-cognito-identity-pool.md b/_archives/create-a-cognito-identity-pool.md
similarity index 79%
rename from _chapters/create-a-cognito-identity-pool.md
rename to _archives/create-a-cognito-identity-pool.md
index 2563610a10..62c4d064d3 100644
--- a/_chapters/create-a-cognito-identity-pool.md
+++ b/_archives/create-a-cognito-identity-pool.md
@@ -5,8 +5,8 @@ date: 2017-01-05 00:00:00
lang: en
ref: create-a-cognito-identity-pool
description: Amazon Cognito Federated Identities helps us secure our AWS resources. We can use the Cognito User Pool as an identity provider for our serverless backend. To allow users to be able to upload files to our S3 bucket and connect to API Gateway we need to create an Identity Pool. We will assign it an IAM Policy with the name of our S3 bucket and prefix our files with the cognito-identity.amazonaws.com:sub. And we’ll add our API Gateway endpoint as a resource as well.
-context: true
comments_id: create-a-cognito-identity-pool/135
+redirect_from: /chapters/create-a-cognito-identity-pool.html
---
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 attachments for their notes. The final piece that ties all these services together in a secure way is called Amazon Cognito Federated Identities.
@@ -31,7 +31,7 @@ Enter an **Identity pool name**. If you have any existing Identity Pools, you'll

-Select **Authentication providers**. Under **Cognito** tab, enter **User Pool ID** and **App Client ID** of the User Pool created in the [Create a Cognito user pool]({% link _chapters/create-a-cognito-user-pool.md %}) chapter. Select **Create Pool**.
+Select **Authentication providers**. Under **Cognito** tab, enter **User Pool ID** and **App Client ID** of the User Pool created in the [Create a Cognito user pool]({% link _archives/create-a-cognito-user-pool.md %}) chapter. Select **Create Pool**.

@@ -47,7 +47,7 @@ It will warn you to read the documentation. 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]({% link _chapters/create-an-s3-bucket-for-file-uploads.md %}) chapter. And replace the `YOUR_API_GATEWAY_REGION` and `YOUR_API_GATEWAY_ID` with the ones that you get after you deployed your API in the last chapter.
+{%change%} 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]({% link _archives/create-an-s3-bucket-for-file-uploads.md %}) chapter. And replace the `YOUR_API_GATEWAY_REGION` and `YOUR_API_GATEWAY_ID` with the ones that you got back in the [Deploy the APIs]({% link _archives/deploy-the-apis.md %}) 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`.
@@ -88,16 +88,18 @@ In our case `YOUR_S3_UPLOADS_BUCKET_NAME` is `notes-app-uploads`, `YOUR_API_GATE
}
```
-A quick note on the block that relates to the S3 Bucket. In the above policy we are granting our logged in users access to the path `private/${cognito-identity.amazonaws.com:sub/}/`. Where `cognito-identity.amazonaws.com:sub` is the authenticated user's federated identity 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.
+Once a user has been authenticated with our User Pool and verified with our Identity Pool, he/she is assigned this [IAM role]({% link _archives/what-is-iam.md %}). This role limits what our user has access to in our AWS account.
+
+A quick note on the block that relates to the S3 Bucket. In the above policy we are granting our logged in users access to the path `private/${cognito-identity.amazonaws.com:sub}/`. Where `cognito-identity.amazonaws.com:sub` is the authenticated user's federated identity 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 authenticated user has access to two resources.
1. Files in the S3 bucket that are inside a folder with their federated identity 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 identity id is a UUID that is assigned by our Identity Pool. This is the id (`event.requestContext.identity.cognitoIdentityId`) that we were using as our user id back when we were creating our APIs.
+One other thing to note is that the federated identity id is a UUID that is assigned by our Identity Pool. This id is different from the one that a user is assigned in a User Pool. This is because you can have multiple authentication providers. The Identity Pool _federates_ these identities and gives each user a unique id.
-Select **Allow**.
+Next, select **Allow**.

@@ -111,4 +113,4 @@ Take a note of the **Identity pool ID** which will be required in the later chap

-Now before we test our serverless API let's take a quick look at the Cognito User Pool and Cognito Identity Pool and make sure we've got a good idea of the two concepts and the differences between them.
+Now we are ready to use what we've created so far to secure access to our APIs.
diff --git a/_chapters/create-a-cognito-test-user.md b/_archives/create-a-cognito-test-user.md
similarity index 82%
rename from _chapters/create-a-cognito-test-user.md
rename to _archives/create-a-cognito-test-user.md
index 3f6b0e4de7..f37f621180 100644
--- a/_chapters/create-a-cognito-test-user.md
+++ b/_archives/create-a-cognito-test-user.md
@@ -5,8 +5,8 @@ date: 2016-12-28 12:00:00
lang: en
ref: create-a-cognito-test-user
description: To test using the Cognito User Pool as an authorizer for our serverless API backend, we are going to create a test user. We can create a user from the AWS CLI using the aws cognito-idp sign-up and admin-confirm-sign-up command.
-context: true
comments_id: create-a-cognito-test-user/126
+redirect_from: /chapters/create-a-cognito-test-user.html
---
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 authentication portion of our app later.
@@ -15,7 +15,7 @@ In this chapter, we are going to create a test user for our Cognito User Pool. W
First, we will use AWS CLI to sign up a user with their email and password.
-In your terminal, run.
+{%change%} In your terminal, run.
``` bash
$ aws cognito-idp sign-up \
@@ -27,7 +27,7 @@ $ aws cognito-idp sign-up \
Now, the user is created in Cognito User Pool. However, before the user can authenticate with the User Pool, the account needs to be verified. Let's quickly verify the user using an administrator command.
-In your terminal, run.
+{%change%} In your terminal, run.
``` bash
$ aws cognito-idp admin-confirm-sign-up \
@@ -36,4 +36,4 @@ $ aws cognito-idp admin-confirm-sign-up \
--username admin@example.com
```
-Now our test user is ready. Next, let's set up the Serverless Framework to create our backend APIs.
+Now that our User Pool has been configured and ready to use, let's create our Identity Pool to manage access to our AWS resources.
diff --git a/_chapters/create-a-cognito-user-pool.md b/_archives/create-a-cognito-user-pool.md
similarity index 87%
rename from _chapters/create-a-cognito-user-pool.md
rename to _archives/create-a-cognito-user-pool.md
index 8227f01c44..afe3d65a0c 100644
--- a/_chapters/create-a-cognito-user-pool.md
+++ b/_archives/create-a-cognito-user-pool.md
@@ -5,8 +5,8 @@ date: 2016-12-28 00:00:00
lang: en
ref: create-a-cognito-user-pool
description: Amazon Cognito User Pool handles sign-up and sign-in functionality for web and mobile apps. We are going to create a Cognito User Pool to store and manage the users for our serverless app. We'll use the email address as username option since we want our users to login with their email. We are also going to set up our app as an App Client for our Cognito User Pool.
-context: true
comments_id: create-a-cognito-user-pool/148
+redirect_from: /chapters/create-a-cognito-user-pool.html
---
Our notes app needs to handle user accounts and authentication in a secure and reliable way. To do this we are going to use [Amazon Cognito](https://aws.amazon.com/cognito/).
@@ -69,11 +69,15 @@ Select **Add an app client**.
Enter **App client name**, un-select **Generate client secret**, select **Enable sign-in API for server-based authentication**, 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 option.
-- **Enable sign-in API for server-based authentication**: required by AWS CLI when managing the pool users via command line interface. We will be creating a test user through the command line interface in the next chapter.
+- **DISABLE client secret generation**: user pool apps with a client secret are not supported by the JavaScript SDK. We need to un-select the option.
+- **Enable username password auth for admin APIs for authentication**: required by AWS CLI when managing the pool users via command line interface. We will be creating a test user through the command line interface in the next chapter.

+Now select **Create app client**.
+
+
+
Your app client has been created. Take note of the **App client id** which will be required in the later chapters.

diff --git a/_chapters/create-a-dynamodb-table.md b/_archives/create-a-dynamodb-table.md
similarity index 62%
rename from _chapters/create-a-dynamodb-table.md
rename to _archives/create-a-dynamodb-table.md
index 0643de40c0..27e45597a0 100644
--- a/_chapters/create-a-dynamodb-table.md
+++ b/_archives/create-a-dynamodb-table.md
@@ -5,15 +5,24 @@ date: 2016-12-27 00:00:00
lang: en
ref: create-a-dynamodb-table
description: Amazon DynamoDB is a fully managed NoSQL database that we are going to use to power our serverless API backend. DynamoDB stores data in tables and each table has a primary key that cannot be changed once set. We are also going to provision the throughput capacity by setting reads and writes for our DynamoDB table.
-context: true
comments_id: create-a-dynamodb-table/139
+redirect_from: /chapters/create-a-dynamodb-table.html
---
-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](https://aws.amazon.com/dynamodb/) to do this.
+We are going to build a REST API for our notes app. It's a simple [CRUD (create, read, update, and delete)](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) API. Meaning that we'll be performing these operations on our database. We also want our users to be able to upload files as attachments to their notes.
+
+So in this section we'll be creating a couple of AWS resources:
+
+1. A database
+2. And a file storage service
+
+Let's first start by thinking about how the data is going to be stored. We are going to use [DynamoDB](https://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 multiple items, and each item is composed of one or more attributes. We are going to cover some basics in the following chapters. But to get a better feel for it, here is a [great guide on DynamoDB](https://www.dynamodbguide.com).
+Amazon DynamoDB is a fully managed NoSQL database that provides fast and predictable performance with seamless scalability. DynamoDB is also a serverless database, which means (as you guessed) it'll scale automatically and you only pay for what you use.
+
+Similar to other databases, DynamoDB stores data in tables. Each table contains multiple items, and each item is composed of one or more attributes. We are going to cover some basics in the following chapters. But to get a better feel for it, here is a [great guide on DynamoDB](https://www.dynamodbguide.com).
### Create Table
@@ -23,7 +32,7 @@ First, log in to your [AWS Console](https://console.aws.amazon.com) and select *
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.
@@ -42,12 +51,14 @@ Next scroll down and deselect **Use default settings**.

-Scroll down further and **On-demand** instead of **Provisioned**.
+Scroll down further and select **On-demand** instead of **Provisioned**.

[On-Demand Capacity](https://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 starting out, this ends up being a lot cheaper than the [Provisioned Capacity](https://aws.amazon.com/dynamodb/pricing/provisioned/) mode.
+If the On-Demand Capacity option is missing and there is an info box containing the message "You do not have the required role to enable Auto Scaling by default", you can create the table and afterwards modify the setting from the "Capacity" tab of the table settings page. The role mentioned by the info box is automatically created by the table creation process.
+
Finally, scroll down and hit **Create**.

@@ -56,8 +67,8 @@ The `notes` table has now been created. If you find yourself stuck with the **Ta

-It is also a good idea to set up backups for your DynamoDB table, especially if you are planning to use it in production. We cover this in an extra-credit chapter, [Backups in DynamoDB]({% link _chapters/backups-in-dynamodb.md %}).
+It is also a good idea to set up backups for your DynamoDB table, especially if you are planning to use it in production. We cover this in an extra-credit chapter, [Backups in DynamoDB]({% link _archives/backups-in-dynamodb.md %}).
-Next we'll set up an S3 bucket to handle file uploads.
+Next let's look at how we are going to store the files that our users upload.
[dynamodb-components]: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html
diff --git a/_archives/create-a-netlify-build-script.md b/_archives/create-a-netlify-build-script.md
new file mode 100644
index 0000000000..eb70738cbe
--- /dev/null
+++ b/_archives/create-a-netlify-build-script.md
@@ -0,0 +1,57 @@
+---
+layout: post
+title: Create a Netlify Build Script
+date: 2018-03-26 00:00:00
+lang: en
+description: To configure our Create React App with Netlify, we need to add a build script to our project root. To make sure that we return a HTTP status code of 200 for our React Router routes we will be adding a redirects rule.
+ref: create-a-netlify-build-script
+comments_id: create-a-build-script/189
+redirect_from:
+ - /chapters/create-a-netlify-build-script.html
+ - /chapters/create-a-build-script.html
+---
+
+To automate our React.js deployments with [Netlify](https://www.netlify.com) we just need to set up a build script. If you recall from the [previous chapter]({% link _archives/manage-environments-in-create-react-app.md %}), 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
+
+{%change%} Start by adding the following to a file called `netlify.toml` to your project root.
+
+``` toml
+# 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 left empty.
+
+2. The `publish` option 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` option is the build command that Netlify will use. If you recall the [Manage environments in Create React App]({% link _archives/manage-environments-in-create-react-app.md %}) chapter, this will seem familiar. In the default context the command is `REACT_APP_STAGE=dev npm run build`.
+
+The production 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-production branch. The `deploy-preview` is for pull requests.
+
+If you commit and push your changes to Git, you'll see Netlify pick up your build script.
diff --git a/_archives/create-an-s3-bucket-for-file-uploads.md b/_archives/create-an-s3-bucket-for-file-uploads.md
new file mode 100644
index 0000000000..1ebcaddbec
--- /dev/null
+++ b/_archives/create-an-s3-bucket-for-file-uploads.md
@@ -0,0 +1,85 @@
+---
+layout: post
+title: Create an S3 Bucket for File Uploads
+date: 2016-12-27 00:00:00
+lang: en
+ref: create-an-s3-bucket-for-file-uploads
+description: To allow users to upload files to our serverless app we are going to use Amazon S3 (Simple Storage Service). S3 allows you to store files and organize them into buckets.
+comments_id: create-an-s3-bucket-for-file-uploads/150
+redirect_from:
+ - /chapters/create-an-s3-bucket-for-file-uploads.html
+ - /chapters/create-a-s3-bucket-for-file-uploads.html
+---
+
+Now that we have [our database table]({% link _archives/create-a-dynamodb-table.md %}) 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 attachment.
+
+[Amazon S3](https://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 identified 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](https://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.
+
+
+
+Then scroll all the way down and click **Create bucket**.
+
+
+
+This should create your new S3 bucket. Before we move on, we need to make sure that our React.js frontend will be able to upload files to this bucket. Since it'll be hosted on a different domain, we need to enable [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
+
+Select the newly created bucket from the list.
+
+
+
+Select the **Permissions** tab
+
+
+
+Then scroll down to the **Cross-origin resource sharing (CORS)** section and hit **Edit**.
+
+
+
+Paste the following CORS configuration into the editor, then hit **Save changes**.
+
+``` json
+[
+ {
+ "AllowedHeaders": [
+ "*"
+ ],
+ "AllowedMethods": [
+ "GET",
+ "PUT",
+ "POST",
+ "HEAD",
+ "DELETE"
+ ],
+ "AllowedOrigins": [
+ "*"
+ ],
+ "ExposeHeaders": [],
+ "MaxAgeSeconds": 3000
+ }
+]
+```
+
+
+
+Note that, you can customize this configuration to use your own domain or a list of domains when you use this in production.
+
+Next we are going to start working on our serverless API backend.
diff --git a/_chapters/create-an-s3-bucket.md b/_archives/create-an-s3-bucket.md
similarity index 93%
rename from _chapters/create-an-s3-bucket.md
rename to _archives/create-an-s3-bucket.md
index bda5904197..48c645c1e5 100644
--- a/_chapters/create-an-s3-bucket.md
+++ b/_archives/create-an-s3-bucket.md
@@ -3,11 +3,12 @@ layout: post
title: Create an S3 Bucket
date: 2017-02-06 00:00:00
lang: en
-redirect_from: /chapters/create-a-s3-bucket.html
description: We are going to use S3 to host our React.js app on AWS. We first need to configure our S3 bucket with the correct Bucket Policy and enable Static Web Hosting through the AWS console before we can upload our app.
-context: true
comments_id: create-an-s3-bucket/48
ref: create-an-s3-bucket
+redirect_from:
+ - /chapters/create-an-s3-bucket.html
+ - /chapters/create-a-s3-bucket.html
---
To be able to host our note taking app, we need to upload the assets that are going to be served out statically on S3. S3 has a concept of buckets (or folders) to separate different types of files.
@@ -46,9 +47,9 @@ Buckets by default are not publicly accessible, so we need to change the S3 Buck

-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.
+{%change%} 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.
-``` json
+```json
{
"Version":"2012-10-17",
"Statement":[{
diff --git a/_archives/creating-a-ci-cd-pipeline-for-react.md b/_archives/creating-a-ci-cd-pipeline-for-react.md
new file mode 100644
index 0000000000..2980707e7f
--- /dev/null
+++ b/_archives/creating-a-ci-cd-pipeline-for-react.md
@@ -0,0 +1,28 @@
+---
+layout: post
+title: Creating a CI/CD pipeline for React
+date: 2020-11-04 00:00:00
+lang: en
+description: In this chapter we are going to look at how to create a CI/CD pipeline for our React app. We'll be using a service called Netlify for this. And we'll be using a branch based Git workflow.
+ref: creating-a-ci-cd-pipeline-for-react
+comments_id: creating-a-ci-cd-pipeline-for-react/188
+redirect_from:
+ - /chapters/creating-a-ci-cd-pipeline-for-react.html
+ - /chapters/automating-react-deployments.html
+---
+
+In the last couple of chapters, we [set our React.js app up in Netlify]({% link _archives/setting-up-your-project-on-netlify.md %}) and [added a custom domain to it]({% link _archives/custom-domain-in-netlify.md %}). In this chapter we'll look at how to use Netlify to create a CI/CD pipeline for our React app.
+
+Here's what the CI/CD pipeline for our React app will look like.
+
+
+
+Let's go over the workflow.
+
+- Our repo will be connected to our CI/CD service.
+- Any commits that are pushed to the `master` branch will be automatically built and deployed under our production url.
+- While any other commits that are pushed to a non-master branch, will be built and deployed to a unique development url.
+
+The one thing we haven't talked about for our React app is, environments. We want our development branches to connect to our development backend resources. And likewise for our production branch. We'll need to do this before we can automate our deployments.
+
+So let's do that next!
diff --git a/_archives/creating-feature-environments.md b/_archives/creating-feature-environments.md
new file mode 100644
index 0000000000..3e3c6048e9
--- /dev/null
+++ b/_archives/creating-feature-environments.md
@@ -0,0 +1,251 @@
+---
+layout: post
+title: Creating Feature Environments
+description: In this chapter we look at how to create feature environments for your serverless app. We'll go over the process of creating a new feature branch in Git and adding a new Serverless service using Seed.
+date: 2019-10-02 00:00:00
+comments_id: creating-feature-environments/1317
+redirect_from: /chapters/creating-feature-environments.html
+---
+
+Over the last couple of chapters we looked at how to work on Lambda and API Gateway locally. However, besides Lambda and API Gateway, your project will have other AWS services. To run your code locally, you have to simulate all the AWS services. Similar to [serverless-offline](https://www.github.com/dherault/serverless-offline), there are plugins like [serverless-dynamodb-local](https://www.github.com/99xt/serverless-dynamodb-local) and [serverless-offline-sns](https://github.com/mj1618/serverless-offline-sns) that can simulate DynamoDB and SNS. However, mocking only takes you so far since they do not simulate IAM permissions and they are not always up to date with the services' latest changes. You want to test your code with the real resources.
+
+Serverless is really good at creating ephemeral environments. Let's look at what the workflow looks like when you are trying to add a new feature to your app.
+
+As an example we'll add a feature that lets you _like_ a note. We will add a new API endpoint `/notes/{id}/like`. We are going to work on this in a new feature branch and then deploy this using Seed.
+
+### Create a feature branch
+
+We will create a new feature branch called `like`.
+
+```bash
+$ git checkout -b like
+```
+
+Since we are going to be using `/notes/{id}/like` as our endpoint we need to first export the `/notes/{id}` API path. Open the `serverless.yml` in the `services/notes-api` service, and append to the resource outputs.
+
+```yaml
+ApiGatewayResourceNotesIdVarId:
+ Value:
+ Ref: ApiGatewayResourceNotesIdVar
+ Export:
+ Name: ${self:custom.stage}-ExtApiGatewayResourceNotesIdVarId
+```
+
+Our resource outputs should now look like:
+
+```yaml
+
+---
+- Outputs:
+ ApiGatewayRestApiId:
+ Value:
+ Ref: ApiGatewayRestApi
+ Export:
+ Name: ${self:custom.stage}-ExtApiGatewayRestApiId
+
+ ApiGatewayRestApiRootResourceId:
+ Value:
+ Fn::GetAtt:
+ - ApiGatewayRestApi
+ - RootResourceId
+ Export:
+ Name: ${self:custom.stage}-ExtApiGatewayRestApiRootResourceId
+
+ ApiGatewayResourceNotesIdVarId:
+ Value:
+ Ref: ApiGatewayResourceNotesIdVar
+ Export:
+ Name: ${self:custom.stage}-ExtApiGatewayResourceNotesIdVarId
+```
+
+Let's create the `like-api` service.
+
+```bash
+$ cd services
+$ mkdir like-api
+$ cd like-api
+```
+
+Add a `serverless.yml`.
+
+```yaml
+service: notes-app-ext-like-api
+
+plugins:
+ - serverless-bundle
+ - serverless-offline
+
+custom: ${file(../../serverless.common.yml):custom}
+
+package:
+ individually: true
+
+provider:
+ name: aws
+ runtime: nodejs12.x
+ stage: dev
+ region: us-east-1
+ tracing:
+ lambda: true
+
+ apiGateway:
+ restApiId: !ImportValue ${self:custom.stage}-ExtApiGatewayRestApiId
+ restApiRootResourceId: !ImportValue ${self:custom.stage}-ExtApiGatewayRestApiRootResourceId
+ restApiResources:
+ /notes/{id}: !ImportValue ${self:custom.stage}-ExtApiGatewayResourceNotesIdVarId
+
+ environment:
+ stage: ${self:custom.stage}
+
+ iamRoleStatements:
+ - ${file(../../serverless.common.yml):lambdaPolicyXRay}
+
+functions:
+ like:
+ handler: like.main
+ events:
+ - http:
+ path: /notes/{id}/like
+ method: post
+ cors: true
+ authorizer: aws_iam
+```
+
+Again, the `like-api` will share the same API endpoint as the `notes-api` service.
+
+Add the handler file `like.js`.
+
+```js
+import { success } from "../../libs/response-lib";
+
+export async function main(event, context) {
+ // Business logic code for liking a post
+
+ return success({ status: true });
+}
+```
+
+Now before we push our Git branch, let's enable the branch workflow in Seed.
+
+### Enable branch workflow in Seed
+
+Go to your app on Seed and head over to the **Pipeline** tab and hit **Edit Pipeline**.
+
+
+
+Enable **Auto-deploy branches**.
+
+
+
+Select the **dev** stage, since we want the stage to be deployed into the **Development** AWS account. Click **Enable**.
+
+
+
+Click **Pipeline** to go.
+
+
+
+### Add the new service to Seed
+
+Click on **Add a Service**.
+
+
+
+Enter the path to the service `services/like-api` and click **Search**.
+
+
+
+Since the code has not been committed to Git yet, Seed is not able to find the `serverless.yml` of the service. That's totally fine. We'll specify a name for the service `like-api`. Then hit **Add Service**.
+
+
+
+This should add the new service across all your stages.
+
+
+
+By default, the new service is added to the last deploy phase. Let's click on **Manage Deploy Phases**, and move it to Phase 2. This is because it's dependent on the API Gateway resources exported by `notes-api`.
+
+
+
+### Git push to deploy new feature
+
+Now we are ready to create our new feature environment. Go back to our command line, and then push the code to the `like` branch.
+
+```bash
+$ git add .
+$ git commit -m "Add like API"
+$ git push --set-upstream origin like
+```
+
+Back in Seed, a new stage called **like** is created and is being deployed automatically.
+
+
+
+After the new stage successfully deploys, you can get the API endpoint in the stage's resources page. Head over to the **Resources** tab.
+
+
+
+And select the **like** stage.
+
+
+
+You will see the API Gateway endpoint for the **like** stage and the API path for the **like** handler.
+
+
+
+You can now use the endpoint in your frontend for further testing and development.
+
+Now that our new feature environment has been created, let's quickly look at the flow for working on your new feature.
+
+### Working on new feature environments locally
+
+Once the environment has been created, we want to continue working on the feature. A common problem people run into is that `serverless deploy` takes very long to execute. And running `serverless deploy` for every change just does not work.
+
+#### Why is 'serverless deploy' slow?
+
+When you run `serverless deploy`, Serverless Framework does two things:
+
+1. Package the Lambda code into zip files.
+2. Build a CloudFormation template with all the resources defined in `serverless.yml`.
+
+The code is uploaded to S3 and the template is submitted to CloudFormation.
+
+There are a couple of things that are causing the slowness here:
+
+- When working on a feature, most of the changes are code changes. It is not necessary to rebuild and resubmit the CloudFormation template for every code change.
+- When making a code change, a lot of the times you are only changing one Lambda function. In this case, it's not necessary to repackage the code for all Lambda functions in the service.
+
+#### Deploying individual functions
+
+Fortunately, there is a way to deploy individual functions using the `serverless deploy -f` command. Let's take a look at an example.
+
+Say we change our new `like.js` code to:
+
+```js
+import { success } from "../../libs/response-lib";
+
+export async function main(event, context) {
+ // Business logic code for liking a post
+
+ console.log("adding some debug code to test");
+
+ return success({ status: true });
+}
+```
+
+To deploy the code for this function, run:
+
+```bash
+$ cd services/like-api
+$ serverless deploy -f like -s like
+```
+
+Deploying an individual function should be much quicker than deploying the entire stack.
+
+#### Deploy multiple functions
+
+Sometimes a code change can affect multiple functions at the same time. For example, if you changed a shared library, you have to redeploy all the services importing the library.
+
+However, there isn't a convenient way to deploy multiple Lambda functions. If you can easily tell which Lambda functions are affected, deploy them individually. If there are many functions involved, run `serverless deploy -s like` to deploy all of them. Just to be on the safe side.
+
+Now let's assume we are done working on our new feature and we want our team lead to review our code before we promote it to production. To do this we are going to create a pull request environment. Let's look at how to do that next.
diff --git a/_archives/creating-pull-request-environments.md b/_archives/creating-pull-request-environments.md
new file mode 100644
index 0000000000..1fb0f902cb
--- /dev/null
+++ b/_archives/creating-pull-request-environments.md
@@ -0,0 +1,76 @@
+---
+layout: post
+title: Creating Pull Request Environments
+description: In this chapter we'll look at the workflow for creating new pull request based environments for your serverless app using Seed.
+date: 2019-10-02 00:00:00
+comments_id: creating-pull-request-environments/1318
+redirect_from: /chapters/creating-pull-request-environments.html
+---
+
+Now that we are done working on our new feature, we would like our team lead to review our work before promoting it to production. To do that we are going to create a pull request and Seed will automatically create an ephemeral environment for it.
+
+### Enable pull request workflow on Seed
+
+To enable auto-deploying pull requests, head over to your app on Seed. Click **Settings**.
+
+
+
+And **Enable auto-deploy pull requests**.
+
+
+
+Select the **dev** stage, since we want the stage to be deployed into the **Development** AWS account. Click **Enable**.
+
+
+
+### Create a pull request
+
+Go to GitHub, and select the **like** branch. Then hit **New pull request**.
+
+
+
+Click **Create pull request**.
+
+
+
+Now back in Seed, a new stage (in this case **pr2**) should be created and is being deployed automatically.
+
+
+
+After the **pr2** stage successfully deploys, you can see the deployed API endpoint on the PR page. You can give the endpoint to your frontend team for testing.
+
+
+
+You can also access the **pr2** stage and the upstream **like** stage on Seed via the **View deployment** button. And you can see the deployment status for each service under the **checks** section.
+
+
+
+Now that our new feature has been reviewed, we are ready to merge it to master.
+
+### Merge to master
+
+Once your final test looks good, you are ready to merge the pull request. Go to GitHub's pr page and click **Merge pull request**.
+
+
+
+Back in Seed, this will trigger a deployment in the **dev** stage automatically, since the stage auto-deploys changes in the **master** branch. Also, since merging the pull request closes it, this will automatically remove the **pr2** stage.
+
+
+
+After the deployment completes and the **pr2** stage is removed, this is what your pipeline should look like:
+
+
+
+From GitHub's pull request screen, we can remove the **like** branch.
+
+
+
+Back in Seed, this will trigger the **like** stage to be automatically removed.
+
+
+
+After the removal is completed, your pipeline should now look like this.
+
+
+
+Next, we are ready to promote our new feature to production.
diff --git a/_archives/cross-stack-references-in-serverless.md b/_archives/cross-stack-references-in-serverless.md
new file mode 100644
index 0000000000..8055b5961f
--- /dev/null
+++ b/_archives/cross-stack-references-in-serverless.md
@@ -0,0 +1,90 @@
+---
+layout: post
+title: Cross-Stack References in serverless
+description: AWS CloudFormation allows us to link multiple serverless services using cross-stack references. To create a cross-stack reference, export a value using the "Export:" option in CloudFormation or CfnOutput construct in CDK. To import it in your serverless.yml, use "Fn::ImportValue".
+date: 2018-04-02 13:00:00
+ref: cross-stack-references-in-serverless
+comments_id: cross-stack-references-in-serverless/405
+redirect_from: /chapters/cross-stack-references-in-serverless.html
+---
+
+In the previous chapter we looked at [some of the most common patterns for organizing your serverless applications]({% link _archives/organizing-serverless-projects.md %}). Now let's look at how to work with multiple services in your Serverless application.
+
+You might recall that a Serverless Framework service is where a single `serverless.yml` is used to define the project. And the `serverless.yml` file is converted into a [CloudFormation template](https://aws.amazon.com/cloudformation/aws-cloudformation-templates/) using Serverless Framework. This means that in the case of multiple services you might need to reference a resource that is available in a different service.
+
+You also might be defining your AWS infrastructure using [AWS CDK]({% link _archives/what-is-aws-cdk.md %}). And you want to make sure your serverless API is connected to those resources.
+
+For example, you might have your DynamoDB tables created in CDK and your APIs (as a Serverless Framework service) need to refer to them. Of course you don't want to hard code this. To do this we are going to be using cross-stack references.
+
+A cross-stack reference is a way for one CloudFormation template to refer to the resource in another CloudFormation template.
+
+- Cross-stack references have a name and value.
+- Cross-stack references only apply within the same region.
+- The name needs to be unique for a given region in an AWS account.
+
+A reference is created when one stack creates a CloudFormation export and another imports it. So for example, our `DynamoDBStack.js` is exporting the name of our DynamoDB table, and our `notes-api` service is importing it. Once the reference has been created, you cannot remove the DynamoDB stack without first removing the stack that is referencing it (the `notes-api`).
+
+The above relationship between two stacks means that they need to be deployed and removed in a specific order. We'll be looking at this later.
+
+### CloudFormation Export in CDK
+
+To create a cross-stack reference, we first create a CloudFormation export.
+
+```js
+new CfnOutput(this, "TableName", {
+ value: table.tableName,
+ exportName: app.logicalPrefixedName("ExtTableName"),
+});
+```
+
+The above is a CDK example from our `DynamoDBStack.js`.
+
+Here the `exportName` is the name of the CloudFormation export. We use a convenience method from [SST](https://github.com/sst/sst) called `app.logicalPrefixedName` that prefixes our export name with the name of the stage we are deploying to, and the name of our SST app. This ensures that our export name is unique when we deploy our stack across multiple environments.
+
+### CloudFormation Export in Serverless Framework
+
+Similarly, we can create a CloudFormation export in Serverless Framework by adding the following
+
+```yml
+resources:
+ Outputs:
+ NotePurchasedTopicArn:
+ Value:
+ Ref: NotePurchasedTopic
+ Export:
+ Name: ${self:custom.stage}-ExtNotePurchasedTopicArn
+```
+
+The above is an example from the `serverless.yml` of our `billing-api`. We can add a `resources:` section to our `serverless.yml` and the `Outputs:` allows us to add CloudFormation exports.
+
+Just as above we need to name our CloudFormation export. We do it using the `Name:` property. Here we are prefixing our export name with the stage name (`${self:custom.stage}`) to make it unique across environments.
+
+The `${self:custom.stage}` is a custom variable that we define at the top of our `serverless.yml`.
+
+```yml
+# 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}
+```
+
+### Importing a CloudFormation Export
+
+Now once we've created a CloudFormation export, we need to import it in our `serverless.yml`. To do so, we'll use the `Fn::ImportValue` CloudFormation function.
+
+For example, in our `notes-api/serverless.yml`.
+
+```yml
+provider:
+ environment:
+ tableName: !ImportValue ${self:custom.sstApp}-ExtTableName
+```
+
+We import the name of the DynamoDB table that we created and exported in `DynamoDBStack.js`.
+
+### Advantages of Cross-Stack References
+
+As your application grows, it can become hard to track the dependencies between the services in the application. 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 application grows.
+
+The other advantage is that you can easily recreate the entire application (say for testing) with ease. This is because none of the services of your application are statically linked to each other.
+
+In the next chapter let's look at how we share code between our services.
diff --git a/_chapters/custom-domain-in-netlify.md b/_archives/custom-domain-in-netlify.md
similarity index 60%
rename from _chapters/custom-domain-in-netlify.md
rename to _archives/custom-domain-in-netlify.md
index 9737f83559..a0fb05fd1c 100644
--- a/_chapters/custom-domain-in-netlify.md
+++ b/_archives/custom-domain-in-netlify.md
@@ -4,32 +4,14 @@ title: Custom Domains in Netlify
date: 2018-03-28 00:00:00
lang: en
description: To configure your React app with custom domains on Netlify and AWS, you need to point the Route 53 DNS to Netlify. Create a new Record set, add an A Record, and a CNAME for your new Netlify project.
-context: true
ref: custom-domains-in-netlify
comments_id: custom-domains-in-netlify/191
+redirect_from: /chapters/custom-domain-in-netlify.html
---
-Now that we have our first deployment, let's configure a custom domain for our app through Netlify.
+Now that we [have our React app hosted on Netlify]({% link _archives/setting-up-your-project-on-netlify.md %}), let's configure a custom domain.
----
-
-**A Note to Readers from Part I**
-
-The following section 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 options:
-
-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 section you can configure something like `https://notes-app-2.my-domain.com`. This is the preferred option 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](https://demo.serverless-stack.com) and the [Part II version here](https://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 floating 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]({% link _chapters/setup-your-domain-with-cloudfront.md %}#point-domain-to-cloudfront-distribution) and the [www domain]({% link _chapters/setup-www-domain-redirect.md %}#point-www-domain-to-cloudfront-distribution) in Part I.
-
-If you are not sure about the two above options or have any questions, post a comment in the discussion thread that we link to at the bottom of the chapter.
-
----
-
-Let's get started!
+Before we get started, make sure to follow this chapter to [purchase a domain on Amazon Route 53]({% link _chapters/purchase-a-domain-with-route-53.md %}).
### Pick a Netlify Site Name
@@ -57,7 +39,7 @@ And hit **Add custom domain**.

-Type in the name of our domain, for example it might be `demo-serverless-stack.com`. And hit **Save**.
+Type in the name of our domain, for example it might be `demo-sst.dev`. And hit **Save**.

@@ -69,15 +51,15 @@ Next hit **Check DNS configuration**.

-This will show you the instructions for setting up your domain through Route 53.
+This will show you the instructions for setting up your domain through Route 53.

### DNS Settings in Route 53
-To do this we need to head back to the [AWS Console](https://console.aws.amazon.com/). and search for Route 53 as the service.
+To do this we need to go to the [AWS Console](https://console.aws.amazon.com/). and search for Route 53 as the service.
-
+
Click on **Hosted zones**.
@@ -109,11 +91,11 @@ Back in Netlify, hit **HTTPS** in the side panel. And it should say that it is w

-Once that is complete, Netlify will automatically provision your SSL ceritificate using Let's Encrypt.
+Once that is complete, Netlify will automatically provision your SSL certificate using Let's Encrypt.

-Wait a few seconds for the ceritificate to be provisioned.
+Wait a few seconds for the certificate to be provisioned.

@@ -121,4 +103,4 @@ Now if you head over to your browser and go to your custom domain, your notes ap

-We have our app in production but we haven't had a chance to go through our workflow just yet. Let's take a look at that next.
+Now our React.js app is hosted on Netlify with a custom domain! Next, let's look at how to use Netlify as a CI/CD pipeline for our React app.
diff --git a/_chapters/customize-the-serverless-iam-policy.md b/_archives/customize-the-serverless-iam-policy.md
similarity index 74%
rename from _chapters/customize-the-serverless-iam-policy.md
rename to _archives/customize-the-serverless-iam-policy.md
index 3e47f98f83..16a8ffd037 100644
--- a/_chapters/customize-the-serverless-iam-policy.md
+++ b/_archives/customize-the-serverless-iam-policy.md
@@ -1,10 +1,10 @@
---
layout: post
-title: Customize the Serverless IAM Policy
+title: Customize the serverless IAM Policy
description: Serverless Framework deploys using the policy attached to the IAM credentials in your AWS CLI profile. To customize the IAM Policy used, access can be restricted to the services that Serverless Framework needs, and to the project that is being deployed.
date: 2018-04-08 00:00:00
-context: true
comments_id: customize-the-serverless-iam-policy/18
+redirect_from: /chapters/customize-the-serverless-iam-policy.html
---
Serverless Framework deploys using the policy attached to the IAM credentials in your AWS CLI profile. Back in the [Create an IAM User]({% link _chapters/create-an-iam-user.md %}) 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.
@@ -19,10 +19,10 @@ The permissions required can be categorized into the following areas:
Granting **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:
+A basic serverless project needs permissions to the following AWS services:
- **CloudFormation** to create change set and update stack
-- **S3** to upload and store Serverless artifacts and Lambda source code
+- **S3** to upload and store serverless artifacts and Lambda source code
- **CloudWatch Logs** to store Lambda execution logs
- **IAM** to manage policies for the Lambda IAM Role
- **API Gateway** to manage API endpoints
@@ -80,7 +80,7 @@ This policy grants your Serverless Framework project access to all the resources
### 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 ``, `` and `` for your specific project.
+Below is a more nuanced policy template that restricts access to the serverless project that is being deployed. Make sure to replace ``, `` and `` for your specific project.
``` json
{
@@ -89,144 +89,142 @@ Below is a more nuanced policy template that restricts access to the Serverless
{
"Effect": "Allow",
"Action": [
- "cloudformation:Describe*",
- "cloudformation:List*",
- "cloudformation:Get*",
- "cloudformation:CreateStack",
- "cloudformation:UpdateStack",
- "cloudformation:DeleteStack"
+ "apigateway:GET",
+ "apigateway:PATCH",
+ "apigateway:POST",
+ "apigateway:PUT"
],
- "Resource": "arn:aws:cloudformation:::stack/*/*"
+ "Resource": [
+ "arn:aws:apigateway:::/apis"
+ ]
},
{
"Effect": "Allow",
"Action": [
- "cloudformation:ValidateTemplate"
+ "apigateway:GET",
+ "apigateway:PATCH",
+ "apigateway:POST",
+ "apigateway:PUT"
],
- "Resource": "*"
+ "Resource": [
+ "arn:aws:apigateway:::/apis/*"
+ ]
},
{
"Effect": "Allow",
"Action": [
- "s3:CreateBucket",
- "s3:DeleteBucket",
- "s3:Get*",
- "s3:List*"
+ "cloudformation:CancelUpdateStack",
+ "cloudformation:ContinueUpdateRollback",
+ "cloudformation:CreateChangeSet",
+ "cloudformation:CreateStack",
+ "cloudformation:CreateUploadBucket",
+ "cloudformation:DeleteStack",
+ "cloudformation:Describe*",
+ "cloudformation:EstimateTemplateCost",
+ "cloudformation:ExecuteChangeSet",
+ "cloudformation:Get*",
+ "cloudformation:List*",
+ "cloudformation:UpdateStack",
+ "cloudformation:UpdateTerminationProtection"
],
- "Resource": [
- "arn:aws:s3:::*"
- ]
+ "Resource": "arn:aws:cloudformation:::stack/*/*"
},
{
"Effect": "Allow",
"Action": [
- "s3:*"
+ "cloudformation:ValidateTemplate"
],
- "Resource": [
- "arn:aws:s3:::*/*"
- ]
+ "Resource": "*"
},
{
"Effect": "Allow",
"Action": [
- "logs:DescribeLogGroups"
+ "ec2:Describe*"
],
- "Resource": "arn:aws:logs:::log-group::log-stream:*"
+ "Resource": [
+ "*"
+ ]
},
{
+ "Effect": "Allow",
"Action": [
- "logs:CreateLogGroup",
- "logs:CreateLogStream",
- "logs:DeleteLogGroup",
- "logs:DeleteLogStream",
- "logs:DescribeLogStreams",
- "logs:FilterLogEvents"
+ "events:Put*",
+ "events:Describe*",
+ "events:List*"
],
- "Resource": "arn:aws:logs:::log-group:/aws/lambda/*:log-stream:*",
- "Effect": "Allow"
+ "Resource": "arn:aws:events:::rule/*"
},
{
"Effect": "Allow",
"Action": [
- "iam:GetRole",
- "iam:PassRole",
+ "iam:AttachRolePolicy",
"iam:CreateRole",
"iam:DeleteRole",
+ "iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
- "iam:PutRolePolicy",
- "iam:AttachRolePolicy",
- "iam:DeleteRolePolicy"
+ "iam:GetRole",
+ "iam:PassRole",
+ "iam:PutRolePolicy"
],
"Resource": [
- "arn:aws:iam:::role/*-lambdaRole"
+ "arn:aws:iam::*:role/*-lambdaRole"
]
},
{
"Effect": "Allow",
"Action": [
- "apigateway:GET",
- "apigateway:POST",
- "apigateway:PUT",
- "apigateway:DELETE"
+ "lambda:*"
],
"Resource": [
- "arn:aws:apigateway:::/restapis"
+ "arn:aws:lambda:*:*:function:*"
]
},
{
"Effect": "Allow",
"Action": [
- "apigateway:GET",
- "apigateway:POST",
- "apigateway:PUT",
- "apigateway:DELETE"
+ "logs:DescribeLogGroups"
],
- "Resource": [
- "arn:aws:apigateway:::/restapis/*"
- ]
+ "Resource": "arn:aws:logs:::log-group::log-stream:*"
},
{
- "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"
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogStreams",
+ "logs:FilterLogEvents"
],
- "Resource": [
- "arn:aws:lambda:*::function:*"
- ]
+ "Resource": "arn:aws:logs:::log-group:/aws/lambda/*:log-stream:*",
+ "Effect": "Allow"
},
{
"Effect": "Allow",
"Action": [
- "ec2:DescribeSecurityGroups",
- "ec2:DescribeSubnets",
- "ec2:DescribeVpcs"
+ "s3:CreateBucket",
+ "s3:DeleteBucket",
+ "s3:DeleteBucketPolicy",
+ "s3:DeleteObject",
+ "s3:DeleteObjectVersion",
+ "s3:Get*",
+ "s3:List*",
+ "s3:PutBucketNotification",
+ "s3:PutBucketPolicy",
+ "s3:PutBucketTagging",
+ "s3:PutBucketWebsite",
+ "s3:PutEncryptionConfiguration",
+ "s3:PutObject"
],
"Resource": [
- "*"
+ "arn:aws:s3:::*"
]
},
{
"Effect": "Allow",
"Action": [
- "events:Put*",
- "events:Remove*",
- "events:Delete*",
- "events:Describe*"
+ "s3:*"
],
- "Resource": "arn:aws:events:::rule/*"
+ "Resource": [
+ "arn:aws:s3:::*/*"
+ ]
}
]
}
@@ -246,6 +244,6 @@ provider:
In the above `serverless.yml`, the `` is `us-east-1` and the `` 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.
+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 additional resources in your **serverless.yml**, or install Serverless plugins, or invoke any AWS APIs in your application 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](https://github.com/serverless/serverless/issues/1439) required for a Serverless project.
+It provides sufficient permissions for a minimal Serverless Framework project. However, if you provision any additional resources in your **serverless.yml**, or install Serverless plugins, or invoke any AWS APIs in your application 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](https://github.com/serverless/serverless/issues/1439) required for a Serverless project.
diff --git a/_chapters/debugging-serverless-api-issues.md b/_archives/debugging-serverless-api-issues.md
similarity index 96%
rename from _chapters/debugging-serverless-api-issues.md
rename to _archives/debugging-serverless-api-issues.md
index 5ce3e69ce7..9c51864534 100644
--- a/_chapters/debugging-serverless-api-issues.md
+++ b/_archives/debugging-serverless-api-issues.md
@@ -3,13 +3,13 @@ layout: post
title: Debugging Serverless API Issues
description: Debugging serverless APIs can be tricky because there isn’t enough visibility on all the steps a request goes through. A very common issue is an invalid or missing IAM Role while using aws_iam as an authorizer for API Gateway and Lambda. To fix this use the Policy Simulator to ensure that your IAM Role has access to API Gateway.
date: 2018-04-04 00:00:00
-context: true
comments_id: debugging-serverless-api-issues/143
+redirect_from: /chapters/debugging-serverless-api-issues.html
---
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](https://seed.run). Check out [Common Serverless Errors](https://seed.run/docs/serverless-errors/) and do a quick search for your error message and see if it has a solution.
+We've also compiled a list of some of the most common serverless errors over on [Seed](https://seed.run). Check out [Common Serverless Errors](https://seed.run/docs/serverless-errors/) and do a quick search for your error message and see if it has a solution.
When a request is made to your serverless API, it starts by hitting API Gateway and makes its way through to Lambda and invokes your function. 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, pinpointing the issue can be a bit tricky. We are going to take a look at the following issues:
@@ -18,14 +18,13 @@ When a request is made to your serverless API, it starts by hitting API Gateway
- [Lambda Function Error](#lambda-function-error)
- [Lambda Function Timeout](#lambda-function-timeout)
-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]({% link _chapters/api-gateway-and-lambda-logs.md %}).
-
+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]({% link _archives/api-gateway-and-lambda-logs.md %}).
### 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:
-```
+```txt
https://API_ID.execute-api.REGION.amazonaws.com/STAGE/PATH
```
@@ -42,15 +41,14 @@ An API request will fail if:
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
+This happens when your API endpoint uses **aws_iam** as the authorizer, and the IAM role
assigned to the Cognito Identity 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 still 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 Identity Pool users have the required permissions, using the [IAM policy Simulator](https://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 identity pool]({% link _chapters/create-a-cognito-identity-pool.md %}) chapter.
+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 identity pool]({% link _archives/create-a-cognito-identity-pool.md %}) chapter.
Select **Cognito** from your [AWS Console](https://console.aws.amazon.com).
@@ -88,7 +86,7 @@ Select **API Gateway** as the service and select the **Invoke** action.

-Expand the service and enter the API Gateway endpoint ARN, then select **Run Simulation**. The format here is the same one we used back in the [Create a Cognito identity pool]({% link _chapters/create-a-cognito-identity-pool.md %}) 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/*`.
+Expand the service and enter the API Gateway endpoint ARN, then select **Run Simulation**. The format here is the same one we used back in the [Create a Cognito identity pool]({% link _archives/create-a-cognito-identity-pool.md %}) 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/*`.

@@ -122,7 +120,7 @@ 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.
-``` coffee
+```coffee
...
{
"Effect": "Allow",
@@ -142,28 +140,25 @@ 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 function but it fails to execute properly due to uncaught exceptions, it'll error out. These are pretty straightforward to debug. When this happens, AWS Lambda will attempt 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
Sometimes we might run into a case where the Lambda function just times out. Normally, a Lambda function will end its execution by invoking the **callback** function that was passed in. By default, the callback will wait until the Node.js runtime event loop is empty before returning the results to the caller. If the Lambda function has an open connection to, let's say a database server, the event loop is not empty, and the callback will wait indefinitely until the connection is closed or the Lambda function times 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.
-``` javascript
+```js
export async function handler(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
-
+
...
};
```
This effectively allows a Lambda function to return its result to the caller without requiring that the database connection be closed. This allows the Lambda function to reuse the same connection across calls, and it reduces the execution time 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.
diff --git a/_archives/deploy-a-serverless-app-with-dependencies.md b/_archives/deploy-a-serverless-app-with-dependencies.md
new file mode 100644
index 0000000000..26095ba422
--- /dev/null
+++ b/_archives/deploy-a-serverless-app-with-dependencies.md
@@ -0,0 +1,82 @@
+---
+layout: post
+title: Deploy a Serverless App with Dependencies
+description: In this chapter we go over how to locally deploy a serverless app with multiple interdependent services. So you'll need to ensure that you deploy the service that is exporting the reference before deploying the one that imports it. You'll only need to do this for the first time.
+date: 2019-09-29 00:00:00
+comments_id: deploying-multiple-services-in-serverless/410
+redirect_from:
+ - /chapters/deploy-a-serverless-app-with-dependencies.html
+ - /chapters/deploying-multiple-services-in-serverless.html
+---
+
+So now that we have a couple of downstream services that are referencing a resource deployed in an upstream service; let's look at how this dependency affects the way we deploy our app.
+
+The short version is that:
+
+- When you introduce a new dependency in your app you cannot deploy all the services concurrently.
+- However, once these services have been deployed, you can do so.
+
+#### First deployment
+
+In our [resources repo]({{ site.backend_ext_resources_github_repo }}) we are using SST to deploy our CDK app. CDK internally keeps track of the dependencies between stacks.
+
+Our `stacks/index.js` looks like this.
+
+```js
+export default function main(app) {
+ new DynamoDBStack(app, "dynamodb");
+
+ const s3 = new S3Stack(app, "s3");
+
+ new CognitoStack(app, "cognito", { bucketArn: s3.bucket.bucketArn });
+}
+```
+
+Here CDK knows that the `CognitoStack` depends on the `S3Stack`. And it needs to wait for the `S3Stack` to complete first.
+
+[SST](https://github.com/sst/sst) will deploy the stacks in our CDK app concurrently while ensuring that the dependencies are respected.
+
+Next for the [API repo]({{ site.backend_ext_api_github_repo }}) for the first time, you have to:
+
+- Deploy the `notes-api` first. This will export the value `dev-ExtApiGatewayRestApiId` and `dev-ExtApiGatewayRestApiRootResourceId`.
+- Then deploy the `billing-api` next. This will export the value `dev-ExtNotePurchasedTopicArn`.
+- Then deploy the `notify-job`.
+
+Assuming that you are deploying to the `dev` stage.
+
+If you were to deploy `billing-api` and `notify-job` concurrently, the `notify-job` will fail with the following CloudFormation error:
+
+```txt
+notify-job - No export named dev-ExtNotePurchasedTopicArn found.
+```
+
+This error is basically saying that the ARN referenced in its `serverless.yml` does not exist. This makes sense because we haven’t created it yet!
+
+#### Subsequent deployments
+
+Once all the services have been successfully deployed, you can deploy them all concurrently. This is because the referenced ARN has already been created.
+
+#### Adding new dependencies
+
+Say you add a new SNS topic in `billing-api` service and you want the `notify-job` service to subscribe to that topic. The first deployment after the change, will again fail if all the services are deployed concurrently. You need to deploy the `billing-api` service first, and then deploy the `notify-job` service.
+
+We are almost ready to deploy our extended notes app. But before we can do that, let's configure our environments.
+
+### Deploying through a CI
+
+If you are using a CI, you'll need to deploy the above in phases. With [Seed](https://seed.run), we handle this using a concept of [Deploy Phases](https://seed.run/docs/configuring-deploy-phases).
+
+### Managing deployment in phases for api
+
+For our api repo, the dependencies look like:
+
+```txt
+notes-api > billing-api > notify-job
+```
+
+To break it down in detail:
+
+- The `billing-api` service relies on the `notes-api` service for the API Gateway export.
+- The `notify-job` service relies on the `billing-api` service for the SNS Topic export.
+
+That covers our section on organizing your serverless app. Next, let's configure our environments.
diff --git a/_archives/deploy-the-api-services-repo.md b/_archives/deploy-the-api-services-repo.md
new file mode 100644
index 0000000000..b3bfd8b24f
--- /dev/null
+++ b/_archives/deploy-the-api-services-repo.md
@@ -0,0 +1,64 @@
+---
+layout: post
+title: Deploy the API Services Repo
+description: In this chapter we'll deploy our demo API services GitHub repo of our serverless app to multiple AWS environments. We'll be using Seed to manage our deployments and environments.
+date: 2019-10-08 00:00:00
+comments_id: deploy-the-api-services-repo/1319
+redirect_from: /chapters/deploy-the-api-services-repo.html
+---
+
+Just as the previous chapter we'll add the API repo on Seed and deploy it to our environments.
+
+Click **Add an App** again, and select your Git provider. This time, select the API repo.
+
+
+
+Select the **notes-api** service from the list of services.
+
+
+
+The environments for our API repo are identical to our resources repo. So instead of manually configuring them, we'll copy the settings.
+
+Select **Copy Settings** tab, and select the resources app. Then hit **Add a New App**.
+
+
+
+The API app has been created. Now, let's add the other services. Head over to the **Pipeline** tab.
+
+
+
+Click **Add a service** to add the **billing-api** service at the `services/billing-api` path. And then repeat the step to add the **notify-job** service at the `services/notify-job` path.
+
+
+
+Next, click on **Manage Deploy Phases**.
+
+
+
+Again you'll notice that by default all the services are deployed concurrently.
+
+
+
+Since the **billing-api** service depends on the **notes-api** service, and in turn the **notify-job** service depends on the **billing-api** service, we are going too add 2 phases. And move the **billing-api** service to **Phase 2**, and the **notify-job** service to **Phase 3**. Finally, click **Update Phases**.
+
+
+
+Now let's make our first deployment.
+
+
+
+You can see the deployments were carried out according to the specified deploy phases.
+
+Just as before, promote **dev** to **prod**.
+
+
+
+Hit **Promote to Production**.
+
+
+
+Now we have the API deployed to both **dev** and **prod**.
+
+
+
+Now that our entire app has been deployed, let's look at how we are sharing environment specific configs across our services.
diff --git a/_chapters/deploy-the-apis.md b/_archives/deploy-the-apis.md
similarity index 50%
rename from _chapters/deploy-the-apis.md
rename to _archives/deploy-the-apis.md
index 62c1c09630..3dd8aba0a4 100644
--- a/_chapters/deploy-the-apis.md
+++ b/_archives/deploy-the-apis.md
@@ -1,17 +1,17 @@
---
layout: post
title: Deploy the APIs
-date: 2017-01-04 00:00:00
+date: 2020-10-20 00:00:00
lang: en
ref: deploy-the-apis
description: Use the “serverless deploy” command to deploy to AWS Lambda and API Gateway using the Serverless Framework. Running this command will display the list of deployed API endpoints and the AWS region it was deployed to. And we can run the "serverless deploy function" command when we want to update an individual Lambda function.
-context: true
comments_id: deploy-the-apis/121
+redirect_from: /chapters/deploy-the-apis.html
---
-Now that our APIs are complete, let's deploy them.
+So far we've been working on our Lambda functions locally. In this chapter we are going to deploy them.
-Run the following in your working directory.
+{%change%} Run the following in your working directory.
``` bash
$ serverless deploy
@@ -23,34 +23,38 @@ If you have multiple profiles for your AWS SDK credentials, you will need to exp
$ 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 multiple AWS profiles]({% link _chapters/configure-multiple-aws-profiles.md %}) chapter.
+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 multiple AWS profiles]({% link _archives/configure-multiple-aws-profiles.md %}) chapter.
Near the bottom of the output for this command, you will find the **Service Information**.
``` bash
Service Information
-service: notes-app-api
+service: notes-api
stage: prod
region: us-east-1
+stack: notes-api-prod
+resources: 32
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}
+ POST - https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/notes
+ GET - https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/notes/{id}
+ GET - https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/notes
+ PUT - https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/notes/{id}
+ DELETE - https://0f7jby961h.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
+ create: notes-api-prod-create
+ get: notes-api-prod-get
+ list: notes-api-prod-list
+ update: notes-api-prod-update
+ delete: notes-api-prod-delete
+layers:
+ None
```
-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 creating 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.
+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 creating 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 `0f7jby961h` is our API Gateway ID.
-If you are running into some issues while deploying your app, we have [a compilation of some of the most common Serverless errors](https://seed.run/docs/serverless-errors/) over on [Seed](https://seed.run).
+If you are running into some issues while deploying your app, we have [a compilation of some of the most common serverless errors](https://seed.run/docs/serverless-errors/) over on [Seed](https://seed.run).
### Deploy a Single Function
@@ -62,4 +66,22 @@ For example, to deploy the list function 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 setting up a Cognito Identity Pool.
+### Test the APIs
+
+So far we've been testing our Lambda functions locally using the `serverless invoke local` command. Now that we've deployed our APIs, we can test it through their endpoints.
+
+So if you head over to our list notes API endpoint. In our case it is:
+
+``` text
+https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/notes
+```
+
+You should see something like this.
+
+``` json
+[{"attachment":"hello.jpg","content":"hello world","createdAt":1487800950620,"noteId":"578eb840-f70f-11e6-9d1a-1359b3b22944","userId":"123"}]
+```
+
+This is a JSON encoded array of notes objects that we stored in DynamoDB.
+
+So our API is publicly available, this means that anybody can access it and create notes. And it's always connecting to the `123` user id. Let's fix these next by handling users and authentication.
diff --git a/_archives/deploy-the-resources-repo.md b/_archives/deploy-the-resources-repo.md
new file mode 100644
index 0000000000..1b62ac92dc
--- /dev/null
+++ b/_archives/deploy-the-resources-repo.md
@@ -0,0 +1,62 @@
+---
+layout: post
+title: Deploy the Resources Repo
+description: In this chapter we'll deploy our demo resources GitHub repo of our serverless app to multiple AWS environments. We'll be using Seed to manage our deployments and environments.
+date: 2019-10-08 00:00:00
+comments_id: deploy-the-resources-repo/1320
+redirect_from: /chapters/deploy-the-resources-repo.html
+---
+
+First, add the resources repo on Seed. If you haven't yet, you can create a free account [here](https://console.seed.run/signup).
+
+Go in to your [Seed account](https://console.seed.run), add a new app, authenticate with GitHub, search for the resources repo, and select it.
+
+
+
+Seed will now automatically detect the SST service in the repo. After detection, select **Add Service**.
+
+
+
+By default, Seed lets you configure two stages out of the box, a **Development** and a **Production** stage. Serverless Framework has a concept of stages. They are synonymous with environments. Recall that in the previous chapter we used this stage name to parameterize our resource names.
+
+Let's first configure the **Development** stage. Enter:
+- **Stage Name**: dev
+- **AWS IAM Access Key** and **AWS IAM Secret Key**: the IAM credentials of the IAM user you created in your **Development** AWS account above.
+
+
+
+Next, let's configure the **Production** stage. Uncheck **Use the same IAM credentials as the dev stage** checkbox since we want to use a different AWS account for **Production**. Then enter:
+- **Stage Name**: prod
+- **AWS IAM Access Key** and **AWS IAM Secret Key**: the IAM credentials of the IAM user you created in your **Production** AWS account above.
+
+Finally hit **Add a New App**.
+
+
+
+Now let's make our first deployment. Click **Trigger Deployment** under the **dev** stage.
+
+
+
+We are deploying the `master` branch here. Confirm this by clicking **Deploy**.
+
+
+
+You'll notice that the service is being deployed.
+
+
+
+After the service is successfully deployed. Click **Promote** to deploy this to the **prod** stage.
+
+
+
+You will see a list of changes in resources. Since this is the first time we are deploying to the `prod` stage, the change list shows all the resources that will be created. We'll take a look at this in detail later in the [Promoting to production]({% link _archives/promoting-to-production.md %}) chapter.
+
+Click **Promote to Production**.
+
+
+
+Now our resources have been deployed to both **dev** and **prod**.
+
+
+
+Next, let's deploy our API services repo.
diff --git a/_chapters/deploy-to-s3.md b/_archives/deploy-to-s3.md
similarity index 93%
rename from _chapters/deploy-to-s3.md
rename to _archives/deploy-to-s3.md
index a45183c21f..c350dc417d 100644
--- a/_chapters/deploy-to-s3.md
+++ b/_archives/deploy-to-s3.md
@@ -4,9 +4,9 @@ title: Deploy to S3
date: 2017-02-07 00:00:00
lang: en
description: To use our React.js app in production we are going to use Create React App’s build command to create a production build of our app. And to upload our React.js app to an S3 Bucket on AWS, we are going to use the AWS CLI s3 sync command.
-context: true
comments_id: deploy-to-s3/134
ref: deploy-to-s3
+redirect_from: /chapters/deploy-to-s3.html
---
Now that our S3 Bucket is created we are ready to upload the assets of our app.
@@ -23,7 +23,7 @@ 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]({% link _chapters/create-an-s3-bucket.md %}) chapter.
+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]({% link _archives/create-an-s3-bucket.md %}) chapter.
``` bash
$ aws s3 sync build/ s3://YOUR_S3_DEPLOY_BUCKET_NAME
diff --git a/_chapters/deploy-again.md b/_archives/deploy-updates.md
similarity index 75%
rename from _chapters/deploy-again.md
rename to _archives/deploy-updates.md
index 15f298eb6a..80132e31e4 100644
--- a/_chapters/deploy-again.md
+++ b/_archives/deploy-updates.md
@@ -1,22 +1,31 @@
---
layout: post
-title: Deploy Again
-date: 2017-02-14 00:00:00
+title: Deploy Updates
+date: 2017-02-12 00:00:00
lang: en
-description: To be able to deploy updates to our React.js app hosted on S3 and CloudFront, we need to uploads our app to S3 and invalidate the CloudFront cache. We can do this using the “aws cloudfront create-invalidation” command in our AWS CLI. To automate these steps by running “npm run deploy”, we will add these commands to predeploy, deploy, and postdeploy scripts in our package.json.
-context: true
-code: frontend_part1
-comments_id: deploy-again/138
-ref: deploy-again
+description: To be able to deploy updates to our React.js app hosted on S3 and CloudFront, we need to uploads our app to S3 and invalidate the CloudFront cache. We can do this using the “aws cloudfront create-invalidation” command in our AWS CLI. To automate these steps by running “npx sst deploy”, we will add these commands to predeploy, deploy, and postdeploy scripts in our package.json.
+comments_id: deploy-updates/16
+ref: deploy-updates
+redirect_from:
+ - /chapters/deploy-updates.html
+ - /chapters/deploy-again.html
---
-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 time we need to deploy any 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 Distributions
+
+We need to do the last step since CloudFront caches our objects in its edge locations. So to make sure that our users see the latest version, we need to tell CloudFront to invalidate it's cache in the edge locations.
+
+Let's assume you've made some changes to your app; you'll need to build these changes first.
### Build Our App
First let's prepare our app for production by building it. Run the following in your working directory.
-``` bash
+```bash
$ npm run build
```
@@ -24,17 +33,13 @@ Now that our app is built and ready in the `build/` directory, let's deploy to S
### 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]({% link _chapters/create-an-s3-bucket.md %}) chapter.
+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]({% link _archives/create-an-s3-bucket.md %}) chapter.
-``` bash
+```bash
$ 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 time 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.
-
-
+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 time 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.
@@ -48,7 +53,7 @@ To do this we'll need the **Distribution ID** of **both** of our CloudFront Dist
Now we can use the AWS CLI to invalidate the cache of the two distributions. Make sure to replace `YOUR_CF_DISTRIBUTION_ID` and `YOUR_WWW_CF_DISTRIBUTION_ID` with the ones from above.
-``` bash
+```bash
$ aws cloudfront create-invalidation --distribution-id YOUR_CF_DISTRIBUTION_ID --paths "/*"
$ aws cloudfront create-invalidation --distribution-id YOUR_WWW_CF_DISTRIBUTION_ID --paths "/*"
```
@@ -59,17 +64,15 @@ This invalidates our distribution for both the www and non-www versions of our d
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`.
+{%change%} Add the following in the `scripts` block above `eject` in the `package.json`.
-``` coffee
+```coffee
"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 '/*'",
@@ -79,20 +82,20 @@ Make sure to replace `YOUR_S3_DEPLOY_BUCKET_NAME`, `YOUR_CF_DISTRIBUTION_ID`, an
For Windows users, if `postdeploy` returns an error like.
-```
+```txt
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 `/*`.
-``` coffee
+```coffee
"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.
-``` bash
-$ npm run deploy
+```bash
+$ npx sst 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 option in the [Facebook Login with Cognito using AWS Amplify]({% link _chapters/facebook-login-with-cognito-using-aws-amplify.md %}) chapter. It builds on what we have covered in Part I so far.
+And that's it! Now you have a workflow for deploying and updating your React app on AWS using S3 and CloudFront.
diff --git a/_archives/deploy-your-hello-world-api.md b/_archives/deploy-your-hello-world-api.md
new file mode 100644
index 0000000000..b842987e05
--- /dev/null
+++ b/_archives/deploy-your-hello-world-api.md
@@ -0,0 +1,57 @@
+---
+layout: post
+title: Deploy your Hello World API
+date: 2020-10-16 00:00:00
+lang: en
+ref: deploy-your-first-serverless-api
+description: In this chapter we'll be deploying our first Hello World serverless API. We'll be using the `serverless deploy` command to deploy it to AWS.
+comments_id: deploy-your-hello-world-api/2173
+redirect_from: /chapters/deploy-your-hello-world-api.html
+---
+
+So far we've [created our Serverless Framework]({% link _archives/setup-the-serverless-framework.md %}) app. Now we are going to deploy it to AWS. Make sure you've [configured your AWS account]({% link _chapters/create-an-aws-account.md %}) and [AWS CLI]({% link _chapters/configure-the-aws-cli.md %}).
+
+A big advantage with serverless is that there isn't any infrastructure or servers to provision. You can simply deploy your app directly and it's ready to serve (millions of) users right away.
+
+Let's do a quick deploy to see how this works.
+
+{%change%} In your project root, run the following.
+
+``` bash
+$ serverless deploy
+```
+
+The first time your serverless app is deployed, it creates a S3 bucket (to store your Lambda function code), Lambda, API Gateway, and a few other resources. This can take a minute or two.
+
+Once complete, you should see something like this:
+
+``` bash
+Service Information
+service: notes-api
+stage: prod
+region: us-east-1
+stack: notes-api-prod
+resources: 11
+api keys:
+ None
+endpoints:
+ GET - https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/hello
+functions:
+ hello: notes-api-prod-hello
+layers:
+ None
+```
+
+Notice that we have a new GET endpoint created. In our case it points to — [https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/hello](https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/hello)
+
+If you head over to that URL, you should see something like this:
+
+``` bash
+{"message":"Go Serverless v2.0! Your function executed successfully! (with a delay)"}
+```
+
+You'll recall that this is the same output that we received when we invoked our Lambda function locally in the last chapter. In this case we are invoking that function through the `/hello` API endpoint.
+
+We now have a serverless API endpoint. You only pay per request to this endpoint and it scales automatically. That's a great first step!
+
+Now we are ready to create our infrastructure. Starting with our database.
diff --git a/_archives/deploy-your-serverless-infrastructure.md b/_archives/deploy-your-serverless-infrastructure.md
new file mode 100644
index 0000000000..026d81f685
--- /dev/null
+++ b/_archives/deploy-your-serverless-infrastructure.md
@@ -0,0 +1,78 @@
+---
+layout: post
+title: Deploy Your Serverless Infrastructure
+date: 2018-03-04 00:00:00
+lang: en
+description: In this chapter we'll be deploying our entire serverless infrastructure. We are using CDK to define our resources and we are deploying it using SST. Our API on the other hand is deployed using Serverless Framework.
+code: backend_full
+ref: deploy-your-serverless-infrastructure
+comments_id: deploy-your-serverless-infrastructure/167
+redirect_from: /chapters/deploy-your-serverless-infrastructure.html
+---
+
+Now that we have all our resources configured, let's go ahead and deploy our entire infrastructure.
+
+Note that, this deployment will create a new set of resources (DynamoDB table, S3 bucket, etc.). You can remove the ones that we had previously created. We're leaving this as an exercise for you.
+
+### Deploy Your Serverless App
+
+Let's deploy our Serverless Framework app.
+
+{%change%} From your project root, run the following.
+
+``` bash
+$ serverless deploy -v
+```
+
+Your output should look something like this:
+
+``` bash
+Service Information
+service: notes-api
+stage: dev
+region: us-east-1
+stack: notes-api-dev
+resources: 44
+api keys:
+ None
+endpoints:
+ POST - https://5opmr1alga.execute-api.us-east-1.amazonaws.com/dev/notes
+ GET - https://5opmr1alga.execute-api.us-east-1.amazonaws.com/dev/notes/{id}
+ GET - https://5opmr1alga.execute-api.us-east-1.amazonaws.com/dev/notes
+ PUT - https://5opmr1alga.execute-api.us-east-1.amazonaws.com/dev/notes/{id}
+ DELETE - https://5opmr1alga.execute-api.us-east-1.amazonaws.com/dev/notes/{id}
+ POST - https://5opmr1alga.execute-api.us-east-1.amazonaws.com/dev/billing
+functions:
+ create: notes-api-dev-create
+ get: notes-api-dev-get
+ list: notes-api-dev-list
+ update: notes-api-dev-update
+ delete: notes-api-dev-delete
+ billing: notes-api-dev-billing
+layers:
+ None
+
+Stack Outputs
+DeleteLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:087220554750:function:notes-api-dev-delete:3
+CreateLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:087220554750:function:notes-api-dev-create:3
+GetLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:087220554750:function:notes-api-dev-get:3
+UpdateLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:087220554750:function:notes-api-dev-update:3
+BillingLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:087220554750:function:notes-api-dev-billing:1
+ListLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:087220554750:function:notes-api-dev-list:3
+ServiceEndpoint: https://5opmr1alga.execute-api.us-east-1.amazonaws.com/dev
+ServerlessDeploymentBucketName: notes-api-dev-serverlessdeploymentbucket-1323e6pius3a
+```
+
+And there you have it! Your entire serverless app has been created completely programmatically.
+
+### Next Steps
+
+You can also deploy your app to production by running.
+
+``` bash
+$ serverless deploy --stage prod
+```
+
+Note that, production in this case is just an environment with a stage called `prod`. You can call it anything you like. Serverless Framework will simply create another version of your app with a completely new set of resources. You can learn more about this in our chapter on [Stages in Serverless Framework]({% link _archives/stages-in-serverless-framework.md %}).
+
+Next, you can go to our main guide and [follow the frontend section]({% link _chapters/create-a-new-reactjs-app.md %}). Just remember to use the resources that were created here in your React.js app config!
diff --git a/_chapters/deploy-the-frontend.md b/_archives/deploying-a-react-app-to-aws.md
similarity index 79%
rename from _chapters/deploy-the-frontend.md
rename to _archives/deploying-a-react-app-to-aws.md
index d024c07a7e..0830005e0f 100644
--- a/_chapters/deploy-the-frontend.md
+++ b/_archives/deploying-a-react-app-to-aws.md
@@ -1,14 +1,17 @@
---
layout: post
-title: Deploy the Frontend
+title: Deploy a React App to AWS
date: 2017-02-05 00:00:00
lang: en
description: Tutorial on how to host a React.js single page application on AWS S3 and CloudFront.
comments_id: deploy-the-frontend/39
ref: deploy-the-frontend
+redirect_from:
+ - /chapters/deploying-a-react-app-to-aws.html
+ - /chapters/deploy-the-frontend.html
---
-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 application.
+In this section we'll be looking at how to deploy your React app as a static website on AWS.
The basic setup we are going to be using will look something like this:
diff --git a/_archives/deploying-a-react-app-to-netlify.md b/_archives/deploying-a-react-app-to-netlify.md
new file mode 100644
index 0000000000..c35d9ed2c6
--- /dev/null
+++ b/_archives/deploying-a-react-app-to-netlify.md
@@ -0,0 +1,26 @@
+---
+layout: post
+title: Deploying a React App to Netlify
+date: 2020-11-03 00:00:00
+lang: en
+description: In this chapter we'll be looking at hosting your React app on Netlify. Our React app is a static site and it's pretty simple to host them.
+ref: deploying-a-react-app-to-netlify
+comments_id: hosting-your-react-app/2177
+redirect_from:
+ - /chapters/deploying-a-react-app-to-netlify.html
+ - /chapters/hosting-your-react-app.html
+---
+
+In this section we'll be looking at how to deploy your React.js app as a static website to [Netlify](https://www.netlify.com). You'll recall that in the [main part of the guide]({% link _chapters/create-a-new-reactjs-app.md %}) we used the SST [`ReactStaticSite`]({{ site.v2_url }}/constructs/ReactStaticSite) construct to deploy our React app to AWS.
+
+Netlify allows you to [host your React app for free](https://www.netlify.com/pricing/) and it allows your to `git push` to deploy your apps.
+
+The basic setup we are going to be using will look something like this:
+
+1. Setup our project on Netlify
+2. Configure custom domains
+3. Create a CI/CD pipeline for our app
+
+We also have another alternative version of this where we deploy our React app to S3 and we use CloudFront as a CDN in front of it. We use Route 53 to configure our custom domain. We also need to configure the www version of our domain and this needs another S3 and CloudFront distribution. The entire process can be a bit cumbersome. But if you are looking for a way to deploy and host the React app in your AWS account, we have an Extra Credit chapter on this — [Deploying a React app on AWS]({% link _archives/deploying-a-react-app-to-aws.md %}).
+
+Let's get started by creating our project on Netlify.
diff --git a/_archives/deploying-only-updated-services.md b/_archives/deploying-only-updated-services.md
new file mode 100644
index 0000000000..87d4f1b1a7
--- /dev/null
+++ b/_archives/deploying-only-updated-services.md
@@ -0,0 +1,56 @@
+---
+layout: post
+title: Deploying Only Updated Services
+description: In this chapter we look at how to speed up deployments to our monorepo serverless app by only redeploying the services that have been updated. We can do this by relying on the check Serverless Framework does. Or by looking at the Git log for the directories that have been updated.
+date: 2019-10-02 00:00:00
+comments_id: deploying-only-updated-services/1321
+redirect_from: /chapters/deploying-only-updated-services.html
+---
+
+Once you are repeatedly deploying your serverless application, you might notice that the Serverless deployments are not very fast. This is especially true if your app has a ton of service. There are a couple of things you can do here to speed up your builds. One of them is to only deploy the services that've been updated.
+
+In this chapter we'll look at how to do that.
+
+Note that, we are doing this by default in Seed. Recall that when we merged the **like** branch to the **master** branch, only the `like-api` service and the `notes-api` showed a solid check. The other two services showed a greyed out check mark. This means that there were no changes to be deployed for this service.
+
+
+
+In a serverless app with a single service, the deployment strategy in your CI/CD pipeline is straight forward: deploy my app on every git push.
+
+However in a monorepo setup, an app is made up of many [Serverless Framework](https://serverless.com/) services. It’s not uncommon for teams to have apps with over 40 services in a single repo on [Seed](https://seed.run/). For these setups, it does not make sense to deploy all the services when all you are trying to do is fix a typo! You only want to deploy the services that have been updated because deploying all your services on every commit is:
+
+1. Slow: deploying all services can take very long, especially when you are not deploying them concurrently.
+2. Expensive: traditional CI services charge extra for concurrency. As of writing this chapter, it costs $50 for each added level of concurrency on [CircleCI](https://circleci.com/), hence it can be very costly to deploy all your services concurrently.
+
+There are a couple of ways to only deploy the services that have been updated in your serverless CI/CD pipeline.
+
+### Strategy 1: Skip deployments in Serverless Framework
+
+The `serverless deploy` command has built-in support to skip a deployment if the deployment package has not changed.
+
+A bit of a background, when you run `serverless deploy`, two things are done behind the scenes. It first does a `serverless package` to generate a deployment package. This includes the CloudFormation template and the zipped Lambda code. Next, it does a `serverless deploy -p path/to/package` to deploy the package that was created. Before Serverless deploys the package in the second step, it first computes the hash of the package and compares it with that of the previous deployment. If the hash is the same, the deployment is skipped. We are simplifying the process here but that’s the basic idea.
+
+However, there are two downsides to this.
+
+1. Serverless Framework still has to generate the deployment package first. For a Node.js application, this could mean installing the dependencies, linting, running Webpack, and finally packaging the code. Meaning that the entire process can still be pretty slow even if you skip the deployment.
+2. If your previous deployment had failed due to an external cause, after you fix the issue and re-run `serverless deploy`, the deployment will be skipped. For example, you tried to create an S3 bucket in your `serverless.yml`, but you hit the 100 S3 buckets per account limit. You talked to AWS support and had the limit lifted. Now you re-run `serverless deploy` , but since neither the CloudFormation template or the Lambda code changed, the deployment will be skipped. To fix this, you need to use the `--force` flag to skip the check and force a deployment.
+
+### Strategy 2: Check the Git log for changes
+
+A better approach here is to check if there are any commits in a service directory before deploying that service.
+
+When some code is pushed, you can run the following command to get a list of updated files:
+
+``` bash
+$ git diff --name-only ${prevCommitSHA} ${currentCommitSHA}
+```
+
+This will give you a list of files that have changed between the two commits. With the list of changed files, there are three scenarios from the perspective of a given service. We are going to use `notes-api` as an example:
+
+1. A file was changed in my service directory (ie. `services/notes-api`) ⇒ we deploy the `notes-api` service
+2. A file was changed in another service's directory (ie. `services/like-api`) ⇒ we do not deploy the `notes-api` service
+3. Or, a file was changed in `libs/` ⇒ we deploy the `notes-api` service
+
+Your repo setup can look different, but the general concept still holds true. You have to figure out which file change affects an individual service, and which affects all the services. The advantage of this strategy is that you know upfront which services can be skipped, allowing you to skip a portion of the entire build process!
+
+And this concludes our section on the development workflow! Next, we'll look at how to trace our serverless applications using AWS X-Ray.
diff --git a/_archives/deploying-to-multiple-aws-accounts.md b/_archives/deploying-to-multiple-aws-accounts.md
new file mode 100644
index 0000000000..d93cb0caab
--- /dev/null
+++ b/_archives/deploying-to-multiple-aws-accounts.md
@@ -0,0 +1,54 @@
+---
+layout: post
+title: Deploying to Multiple AWS Accounts
+description: Once you've configured the environments in your serverless app across multiple AWS accounts, you'll want to deploy them. In this chapter, we look at how to create the AWS credentials and manage the environments using Seed.
+date: 2019-09-30 00:00:00
+comments_id: deploying-to-multiple-aws-accounts/1322
+redirect_from: /chapters/deploying-to-multiple-aws-accounts.html
+---
+
+Now that you have a couple of AWS accounts created and your resources have been parameterized, let's look at how to deploy them. In this chapter, we'll deploy the following:
+
+1. The [resources repo]({{ site.backend_ext_resources_github_repo }}) will be deployed in phases to the `dev` and `prod` stage. These two stages are configured in our `Development` and `Production` AWS accounts respectively.
+
+2. Then we'll do the same with the [APIs repo]({{ site.backend_ext_api_github_repo }}).
+
+### Configure AWS Profiles
+
+Follow the [setup up IAM users]({% link _chapters/create-an-iam-user.md %}) chapter to create an IAM user in your `Development` AWS account. And take a note of the **Access key ID** and **Secret access key** for the user.
+
+Next, set these credentials in your local machine using the AWS CLI:
+
+``` bash
+$ aws configure --profile default
+```
+
+This sets the default IAM credentials to those of the Development account. Meaning when you run `serverless deploy`, a service will get deployed into the Development account.
+
+Repeat the step to create an IAM user in your `Production` account. And make a note of the credentials. We will not add the IAM credentials for the Production account on our local machine. This is because we do not want to be able to deploy code to the Production environment EVER from our local machine.
+
+Production deployments should always go through our CI/CD pipeline.
+
+Next we are going to deploy our two repos to our environments. We want you to follow along so you can get a really good sense of what the workflow is like.
+
+So let's start by using the demo repo templates from GitHub.
+
+### Create demo repos
+
+Let's first create [the resources repo]({{ site.backend_ext_resources_github_repo }}). Click **Use this template**.
+
+
+
+Enter Repository name **serverless-stack-demo-ext-resources** and click **Create repository from template**.
+
+
+
+And do the same for [the API services repo]({{ site.backend_ext_api_github_repo }}).
+
+
+
+Enter Repository name **serverless-stack-demo-ext-api** and click **Create repository from template**.
+
+
+
+Now that we've forked these repos, let's deploy them to our environments. We are going to use [Seed](https://seed.run) to do this but you can set this up later with your favorite CI/CD tool.
diff --git a/_archives/dynamically-generate-social-share-images-with-serverless.md b/_archives/dynamically-generate-social-share-images-with-serverless.md
new file mode 100644
index 0000000000..80da3e4936
--- /dev/null
+++ b/_archives/dynamically-generate-social-share-images-with-serverless.md
@@ -0,0 +1,781 @@
+---
+layout: post
+title: Dynamically generate social share images with serverless
+date: 2021-06-25 00:00:00
+lang: en
+description: In this chapter we'll look at how to create a serverless service that dynamically generates social cards for our website or blog. It uses SST and Puppeteer to generate these images.
+repo: https://github.com/sst/social-cards
+ref: dynamically-generate-social-share-images-with-serverless
+comments_id: dynamically-generate-social-share-images-with-serverless/2419
+redirect_from: /chapters/dynamically-generate-social-share-images-with-serverless.html
+---
+
+In this chapter we'll look at how to dynamically generate social share images or open graph (OG) images with serverless.
+
+Social cards or social share images or open graph images are preview images that are displayed in social media sites like Facebook, Twitter, etc. when a link is posted. These images give users better context on what the link is about. They also look nicer than a logo or favicon.
+
+However, creating a unique image for each blog post or page of your website can be time consuming and impractical. So we ideally want to be able to generate these images dynamically based on the title of the blog post or page and some other accompanying information.
+
+We wanted to do something like this for [SST](/). And this was a perfect use case for [serverless]({% link _chapters/what-is-serverless.md %}). These images will be generated when your website is shared and it doesn't make sense to run a server to serve these out. So we built our own social cards service with [SST](/)! It's also deployed and managed with [Seed](https://seed.run).
+
+For instance, here is what the social card for one of our chapters looks like.
+
+
+
+We also have multiple templates to generate these social cards. Here's one for our blog.
+
+
+
+These images are served out of our social cards service. It's built using [SST](/) and is hosted on AWS:
+
+```bash
+https://social-cards.sst.dev
+```
+
+In this chapter we'll look at how we created this service and how you can do the same!
+
+The entire social cards service is open source and available on GitHub. So you can fork it and play around with everything that will be talked about in this chapter.
+
+ [SST Social Cards Service on GitHub]({{ page.repo }})
+
+### Table of Contents
+
+In this chapter we'll be looking at:
+
+1. [The architecture of our social cards service](#the-architecture)
+1. [Create a serverless app with SST](#create-an-sst-app)
+1. [Design templates for our social cards in the browser](#design-social-card-templates)
+1. [Use Puppeteer to take screenshots of the templates](#take-screenshots-with-puppeteer)
+1. [Support non-Latin fonts in Lambda](#support-non-latin-fonts-in-lambda)
+1. [Cache the images in an S3 bucket](#cache-images-in-s3)
+1. [Use CloudFront as a CDN to serve the images](#use-cloudfront-as-a-cdn)
+1. [Add a custom domain for our social cards service](#adding-a-custom-domain)
+1. [Integrate with static site generators](#integrate-with-static-site-generators)
+
+Let's start by taking a step back and getting a sense of the architecture of our social card service.
+
+### The Architecture
+
+Our social cards service is a serverless app deployed to AWS.
+
+
+
+There are a couple of key parts to this. So let's look at it in detail.
+
+1. We have [CloudFront](https://aws.amazon.com/cloudfront/) as our CDN to serve out images.
+2. CloudFront connects to our serverless API.
+3. The API is powered by a Lambda function.
+4. The Lambda function generating these images will:
+ - Include the templates that we'll be using. These are HTML files that are included in our Lambda function.
+ - Run a headless Chrome instance with [Puppeteer](https://developers.google.com/web/tools/puppeteer) and pass in the parameters for the templates.
+ - Load these templates and take a screenshot.
+ - Store these images in an S3 bucket.
+ - Check the S3 bucket first to see if we've previously generated these images.
+
+### Create an SST App
+
+We'll start by creating a new SST app.
+
+```bash
+$ npx create-sst@one --template=minimal/javascript-starter social-cards
+$ cd social-cards
+```
+
+The infrastructure in our app is defined using [CDK]({% link _archives/what-is-aws-cdk.md %}). Currently we just have a simple API that invokes a Lambda function.
+
+You can see this in `stacks/MyStack.js`.
+
+```js
+// Create a HTTP API
+const api = new Api(stack, "Api", {
+ routes: {
+ "GET /": "functions/lambda.handler",
+ },
+});
+```
+
+For now our Lambda function in `functions/lambda.js` just prints out _"Hello World"_.
+
+### Design Social Card Templates
+
+The first step is to create a template for our social share images. These HTML files will be loaded locally and we'll pass in the parameters for our template via the query string.
+
+Let's look at the blog template that we use in SST as an example.
+
+
+
+The HTML that generates this page looks like:
+
+```html
+
+
+
+
+
+
+
+
+
+ Blog
+
+
+
+
+
+
+
+ Read Post
+
+
+
+```
+
+Note the `
+```
+
A couple of notes here.
- Amplify refers to Cognito as `Auth`, S3 as `Storage`, and API Gateway as `API`.
@@ -112,6 +115,16 @@ A couple of notes here.
- The `name: "notes"` is basically telling Amplify that we want to name our API. Amplify allows you to add multiple APIs that your app is going to work with. In our case our entire backend is just one single API.
-- The `Amplify.configure()` is just setting the various AWS resources that we want to interact with. It isn't doing anything else special here beside configuration. So while this might look intimidating, just remember this is only setting things up.
+- The `Amplify.configure()` is just setting the various AWS resources that we want to interact with. It isn't doing anything else special here beside configuration. So while this might look intimidating, just remember this is only setting things up.
+
+### Commit the Changes
+
+{%change%} Let's commit our code so far and push it to GitHub.
+
+```bash
+$ git add .
+$ git commit -m "Setting up our React app"
+$ git push
+```
Next up, we are going to work on creating our login and sign up forms.
diff --git a/_chapters/configure-secrets-in-seed.md b/_chapters/configure-secrets-in-seed.md
index 8b24134aa0..e94c35f1a7 100644
--- a/_chapters/configure-secrets-in-seed.md
+++ b/_chapters/configure-secrets-in-seed.md
@@ -3,36 +3,49 @@ layout: post
title: Configure Secrets in Seed
lang: en
date: 2018-03-13 00:00:00
-description: To automate our Serverless deployments with Seed (https://seed.run), we will need to set our secrets in the Seed console. Move the environment variables from your env.yml to the stage we are deploying to.
-context: true
+description: To automate our serverless deployments with Seed, we will need to set our secrets in the Seed console. Move the environment variables from `sst secrets` to the stage we are deploying to.
ref: configure-secrets-in-seed
comments_id: configure-secrets-in-seed/176
---
-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](https://seed.run) to deploy with our secrets for us.
+Before we can make 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)]({% link _chapters/handling-secrets-in-sst.md %}). This means that if somebody else on our team needs to deploy, we'll need to pass the information around. Instead we'll configure [Seed](https://seed.run) to deploy with our secrets for us.
-To do that, hit the **Settings** button in our **dev** stage.
+We are also going to configure Seed to deploy our app to production when we push any changes to the `main` branch.
-
+By default, Seed sets you up with two environments, `dev` and `prod`. Where pushing to the `main` branch would deploy to `dev`. And you'll need to promote your changes to `prod`. To keep things simple, we are only going to use the `prod` stage here and deploy directly to it.
-Here click **Show Env Variables**.
+To configure the above, click **dev** in your app **Settings**.
-
+
-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]({% link _chapters/load-secrets-from-env-yml.md %}) chapter. Hit **Add** to save your secret key.
+Here **turn off the Auto-deploy** setting.
-
+
-Next we need to configure our secrets for the `prod` stage. Head over there and hit the **Settings** button.
+Then head over to the **prod** stage in your app **Settings**.
-
+
-Click **Show Env Variables**.
+Here **turn on the Auto-deploy** setting.
-
+
-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]({% link _chapters/load-secrets-from-env-yml.md %}) chapter. Hit **Add** to save your secret key.
+You'll be prompted to select a branch. Select **main** and click **Enable**.
-
+
+
+Next, scroll down and click **Show Env Variables**.
+
+
+
+And type in `STRIPE_SECRET_KEY` as the **Key**.
+
+{%note%}
+We saved this in as an sst secret back in the [Handling Secrets in SST]({% link _chapters/handling-secrets-in-sst.md %}) chapter. Remember, you can run `pnpm sst secrets list` to see the secrets for the current stage.
+{%endnote%}
+
+Hit **Add** to save your secret key.
+
+
Next, we'll trigger our first deployment on Seed.
diff --git a/_chapters/configure-the-aws-cli.md b/_chapters/configure-the-aws-cli.md
index 2a65391b6b..c91d3db8a6 100644
--- a/_chapters/configure-the-aws-cli.md
+++ b/_chapters/configure-the-aws-cli.md
@@ -5,7 +5,6 @@ date: 2016-12-26 00:00:00
lang: en
ref: configure-the-aws-cli
description: To interact with AWS using the command line we need to install the AWS command line interface (or AWS CLI). It also needs to be configured with our IAM user Access key ID and Secret Access key from the AWS console.
-context: true
comments_id: configure-the-aws-cli/86
---
@@ -18,7 +17,7 @@ AWS CLI needs Python 2 version 2.6.5+ or Python 3 version 3.3+ and [Pip](https:/
- [Installing Python](https://www.python.org/downloads/)
- [Installing Pip](https://pip.pypa.io/en/stable/installing/)
-Now using Pip you can install the AWS CLI (on Linux, macOS, or Unix) by running:
+{%change%} Now using Pip you can install the AWS CLI (on Linux, macOS, or Unix) by running:
``` bash
$ sudo pip install awscli
@@ -41,7 +40,7 @@ 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.
+{%change%} Simply run the following with your Secret Key ID and your Access Key.
``` bash
$ aws configure
diff --git a/_chapters/connect-the-billing-form.md b/_chapters/connect-the-billing-form.md
index 8f3eb10e96..9e3f52d8b7 100644
--- a/_chapters/connect-the-billing-form.md
+++ b/_chapters/connect-the-billing-form.md
@@ -1,83 +1,83 @@
---
layout: post
title: Connect the Billing Form
-date: 2018-03-24 00:00:00
+date: 2017-01-31 18:00:00
lang: en
-description: To add our Stripe billing form to our React app container we need to wrap it inside a StripeProvider component. We also need to include Stripe.js in our HTML page.
-context: true
+description: To add our Stripe billing form to our React app container we need to wrap it inside a StripeProvider component.
ref: connect-the-billing-form
comments_id: connect-the-billing-form/187
---
Now all we have left to do is to connect our billing form to our billing API.
-Let's start by including Stripe.js in our HTML.
+{%change%} Replace our `return` statement in `src/containers/Settings.tsx` with this.
-Append the following the the `` block in our `public/index.html`.
-
-``` html
-
-```
-
-Replace our `render` method in `src/containers/Settings.js` with this.
-
-``` coffee
-handleFormSubmit = async (storage, { token, error }) => {
- if (error) {
- alert(error);
+{% raw %}
+```tsx
+const handleFormSubmit: BillingFormType["onSubmit"] = async (
+ storage,
+ info
+) => {
+ if (info.error) {
+ onError(info.error);
return;
}
- this.setState({ isLoading: true });
+ setIsLoading(true);
try {
- await this.billUser({
+ await billUser({
storage,
- source: token.id
+ source: info.token?.id,
});
alert("Your card has been charged successfully!");
- this.props.history.push("/");
+ nav("/");
} catch (e) {
- alert(e);
- this.setState({ isLoading: false });
+ onError(e);
+ setIsLoading(false);
}
-}
-
-render() {
- return (
-
-
-
-
-
-
-
- );
-}
+};
+
+return (
+
+
+
+
+
+);
```
+{% endraw %}
-And add the following to the header.
+{%change%} Add the following imports to the header.
-``` js
-import { Elements, StripeProvider } from "react-stripe-elements";
-import BillingForm from "../components/BillingForm";
-import config from "../config";
+```tsx
+import { Elements } from "@stripe/react-stripe-js";
+import { BillingForm, BillingFormType } from "../components/BillingForm";
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 after letting the user know.
+We are adding the `BillingForm` component that we previously created here and passing in the `isLoading` and `onSubmit` prop that we referenced in the previous chapter. In the `handleFormSubmit` method, we are checking if the Stripe method returned an error. And if things looked okay then we call our billing API and redirect to the home page after letting the user know.
+
+To initialize the Stripe Elements we pass in the Stripe.js object that we loaded [a couple of chapters ago]({% link _chapters/add-stripe-keys-to-config.md %}). This Elements component needs to wrap around any Stripe React components.
-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.
+The Stripe elements are loaded inside an [IFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe). So if we are using any custom fonts, we'll need to include them explicitly. As covered in the [Stripe docs](https://stripe.com/docs/js/elements_object){:target="_blank"}.
Finally, let's handle some styles for our settings page as a whole.
-Add the following to `src/containers/Settings.css`.
+{%change%} Create a file named `src/containers/Settings.css` and add the following.
-``` css
+```css
@media all and (min-width: 480px) {
.Settings {
padding: 60px 0;
@@ -106,13 +106,8 @@ If everything is set correctly, you should see the success message and you'll be

-### Commit the Changes
+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.
-Let's quickly commit these to Git.
-
-``` bash
-$ git add .
-$ git commit -m "Connecting the billing form"
-```
+
-Next, we'll set up automatic deployments for our React app using a service called [Netlify](https://www.netlify.com). This will be fairly similar to what we did for our serverless backend API.
+Instead, we would like it to redirect us to the login page and then redirect us back after we login. Let's look at how to do that next.
diff --git a/_chapters/create-a-billing-form.md b/_chapters/create-a-billing-form.md
index 5a6ce4d2e1..01a3970af0 100644
--- a/_chapters/create-a-billing-form.md
+++ b/_chapters/create-a-billing-form.md
@@ -1,175 +1,190 @@
---
layout: post
title: Create a Billing Form
-date: 2018-03-23 00:00:00
+date: 2017-01-31 12:00:00
lang: en
description: We will create a billing form in our React app using the Stripe React SDK. We will use the CardElement to let the user input their credit card details and call the createToken method to generate a token that we can pass to our serverless billing API.
-context: true
ref: create-a-billing-form
comments_id: create-a-billing-form/186
---
Now our settings 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.
+{%change%} Run the following **in the `packages/frontend/` directory**.
-``` bash
-$ npm install --save react-stripe-elements
+```bash
+$ npm install @stripe/react-stripe-js
```
Next let's create our billing form component.
-Add the following to a new file in `src/components/BillingForm.js`.
+{%change%} Add the following to a new file in `src/components/BillingForm.tsx`.
{% raw %}
-``` coffee
-import React, { Component } from "react";
-import { FormGroup, FormControl, ControlLabel } from "react-bootstrap";
-import { CardElement, injectStripe } from "react-stripe-elements";
-import LoaderButton from "./LoaderButton";
+
+```tsx
+import React, { useState } from "react";
+import Form from "react-bootstrap/Form";
+import Stack from "react-bootstrap/Stack";
+import { useFormFields } from "../lib/hooksLib";
+import { Token, StripeError } from "@stripe/stripe-js";
+import LoaderButton from "../components/LoaderButton";
+import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
import "./BillingForm.css";
-class BillingForm extends Component {
- constructor(props) {
- super(props);
+export interface BillingFormType {
+ isLoading: boolean;
+ onSubmit: (
+ storage: string,
+ info: { token?: Token; error?: StripeError }
+ ) => Promise;
+}
- this.state = {
- name: "",
- storage: "",
- isProcessing: false,
- isCardComplete: false
- };
- }
+export function BillingForm({ isLoading, onSubmit }: BillingFormType) {
+ const stripe = useStripe();
+ const elements = useElements();
+ const [fields, handleFieldChange] = useFormFields({
+ name: "",
+ storage: "",
+ });
+ const [isProcessing, setIsProcessing] = useState(false);
+ const [isCardComplete, setIsCardComplete] = useState(false);
- validateForm() {
+ isLoading = isProcessing || isLoading;
+
+ function validateForm() {
return (
- this.state.name !== "" &&
- this.state.storage !== "" &&
- this.state.isCardComplete
+ stripe &&
+ elements &&
+ fields.name !== "" &&
+ fields.storage !== "" &&
+ isCardComplete
);
}
- handleFieldChange = event => {
- this.setState({
- [event.target.id]: event.target.value
- });
- }
+ async function handleSubmitClick(event: React.FormEvent) {
+ event.preventDefault();
- handleCardFieldChange = event => {
- this.setState({
- isCardComplete: event.complete
- });
- }
+ if (!stripe || !elements) {
+ // Stripe.js has not loaded yet. Make sure to disable
+ // form submission until Stripe.js has loaded.
+ return;
+ }
- handleSubmitClick = async event => {
- event.preventDefault();
+ if (!elements.getElement(CardElement)) {
+ return;
+ }
- const { name } = this.state;
+ setIsProcessing(true);
- this.setState({ isProcessing: true });
+ const cardElement = elements.getElement(CardElement);
- const { token, error } = await this.props.stripe.createToken({ name });
+ if (!cardElement) {
+ return;
+ }
- this.setState({ isProcessing: false });
+ const { token, error } = await stripe.createToken(cardElement);
- this.props.onSubmit(this.state.storage, { token, error });
- }
+ setIsProcessing(false);
- render() {
- const loading = this.state.isProcessing || this.props.loading;
+ onSubmit(fields.storage, { token, error });
+ }
- return (
-
- );
- }
+ isLoading={isLoading}
+ disabled={!validateForm()}
+ >
+ Purchase
+
+
+
+ );
}
-
-export default injectStripe(BillingForm);
```
+
{% endraw %}
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.
+- To begin with we are getting a reference to the Stripe object by calling `useStripe`.
-- 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.
+- 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 `handleFieldChange` method that we get from our `useFormFields` custom React Hook.
- The credit card number form is provided by the Stripe React SDK through the `CardElement` component that we import in the header.
-- The submit button 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 Settings container is calling the billing API we use the `this.props.loading` to set the state of the button from the Settings container.
+- The submit button 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 Settings container is calling the billing API we use the `props.isLoading` to set the state of the button from the Settings 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.
+- 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 settings page via the `this.props.onSubmit` method. We will be setting this up shortly.
+- Finally, once the user completes and submits the form we make a call to Stripe by passing in the `CardElement`. It uses this to generate a token for the specific call. We simply pass this and the number of notes to be stored to the settings page via the `onSubmit` method. We will be setting this up shortly.
-You can read more about how to use the [React Stripe Elements here](https://github.com/stripe/react-stripe-elements).
+You can read more about how to use the [React Stripe SDK here](https://github.com/stripe/react-stripe-js).
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`.
+{%change%} Create a file at `src/components/BillingForm.css`.
-``` css
+```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;
+ line-height: 1.5;
+ padding: 0.65rem 0.75rem;
+ background-color: var(--bs-body-bg);
+ border: 1px solid var(--bs-border-color);
+ border-radius: var(--bs-border-radius-lg);
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.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;
+ outline: 0;
+ border-color: #86B7FE;
+ box-shadow: 0 0 0 .25rem rgba(13, 110, 253, 0.25);
}
```
-### Commit the Changes
-
-Let's quickly commit these to Git.
-
-``` bash
-$ git add .
-$ git commit -m "Adding a billing form"
-```
+These styles might look complicated. But we are just copying them from the other form fields on the page to make sure that the card field looks like them.
Next we'll plug our form into the settings page.
diff --git a/_chapters/create-a-build-script.md b/_chapters/create-a-build-script.md
deleted file mode 100644
index e7ddf33dc1..0000000000
--- a/_chapters/create-a-build-script.md
+++ /dev/null
@@ -1,104 +0,0 @@
----
-layout: post
-title: Create a Build Script
-date: 2018-03-26 00:00:00
-lang: en
-code: frontend_full
-description: To configure our Create React App with Netlify, we need to add a build script to our project root. To make sure that we return a HTTP status code of 200 for our React Router routes we will be adding a redirects rule.
-ref: create-a-build-script
-comments_id: create-a-build-script/189
----
-
-Before we can add our project to [Netlify](https://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.
-
-``` toml
-# 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 left empty.
-
-2. The `publish` option 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` option is the build command that Netlify will use. If you recall the [Manage environments in Create React App]({% link _chapters/manage-environments-in-create-react-app.md %}) chapter, this will seem familiar. In the default context the command is `REACT_APP_STAGE=dev npm run build`.
-
-The production 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-production 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 routing 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 bottom of `netlify.toml`:
-
-``` 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.
-
-``` coffee
-"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test --env=jsdom",
- "eject": "react-scripts eject"
-}
-```
-
-You'll notice we are getting 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.
-
-``` bash
-$ git add .
-$ git commit -m "Adding a Netlify build script"
-```
-
-### Push the Changes
-
-We are pretty much done making changes to our project. So let's go ahead and push them to GitHub.
-
-``` bash
-$ git push
-```
-
- Now we are ready to add our project to Netlify.
diff --git a/_chapters/create-a-custom-react-hook-to-handle-form-fields.md b/_chapters/create-a-custom-react-hook-to-handle-form-fields.md
new file mode 100644
index 0000000000..6e13e12303
--- /dev/null
+++ b/_chapters/create-a-custom-react-hook-to-handle-form-fields.md
@@ -0,0 +1,159 @@
+---
+layout: post
+title: Create a Custom React Hook to Handle Form Fields
+date: 2017-01-18 12:00:00
+lang: en
+ref: create-a-custom-react-hook-to-handle-form-fields
+description: In this chapter we are going to create a custom React Hook to make it easier to handle form fields in our React app.
+comments_id: create-a-custom-react-hook-to-handle-form-fields/1316
+---
+
+Now before we move on to creating our sign up page, we are going to take a short detour to simplify how we handle form fields in React. We built a form as a part of our login page and we are going to do the same for our sign up page. You'll recall that in our login component we were creating two state variables to store the username and password.
+
+```typescript
+const [email, setEmail] = useState("");
+const [password, setPassword] = useState("");
+```
+
+And we also use something like this to set the state:
+
+```tsx
+onChange={(e) => setEmail(e.target.value)}
+```
+
+Now we are going to do something similar for our sign up page and it'll have a few more fields than the login page. So it makes sense to simplify this process and have some common logic that all our form related components can share. Plus this is a good way to introduce the biggest benefit of React Hooks — reusing stateful logic between components.
+
+### Creating a Custom React Hook
+
+{%change%} Add the following to `src/lib/hooksLib.ts`.
+
+```typescript
+import { useState, ChangeEvent, ChangeEventHandler } from "react";
+
+interface FieldsType {
+ [key: string | symbol]: string;
+}
+
+export function useFormFields(
+ initialState: FieldsType
+): [FieldsType, ChangeEventHandler] {
+ const [fields, setValues] = useState(initialState);
+
+ return [
+ fields,
+ function (event: ChangeEvent) {
+ setValues({
+ ...fields,
+ [event.target.id]: event.target.value,
+ });
+ return;
+ },
+ ];
+}
+```
+
+Creating a custom hook is amazingly simple. In fact, we did this back when we created our app context. But let's go over in detail how this works:
+
+1. A custom React Hook starts with the word `use` in its name. So ours is called `useFormFields`.
+
+2. Our Hook takes the initial state of our form fields as an object and saves it as a state variable called `fields`. The initial state in our case is an object where the _keys_ are the ids of the form fields and the _values_ are what the user enters.
+
+3. So our hook returns an array with `fields` and a callback function that sets the new state based on the event object. The callback function takes the event object and gets the form field id from `event.target.id` and the value from `event.target.value`. In the case of our form elements, the `event.target.id` comes from the `controlId` that's set in the `Form.Group` element:
+
+ ```tsx
+
+ Email
+ setEmail(e.target.value)}
+ />
+
+ ```
+
+4. The callback function is directly using `setValues`, the function that we get from `useState`. So `onChange` we take what the user has entered and call `setValues` to update the state of `fields`, `{ ...fields, [event.target.id]: event.target.value }`. This updated object is now set as our new form field state.
+
+And that's it! We can now use this in our Login component.
+
+### Using Our Custom Hook
+
+We need to make a couple of changes to the component to use our custom hook.
+
+{%change%} Let's start by importing it in `src/containers/Login.tsx`.
+
+```typescript
+import { useFormFields } from "../lib/hooksLib";
+```
+
+{%change%} Replace the variable declarations.
+
+```typescript
+const [email, setEmail] = useState("");
+const [password, setPassword] = useState("");
+```
+
+{%change%} With:
+
+```typescript
+const [fields, handleFieldChange] = useFormFields({
+ email: "",
+ password: "",
+});
+```
+
+{%change%} Replace the `validateForm` function with.
+
+```typescript
+function validateForm() {
+ return fields.email.length > 0 && fields.password.length > 0;
+}
+```
+
+{%change%} In the `handleSubmit` function, replace the `Auth.signIn` call with.
+
+```typescript
+await Auth.signIn(fields.email, fields.password);
+```
+
+{%change%} Replace our two form fields, starting with the ``.
+
+```tsx
+
+```
+
+{%change%} And finally the password ``.
+
+```tsx
+
+```
+
+You'll notice that we are using our `useFormFields` hook. A good way to think about custom React Hooks is to simply replace the line where we use it, with the Hook code itself. So instead of this line:
+
+```typescript
+const [fields, handleFieldChange] = useFormFields({
+ email: "",
+ password: "",
+});
+```
+
+Simply imagine the code for the `useFormFields` function instead!
+
+Finally, we are setting our fields using the function our custom hook is returning.
+
+```tsx
+onChange = { handleFieldChange }
+```
+
+Now we are ready to tackle our sign up page.
diff --git a/_chapters/create-a-dynamodb-table-in-sst.md b/_chapters/create-a-dynamodb-table-in-sst.md
new file mode 100644
index 0000000000..2695dbc9b0
--- /dev/null
+++ b/_chapters/create-a-dynamodb-table-in-sst.md
@@ -0,0 +1,59 @@
+---
+layout: post
+title: Create a DynamoDB Table in SST
+date: 2021-08-23 00:00:00
+lang: en
+description: In this chapter we'll be using a higher-level SST component to configure a DynamoDB table.
+redirect_from: /chapters/configure-dynamodb-in-cdk.html
+ref: create-a-dynamodb-table-in-sst
+comments_id: create-a-dynamodb-table-in-sst/2459
+---
+
+We are now going to start creating our infrastructure in SST. Starting with DynamoDB.
+
+### Create a Table
+
+{%change%} Add the following to our `infra/storage.ts`.
+
+```typescript
+// Create the DynamoDB table
+export const table = new sst.aws.Dynamo("Notes", {
+ fields: {
+ userId: "string",
+ noteId: "string",
+ },
+ primaryIndex: { hashKey: "userId", rangeKey: "noteId" },
+});
+```
+
+Let's go over what we are doing here.
+
+We are using the [`Dynamo`]({{ site.sst_url }}/docs/component/aws/dynamo/){:target="_blank"} component to create our DynamoDB table.
+
+It has two fields:
+
+1. `userId`: The id of the user that the note belongs to.
+2. `noteId`: The id of the note.
+
+We are then creating an index for our table.
+
+Each DynamoDB table has a primary key. This cannot be changed once set. The primary key uniquely identifies each item in the table, so that no two items can have the same key. DynamoDB supports two different kinds of primary keys:
+
+- Partition key
+- Partition key and sort key (composite)
+
+We are going to use the composite primary key (referenced by `primaryIndex` in code block above) which gives us additional 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 particular note.
+
+### Deploy Changes
+
+After you make your changes, SST will automatically create the table. You should see something like this at the end of the deploy process.
+
+```bash
+| Created Notes sst:aws:Dynamo
+```
+
+{%info%}
+You'll need to make sure you have `sst dev` running, if not then restart it by running `npx sst dev`.
+{%endinfo%}
+
+Now that our database has been created, let's create an S3 bucket to handle file uploads.
diff --git a/_chapters/create-a-hello-world-api.md b/_chapters/create-a-hello-world-api.md
new file mode 100644
index 0000000000..66ad6f97a4
--- /dev/null
+++ b/_chapters/create-a-hello-world-api.md
@@ -0,0 +1,121 @@
+---
+layout: post
+title: Create a Hello World API
+date: 2021-08-17 00:00:00
+lang: en
+description: In this chapter we'll be creating a simple Hello World API using SST.
+ref: create-a-hello-world-api
+comments_id: create-a-hello-world-api/2460
+---
+
+With our newly created SST app, we are ready to deploy a simple _Hello World_ API. Let's rename some of the files from our template.
+
+### Rename the Template
+
+{%change%} Replace our `infra/api.ts` with the following.
+
+```ts
+import { bucket } from "./storage";
+
+export const api = new sst.aws.ApiGatewayV2("Api");
+
+api.route("GET /", {
+ link: [bucket],
+ handler: "packages/functions/src/api.handler",
+});
+```
+
+Here we are creating a simple API with one route, `GET /`. When this API is invoked, the function called `handler` in `packages/functions/src/api.ts` will be executed.
+
+We are also _linking_ an S3 Bucket to our API. This allows the functions in our API to access the bucket. We'll be using this bucket later to handle file uploads. For now let's quickly rename it.
+
+{%change%} Replace our `infra/storage.ts` with.
+
+```ts
+// Create an S3 bucket
+export const bucket = new sst.aws.Bucket("Uploads");
+```
+
+Also let's rename how this bucket is accessed in our app code. We'll go into detail about this in the coming chapters.
+
+{%change%} Rename `Resource.MyBucket.name` line in `packages/functions/src/api.ts`.
+
+```ts
+body: `${Example.hello()} Linked to ${Resource.Uploads.name}.`,
+```
+
+Given that we've renamed a few components, let's also make the change in our config.
+
+{%change%} Replace the `run` function in `sst.config.ts`.
+
+```ts
+async run() {
+ await import("./infra/storage");
+ await import("./infra/api");
+},
+```
+
+{%note%}
+By default SST sets you up with a TypeScript project. While the infrastructure is in TypeScript, you are free to use regular JavaScript in your application code.
+{%endnote%}
+
+Let's go ahead and deploy this.
+
+### Start Your Dev Environment
+
+We'll do this by starting up our local development environment. SST's dev environment runs your functions [Live]({{ site.sst_url }}/docs/live){:target="_blank"}. It allows you to work on your serverless apps live.
+
+{%change%} Start your dev environment.
+
+```bash
+$ npx sst dev
+```
+
+Running `sst dev` will take a minute or two to deploy your app and bootstrap your account for SST.
+
+```txt
+SST 0.1.17 ready!
+
+➜ App: notes
+ Stage: jayair
+ Console: https://console.sst.dev/local/notes/jayair
+
+ ...
+
++ Complete
+ Api: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
+```
+
+The `Api` is the API we just created. Let's test our endpoint. If you open the endpoint URL in your browser, you should see _Hello World!_ being printed out.
+
+
+
+You'll notice its also printing out the name of the bucket that it's linked to.
+
+### Deploying to Prod
+
+To deploy our API to prod, we'll need to stop our local development environment and run the following.
+
+```bash
+$ npx sst deploy --stage production
+```
+
+We don't have to do this right now. We'll be doing it later once we are done working on our app.
+
+The idea here is that we are able to work on separate environments. So when we are working in our personal stage (`jayair`), it doesn't break the API for our users in `production`. The environment (or stage) names in this case are just strings and have no special significance. We could've called them `development` and `prod` instead.
+
+We are however creating completely new apps when we deploy to a different environment. This is another advantage of the SST workflow. The infrastructure as code idea makes it easy to replicate to new environments. And the pay per use model of serverless means that we are not charged for these new environments unless we actually use them.
+
+### Commit the Changes
+
+As we work through the guide we'll save our changes.
+
+{%change%} Commit what we have and push our changes to GitHub.
+
+```bash
+$ git add .
+$ git commit -m "Initial commit"
+$ git push
+```
+
+Now we are ready to create the backend for our notes app.
diff --git a/_chapters/create-a-login-page.md b/_chapters/create-a-login-page.md
index 8378490cf6..bbf4497bcc 100644
--- a/_chapters/create-a-login-page.md
+++ b/_chapters/create-a-login-page.md
@@ -3,102 +3,88 @@ layout: post
title: Create a Login Page
date: 2017-01-13 00:00:00
lang: en
-comments_id: create-a-login-page
+ref: create-a-login-page
description: We are going to add a login page to our React.js app. To create the login form we are using the FormGroup and FormControl React-Bootstrap components.
-context: true
comments_id: create-a-login-page/71
---
-Let's create a page where the users of our app can login with their credentials. 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.
+Let's create a page where the users of our app can login with their credentials. When we [created our User Pool]({% link _archives/create-a-cognito-user-pool.md %}){:target="_blank"} 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 creating 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.
+{%change%} Create a new file `src/containers/Login.tsx` and add the following.
-``` coffee
-import React, { Component } from "react";
-import { Button, FormGroup, FormControl, ControlLabel } from "react-bootstrap";
+```tsx
+import React, { useState } from "react";
+import Form from "react-bootstrap/Form";
+import Stack from "react-bootstrap/Stack";
+import Button from "react-bootstrap/Button";
import "./Login.css";
-export default class Login extends Component {
- constructor(props) {
- super(props);
+export default function Login() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
- this.state = {
- email: "",
- password: ""
- };
+ function validateForm() {
+ return email.length > 0 && password.length > 0;
}
- validateForm() {
- return this.state.email.length > 0 && this.state.password.length > 0;
- }
-
- handleChange = event => {
- this.setState({
- [event.target.id]: event.target.value
- });
- }
-
- handleSubmit = event => {
+ function handleSubmit(event: React.FormEvent) {
event.preventDefault();
}
- render() {
- return (
-
-
-
- );
- }
+
+
+
+ );
}
```
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.
+1. Right at the top of our component, we are using the `useState` hook to store what the user enters in the form. The `useState` hook just gives you the current value of the variable you want to store in the state and a function to set the new value.
-2. We then connect the state to our two fields in the form by setting `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.
+2. We then connect the state to our two fields in the form using the `setEmail` and `setPassword` functions to store what the user types in — `e.target.value`. Once we set the new state, our component gets re-rendered. The variables `email` and `password` now have the new values.
-3. But to update the state when the user types something into these fields, we'll call a handle function named `handleChange`. This function grabs the `id` (set as `controlId` for the ``) 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 function like so: `handleChange = (event) => { } `.
+3. We are setting the form controls to show the value of our two state variables `email` and `password`. In React, this pattern of displaying the current form value as a state variable and setting the new one when a user types something, is called a Controlled Component.
4. We are setting 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 button with our state by using a validate function 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 submitted. For now we are simply suppressing the browsers default behavior on submit but we'll do more here later.
+6. Finally, we trigger our callback `handleSubmit` when the form is submitted. For now we are simply suppressing the browser's 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`.
+{%change%} Let's add a couple of styles to this in the file `src/containers/Login.css`.
-``` css
+```css
@media all and (min-width: 480px) {
.Login {
padding: 60px 0;
@@ -115,16 +101,16 @@ 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 ``.
+{%change%} Now we link this container up with the rest of our app by adding the following line to `src/Routes.tsx` below our `` route.
-``` coffee
-
+```tsx
+} />
```
-And include our component in the header.
+{%change%} And include our component in the header.
-``` javascript
-import Login from "./containers/Login";
+```tsx
+import Login from "./containers/Login.tsx";
```
Now if we switch to our browser and navigate to the login page we should see our newly created form.
diff --git a/_chapters/create-a-new-reactjs-app.md b/_chapters/create-a-new-reactjs-app.md
index 11c9f9ad83..52703d5886 100644
--- a/_chapters/create-a-new-reactjs-app.md
+++ b/_chapters/create-a-new-reactjs-app.md
@@ -4,48 +4,152 @@ title: Create a New React.js App
date: 2017-01-06 00:00:00
lang: en
ref: create-a-new-react-js-app
-description: Create React App helps you build React.js app with no configuration. Install the Create React App CLI using the NPM package and use the command to start a new React.js project.
-context: true
+description: In this chapter we'll use Vite to create a new React.js app. We'll be deploying our React app to AWS using the SST StaticSite component.
comments_id: create-a-new-react-js-app/68
---
-Let's get started with our frontend. We are going to create a single page app using [React.js](https://facebook.github.io/react/). We'll use the [Create React App](https://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.
+We are now ready to work on our frontend. So far we've built and deployed our backend API and infrastructure. We are now going to build a web app that connects to our backend.
-Move out of the directory that we were working in for the backend.
+We are going to create a single page app using [React.js](https://facebook.github.io/react/). We'll use the [Vite](https://vitejs.dev) project to set everything up.
-``` bash
-$ cd ../
+### Create a New React App
+
+{%change%} Run the following command **in the `packages/` directory**.
+
+```bash
+$ npm create vite@latest frontend -- --template react-ts
```
-### Create a New React App
+{%note%}
+Make sure you use the extra `--` in the command.
+{%endnote%}
+
+This will create your new project in the `frontend/` directory.
+
+{%change%} Let's update the name of the package in the `packages/frontend/package.json`. Replace this:
+
+```diff
+- "name": "frontend",
++ "name": "@notes/frontend",
+```
+
+Make sure to use the name of your app instead of `notes`.
+
+{%change%} Now install the dependencies.
+
+```bash
+$ cd frontend
+$ npm install
+```
+
+This should take a second to run.
+
+We also need to make a small change to our Vite config to bundle our frontend.
+
+{%change%} Add the following below the `plugins: [react()],` line in `packages/frontend/vite.config.ts`.
+
+```ts
+build: {
+ // NOTE: Needed when deploying
+ chunkSizeWarningLimit: 800,
+},
+```
+
+### Add the React App to SST
+
+We are going to be deploying our React app to AWS. To do that we'll be using the SST [`StaticSite`]({{ site.sst_url }}/docs/component/aws/static-site/){:target="_blank"} component.
+
+{%change%} Create a new file in `infra/web.ts` and add the following.
+
+```ts
+import { api } from "./api";
+import { bucket } from "./storage";
+import { userPool, identityPool, userPoolClient } from "./auth";
+
+const region = aws.getRegionOutput().name;
+
+export const frontend = new sst.aws.StaticSite("Frontend", {
+ path: "packages/frontend",
+ build: {
+ output: "dist",
+ command: "npm run build",
+ },
+ environment: {
+ VITE_REGION: region,
+ VITE_API_URL: api.url,
+ VITE_BUCKET: bucket.name,
+ VITE_USER_POOL_ID: userPool.id,
+ VITE_IDENTITY_POOL_ID: identityPool.id,
+ VITE_USER_POOL_CLIENT_ID: userPoolClient.id,
+ },
+});
+```
+
+We are doing a couple of things of note here:
+
+1. We are pointing our `StaticSite` component to the `packages/frontend/` directory where our React app is.
+2. We are passing in the outputs from our other components as [environment variables in Vite](https://vitejs.dev/guide/env-and-mode.html#env-variables){:target="_blank"}. This means that we won't have to hard code them in our React app. The `VITE_*` prefix is a convention Vite uses to say that we want to access these in our frontend code.
+
+### Adding to the app
+
+Let's add this to our config.
+
+
+{%change%} Add this below the `await import("./infra/api");` line in your `sst.config.ts`.
+
+```ts
+await import("./infra/web");
+```
-Run the following command to create the client for our notes app.
+### Deploy Our Changes
-``` bash
-$ npx create-react-app notes-app-client
+If you switch over to your terminal, you will notice that your changes are being deployed.
+
+{%info%}
+You’ll need to have `sst dev` running for this to happen. If you had previously stopped it, then running `npx sst dev` will deploy your changes again.
+{%endinfo%}
+
+```bash
++ Complete
+ Api: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
+ Frontend: https://d1wyq46yoha2b6.cloudfront.net
+ ...
```
-This should take a second to run, and it will create your new project and your new working directory.
+### Starting the React App
+
+The `sst dev` CLI will automatically start our React frontend by running `npm run dev`. It also passes in the environment variables that we have configured above.
+
+
+
+You can click on **Frontend** in the sidebar or navigate to it.
-Now let's go into our working directory and run our project.
+This should show where your frontend is running locally.
-``` bash
-$ cd notes-app-client
-$ npm start
+```bash
+VITE v5.3.4 ready in 104 ms
+
+➜ Local: http://127.0.0.1:5173/
+➜ Network: use --host to expose
+➜ press h + enter to show help
```
-This should fire up the newly created app in your browser.
+{%info%}
+SST doesn't deploy your frontend while you are working locally. This is because most frontends come with their own local dev environments.
+{%endinfo%}
+
+If you head to that URL in your browser you should see.
-
+
### Change the Title
-Let's quickly change the title of our note taking app. Open up `public/index.html` and edit the `title` tag to the following:
+Let's quickly change the title of our note taking app.
-``` html
+{%change%} Open up `packages/frontend/index.html` and edit the `title` tag to the following:
+
+```html
Codestin Search App
```
-Create React App comes pre-loaded with a pretty convenient yet minimal development environment. It includes live reloading, a testing framework, ES6 support, and [much more](https://github.com/facebookincubator/create-react-app#why-use-this).
-
-Next, we are going to create our app icon and update the favicons.
+Now we are ready to build our frontend! We are going to start by creating our app icon and updating the favicons.
diff --git a/_chapters/create-a-route-that-redirects.md b/_chapters/create-a-route-that-redirects.md
index af44e78998..87f22b3735 100644
--- a/_chapters/create-a-route-that-redirects.md
+++ b/_chapters/create-a-route-that-redirects.md
@@ -4,53 +4,75 @@ title: Create a Route That Redirects
date: 2017-02-02 00:00:00
lang: en
redirect_from: /chapters/create-a-hoc-that-checks-auth.html
-description: In our React.js app we want to redirect users to the login page if they are not logged in and redirect them away from the login page if they are logged in. To do so we are going to use the Redirect component from React Router v4.
-context: true
+description: In our React.js app we want to redirect users to the login page if they are not logged in and redirect them away from the login page if they are logged in. To do so we are going to use the Redirect component and useLocation hook from React Router. While, the session will be stored in our app Context using the useContext hook.
comments_id: create-a-route-that-redirects/47
ref: create-a-route-that-redirects
---
Let's first create a route that will check if the user is logged in before routing.
-Add the following to `src/components/AuthenticatedRoute.js`.
-
-``` coffee
-import React from "react";
-import { Route, Redirect } from "react-router-dom";
-
-export default ({ component: C, props: cProps, ...rest }) =>
-
- cProps.isAuthenticated
- ?
- : }
- />;
+{%change%} Add the following file in components `src/components/AuthenticatedRoute.tsx`.
+
+```tsx
+import { ReactElement } from "react";
+import { Navigate, useLocation } from "react-router-dom";
+import { useAppContext } from "../lib/contextLib";
+
+export default function AuthenticatedRoute({
+ children,
+}: {
+ children: ReactElement;
+}): ReactElement {
+ const { pathname, search } = useLocation();
+ const { isAuthenticated } = useAppContext();
+
+ if (!isAuthenticated) {
+ return ;
+ }
+
+ return children;
+}
```
-This component is similar to the `AppliedRoute` component that we created in the [Add the session to the state]({% link _chapters/add-the-session-to-the-state.md %}) chapter. The main difference being that we look at the props that are passed in to check if a user is authenticated. If the user is authenticated, then we simply render the passed in component. And if the user is not authenticated, 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 after the user logs in.
+This simple component creates a `Route` where its children are rendered only if the user is authenticated. If the user is not authenticated, then it redirects to the login page. Let's take a closer look at it:
+
+- Like all components in React, `AuthenticatedRoute` has a prop called `children` that represents all child components. Example child components in our case would be `NewNote`, `Notes` and `Settings`.
+
+- The `AuthenticatedRoute` component returns a React Router `Route` component.
+
+- We use the `useAppContext` hook to check if the user is authenticated.
+
+- If the user is authenticated, then we simply render the `children` component. And if the user is not authenticated, then we use the `Navigate` React Router component to redirect the user to the login page.
+
+- We also pass in the current path to the login page (`redirect` in the query string). We will use this later to redirect us back after the user logs in. We use the `useLocation` React Router hook to get this info.
We'll do something similar to ensure that the user is not authenticated.
-Add the following to `src/components/UnauthenticatedRoute.js`.
+{%change%} Next, add the following file in components `src/components/UnauthenticatedRoute.tsx`.
+
+```tsx
+import { cloneElement, ReactElement } from "react";
+import { Navigate } from "react-router-dom";
+import { useAppContext } from "../lib/contextLib";
-``` coffee
-import React from "react";
-import { Route, Redirect } from "react-router-dom";
+interface Props {
+ children: ReactElement;
+}
-export default ({ component: C, props: cProps, ...rest }) =>
-
- !cProps.isAuthenticated
- ?
- : }
- />;
+export default function UnauthenticatedRoute(props: Props): ReactElement {
+ const { isAuthenticated } = useAppContext();
+ const { children } = props;
+
+ if (isAuthenticated) {
+ return ;
+ }
+
+ return cloneElement(children, props);
+}
```
-Here we are checking to ensure that the user is not authenticated before we render the component that is passed in. And in the case where the user is authenticated, we use the `Redirect` component to simply send the user to the homepage.
+Here we are checking to ensure that the user is **not** authenticated before we render the child components. Example child components here would be `Login` and `Signup`. And in the case where the user is authenticated, we use the `Navigate` component to simply send the user to the homepage.
+
+The `cloneElement` above makes sure that passed in `state` is handled correctly for child components of `UnauthenticatedRoute` routes.
Next, let's use these components in our app.
diff --git a/_chapters/create-a-settings-page.md b/_chapters/create-a-settings-page.md
index 9904a1d970..fb7efd2434 100644
--- a/_chapters/create-a-settings-page.md
+++ b/_chapters/create-a-settings-page.md
@@ -1,10 +1,9 @@
---
layout: post
title: Create a Settings Page
-date: 2018-03-21 00:00:00
+date: 2017-01-31 06:00:00
lang: en
description: Our notes app needs a settings page for our users to input their credit card details and sign up for a pricing plan.
-context: true
ref: create-a-settings-page
comments_id: create-a-settings-page/184
---
@@ -18,118 +17,72 @@ We are going to add a settings page to our app. This is going to allow users to
To get started let's add our settings page.
-Create a new file in `src/containers/Settings.js` and add the following.
+{%change%} Create a new file in `src/types/billing.ts` and add the following to define a type for our billing API.
-``` coffee
-import React, { Component } from "react";
-import { API } from "aws-amplify";
+```typescript
+export interface BillingType {
+ storage: string;
+ source?: string;
+}
+```
-export default class Settings extends Component {
- constructor(props) {
- super(props);
+{%change%} Create a new file in `src/containers/Settings.tsx` and add the following.
- this.state = {
- isLoading: false
- };
- }
+```tsx
+import { useState } from "react";
+import config from "../config";
+import { API } from "aws-amplify";
+import { onError } from "../lib/errorLib";
+import { useNavigate } from "react-router-dom";
+import { BillingType } from "../types/billing";
- billUser(details) {
+export default function Settings() {
+ const nav = useNavigate();
+ const [isLoading, setIsLoading] = useState(false);
+
+ function billUser(details: BillingType) {
return API.post("notes", "/billing", {
- body: details
+ body: details,
});
}
- render() {
- return (
-
-
- );
- }
+ return ;
}
```
-Next import this component in the header of `src/Routes.js`.
+{%change%} Next, add the following below the `/signup` route in our `` block in `src/Routes.tsx`.
-``` js
-import Settings from "./containers/Settings";
+```tsx
+} />
```
-And replace our `` block in `src/Routes.js` with this.
-
-``` coffee
-
-
-
-
-
-
-
- { /* Finally, catch all unmatched routes */ }
-
-
-```
+{%change%} And import this component in the header of `src/Routes.js`.
-Notice that we added a route for our new settings page.
-
-Next add a link to our settings page in the navbar by replacing the `render` method in `src/App.js` with this.
-
-``` coffee
-render() {
- const childProps = {
- isAuthenticated: this.state.isAuthenticated,
- userHasAuthenticated: this.userHasAuthenticated
- };
-
- return (
- !this.state.isAuthenticating &&
-
-
-
-
- Scratch
-
-
-
-
-
-
-
-
-
- );
-}
+```tsx
+import Settings from "./containers/Settings.tsx";
```
-You'll notice that we added another link in the navbar for the case a user is logged in.
+Next add a link to our settings page in the navbar.
-Now if you head over to your app, you'll see a new **Settings** link at the top. Of course, the page is pretty empty right now.
-
-
+{%change%} Replace the following line in the `return` statement `src/App.tsx`.
-### Commit the Changes
+```tsx
+Logout
+```
-Let's quickly commit these to Git.
+{%change%} With.
-``` bash
-$ git add .
-$ git commit -m "Adding settings page"
+```tsx
+<>
+
+ Settings
+
+ Logout
+>
```
+Now if you head over to your app, you'll see a new **Settings** link at the top. Of course, the page is pretty empty right now.
+
+
+
Next, we'll add our Stripe SDK keys to our config.
diff --git a/_chapters/create-a-signup-page.md b/_chapters/create-a-signup-page.md
index 256725c845..db6c20cd48 100644
--- a/_chapters/create-a-signup-page.md
+++ b/_chapters/create-a-signup-page.md
@@ -7,7 +7,7 @@ date: 2017-01-19 00:00:00
comments_id: create-a-signup-page/65
---
-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 confirmation code via email. We also need to authenticate the new user once they've confirmed their account.
+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]({% link _archives/create-a-cognito-user-pool.md %}) sends them a confirmation code via email. We also need to authenticate the new user once they've confirmed their account.
So the signup flow will look something like this:
diff --git a/_chapters/create-an-aws-account.md b/_chapters/create-an-aws-account.md
index df1bc6daa0..5ec172589c 100644
--- a/_chapters/create-an-aws-account.md
+++ b/_chapters/create-an-aws-account.md
@@ -5,12 +5,9 @@ date: 2016-12-25 00:00:00
lang: en
ref: create-an-aws-account
description: To create a serverless app using Lambda we are going to first need to create an AWS (Amazon Web Services) account.
-context: true
comments_id: create-an-aws-account/88
---
-Let's first get started by creating an AWS (Amazon Web Services) account. Of course you can skip this if you already have one. Head over to the [AWS homepage](https://aws.amazon.com) and hit the **Create a Free Account** and follow the steps to create your account.
+Let's first get started by creating an AWS (Amazon Web Services) account. Of course you can skip this if you already have one. Head over to the [AWS homepage](https://aws.amazon.com){:target="_blank"} and create your account.
-
-
-Next let's configure your account so it's ready to be used for the rest of our guide.
+Next we'll configure your account so it's ready to be used for the rest of our guide.
diff --git a/_chapters/create-an-iam-user.md b/_chapters/create-an-iam-user.md
index 088802ac0b..be37c1833b 100644
--- a/_chapters/create-an-iam-user.md
+++ b/_chapters/create-an-iam-user.md
@@ -5,54 +5,79 @@ date: 2016-12-25 12:00:00
lang: en
ref: create-an-iam-user
description: To interact with AWS using some command line tools we need to create an IAM user through the AWS console.
-context: true
comments_id: create-an-iam-user/92
---
+Once we have an AWS account, we'll need to create an IAM user to programmatically interact with it. We'll be using this later to configure our AWS CLI (command-line interface).
+
Amazon IAM (Identity 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 application 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](https://console.aws.amazon.com) and select IAM from the list of services.
-
-
+First, log in to your [AWS Console](https://console.aws.amazon.com) and search for IAM in the search bar. Hover or focus on the **IAM card** and then select the **Users** link.
-Select **Users**.
+
-
+Select **Add Users**.
-Select **Add User**.
+
-
+Enter a **User name**, then select **Next**.
-Enter a **User name** and check **Programmatic access**, then select **Next: Permissions**.
+This account will be used by our [AWS CLI](https://aws.amazon.com/cli/) and [SST]({{ site.sst_github_repo }}). They will be connecting to the AWS API directly and will not be using the Management Console.
-This account will be used by our [AWS CLI](https://aws.amazon.com/cli/) and [Serverless Framework](https://serverless.com). They'll be connecting to the AWS API directly and will not be using the Management Console.
+{%note%}
+The best practice is to avoid creating keys when possible. When using programmatic access keys, regularly rotate them. In most cases, there are alternative solutions, see the [AWS IAM User Guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_RotateAccessKey) for more information.
+{%endnote%}
-
+
Select **Attach existing policies directly**.
-
+
-Search for **AdministratorAccess** and select the policy, then select **Next: Review**.
+Search for **AdministratorAccess** and select the policy by checking the checkbox, then select **Next**.
-We can provide a more fine-grained policy here and we cover this later in the [Customize the Serverless IAM Policy]({% link _chapters/customize-the-serverless-iam-policy.md %}) chapter. But for now, let's continue with this.
+We can provide a more fine-grained policy here. We cover this later in the [Customize the Serverless IAM Policy]({% link _archives/customize-the-serverless-iam-policy.md %}) chapter. But for now, let's continue with this.
-
+
Select **Create user**.
-
+
+
+Select **View user**.
+
+
+
+Select **Security credentials**
+
+
+
+Select **Create access key**
+
+
+
+In keeping with the current guide instructions, we will choose other to generate an access key and secret. Select **Other** and select **Next**
+
+
+
+You could add a descriptive tag here, but we will skip that in this tutorial, select **Create access key**
+
+
Select **Show** to reveal **Secret access key**.
-
+
+
+{%note%}
+This is the only screen on which you will be able to access this key. Save it to a secure location using best practices to ensure the security of your application.
+{%endnote%}
-Take a note of the **Access key ID** and **Secret access key**. We will be needing this later.
+Take a note of the **Access key** and **Secret access key**. We will be needing this in the next chapter.
-
+
-The concept of IAM pops up very frequently when working with AWS services. So it is worth taking a better look at what IAM is and how it can help us secure our serverless setup.
+Now let's configure our AWS CLI. By configuring the AWS CLI, we can deploy our applications from our command line.
diff --git a/_chapters/create-an-s3-bucket-for-file-uploads.md b/_chapters/create-an-s3-bucket-for-file-uploads.md
deleted file mode 100644
index c54b1c2a4b..0000000000
--- a/_chapters/create-an-s3-bucket-for-file-uploads.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-layout: post
-title: Create an S3 Bucket for File Uploads
-date: 2016-12-27 00:00:00
-lang: en
-ref: create-an-s3-bucket-for-file-uploads
-description: To allow users to upload files to our serverless app we are going to use Amazon S3 (Simple Storage Service). S3 allows you to store files and organize them into buckets. We are going to create an S3 bucket and enable CORS (cross-origin resource sharing) to ensure that our React.js app can upload files to it.
-redirect_from: /chapters/create-a-s3-bucket-for-file-uploads.html
-context: true
-comments_id: create-an-s3-bucket-for-file-uploads/150
----
-
-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 attachment.
-
-[Amazon S3](https://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 identified 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](https://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 communicating 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 applications 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 configuration**.
-
-
-
-Add the following CORS configuration into the editor, then hit **Save**.
-
-``` xml
-
-
- *
- GET
- PUT
- POST
- HEAD
- DELETE
- 3000
- *
-
-
-```
-
-Note that you can edit this configuration to use your own domain or a list of domains when you use this in production.
-
-
-
-Now that our S3 bucket is ready, let's get set up to handle user authentication.
-
diff --git a/_chapters/create-an-s3-bucket-in-sst.md b/_chapters/create-an-s3-bucket-in-sst.md
new file mode 100644
index 0000000000..89c59ea349
--- /dev/null
+++ b/_chapters/create-an-s3-bucket-in-sst.md
@@ -0,0 +1,33 @@
+---
+layout: post
+title: Create an S3 Bucket in SST
+date: 2021-07-17 00:00:00
+lang: en
+description: In this chapter we will be using a higher-level component to create an S3 bucket in our SST app.
+redirect_from: /chapters/configure-s3-in-cdk.html
+ref: create-an-s3-bucket-in-sst
+comments_id: create-an-s3-bucket-in-sst/2461
+---
+
+We'll be storing the files that's uploaded by our users to an S3 bucket. The template we are using comes with a bucket that we renamed back in the [Create a Hello World API]({% link _chapters/create-a-hello-world-api.md %}).
+
+Recall the following from `infra/storage.ts`.
+
+```ts
+// Create an S3 bucket
+export const bucket = new sst.aws.Bucket("Uploads");
+```
+
+Here we are creating a new S3 bucket using the SST [`Bucket`]({{ site.sst_url }}/docs/component/aws/bucket){:target="_blank"} component.
+
+### Commit the Changes
+
+{%change%} Let's commit and push our changes to GitHub.
+
+```bash
+$ git add .
+$ git commit -m "Adding storage"
+$ git push
+```
+
+Next, let's create the API for our notes app.
diff --git a/_chapters/create-an-sst-app.md b/_chapters/create-an-sst-app.md
new file mode 100644
index 0000000000..a4a2acbab8
--- /dev/null
+++ b/_chapters/create-an-sst-app.md
@@ -0,0 +1,95 @@
+---
+layout: post
+title: Create an SST app
+date: 2021-08-17 00:00:00
+lang: en
+description: Create your first SST app by cloning the template from GitHub.
+redirect_from:
+ - /chapters/building-a-cdk-app-with-sst.html
+ - /chapters/initialize-a-github-repo.html
+ - /chapters/initialize-the-backend-repo.html
+ - /chapters/initialize-the-frontend-repo.html
+ref: create-an-sst-app
+comments_id: create-an-sst-app/2462
+---
+
+Now that we have some background on SST and _infrastructure as code_, we are ready to create our first SST app!
+
+We are going to use a [template SST project][Template]{:target="_blank"}, it comes with a good monorepo setup. It'll help us organize our frontend and APIs.
+
+Head over to — [**github.com/sst/monorepo-template**][Template]{:target="_blank"}, click **Use this template** > **Create a new repository**.
+
+
+
+Give your repository a name, in our case we are calling it `notes`. Next hit **Create repository**.
+
+
+
+Once your repository is created, copy the repository URL.
+
+{%change%} Run the following in your working directory.
+
+```bash
+$ git clone
+$ cd notes
+```
+
+{%change%} Use your app name in the template.
+
+```bash
+$ npx replace-in-file '/monorepo-template/g' 'notes' '**/*.*' --verbose
+```
+
+{%change%} Install the dependencies.
+
+```bash
+$ npm install
+```
+
+By default, the template is creating an API. You can see this in the `sst.config.ts` in the root.
+
+```ts
+///
+
+export default $config({
+ app(input) {
+ return {
+ name: "notes",
+ removal: input?.stage === "production" ? "retain" : "remove",
+ home: "aws",
+ };
+ },
+ async run() {
+ await import("./infra/storage");
+ const api = await import("./infra/api");
+
+ return {
+ api: api.myApi.url,
+ };
+ },
+});
+```
+
+{%caution%}
+To rename an app, you’ll need to remove the resources from the old one and deploy to the new one.
+{%endcaution%}
+
+The name of your app as you might recall is `notes`. A word of caution on IaC, if you rename your app after you deploy it, it doesn't rename the previously created resources in your app. You'll need to remove your old app and redeploy it again with the new name. To get a better sense of this, you can read more about the [SST workflow]({{ site.sst_url }}/docs/workflow){:target="_blank"}.
+
+## Project layout
+
+An SST app is made up of two parts.
+
+1. `infra/` — App Infrastructure
+
+ The code that describes the infrastructure of your serverless app is placed in the `infra/` directory of your project.
+
+2. `packages/` — App Code
+
+ The Lambda function code that's run when your API is invoked is placed in the `packages/functions` directory of your project, the `packages/core` contains our business logic, and the `packages/scripts` are for any one-off scripts we might create.
+
+Later on we'll be adding a `packages/frontend/` directory for our React app.
+
+The starter project that's created is defining a simple _Hello World_ API. In the next chapter, we'll be deploying it and running it locally.
+
+[Template]: https://github.com/sst/monorepo-template
diff --git a/_chapters/create-containers.md b/_chapters/create-containers.md
index 874e7be065..87b328c08a 100644
--- a/_chapters/create-containers.md
+++ b/_chapters/create-containers.md
@@ -4,46 +4,31 @@ title: Create Containers
date: 2017-01-11 00:00:00
lang: en
ref: create-containers
-description: To split up our React.js app into different routes we are going to structure it using containers in React Router v4. We are also going to add the Navbar React-Bootstrap component to our App container.
-context: true
+description: To split up our React.js app into different routes we are going to structure it using containers in React Router v6. We are also going to add the Navbar React-Bootstrap component to our App container.
comments_id: create-containers/62
---
-Currently, our app has a single component that renders our content. For creating 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.
+Currently, our app has a single component that renders our content. For creating 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" (or UI) of our app inside a component and render all the top level components inside them. We are calling the top level components that represent the various pages, containers.
### Add a Navbar
Let's start by creating the outer chrome of our application by first adding a navigation bar to it. We are going to use the [Navbar](https://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.
+{%change%} Go ahead and remove the code inside `src/App.tsx` and replace it with the following.
-``` bash
-$ rm src/logo.svg
-```
-
-And go ahead and remove the code inside `src/App.js` and replace it with the following.
-
-``` coffee
-import React, { Component } from "react";
-import { Link } from "react-router-dom";
-import { Navbar } from "react-bootstrap";
+```tsx
+import Navbar from "react-bootstrap/Navbar";
import "./App.css";
-class App extends Component {
- render() {
- return (
-
-
-
-
- Scratch
-
-
-
-
-
- );
- }
+function App() {
+ return (
+
+
+ Scratch
+
+
+
+ );
}
export default App;
@@ -52,62 +37,63 @@ export default App;
We are doing a few things here:
1. Creating 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 attribute `fluid`.
-3. Using `Link` component from the React-Router to handle the link to our app's homepage (without forcing the page to refresh).
+1. Using a couple of [Bootstrap spacing utility classes](https://getbootstrap.com/docs/4.5/utilities/spacing/) (like `mb-#` and `py-#`) to add margin bottom (`mb`) and padding vertical (`py`). These use a proportional set of spacer units to give a more harmonious feel to our UI.
-Let's also add a couple of line of styles to space things out a bit more.
+Let's clear out the styles that came with our template.
-Remove all the code inside `src/App.css` and replace it with the following:
+{%change%} Remove all the code inside `src/App.css` and replace it with the following:
-``` css
+```css
.App {
- margin-top: 15px;
}
+```
-.App .navbar-brand {
- font-weight: bold;
-}
+For now we don't have any styles to add but we'll leave this file around, in case you want to add to it later.
+
+Also, let's remove some unused template files.
+
+{%change%} Run the following **in the `packages/frontend/` directory**.
+
+```bash
+$ rm -r public/vite.svg src/assets/
```
### Add the Home container
-Now that we have the outer chrome of our application ready, let's add the container for the homepage of our app. It'll respond to the `/` route.
+Now that we have the outer chrome of our application 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.
+{%change%} Create a `src/containers/` directory by running the following **in the `packages/frontend/` directory**.
-``` bash
+```bash
$ 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.
+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`.
+{%change%} Create a new container and add the following to `src/containers/Home.tsx`.
-``` coffee
-import React, { Component } from "react";
+```tsx
import "./Home.css";
-export default class Home extends Component {
- render() {
- return (
-
-
-
Scratch
-
A simple note taking app
-
+export default function Home() {
+ return (
+
+
+
Scratch
+
A simple note taking app
- );
- }
+
+ );
}
```
-This simply renders our homepage given that the user is not currently signed in.
+This 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`.
+{%change%} Add the following into `src/containers/Home.css`.
-``` css
+```css
.Home .lander {
padding: 80px 0;
text-align: center;
@@ -117,60 +103,54 @@ Now let's add a few lines to style this.
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.
+{%change%} Create `src/Routes.tsx` and add the following into it.
-``` coffee
-import React from "react";
-import { Route, Switch } from "react-router-dom";
-import Home from "./containers/Home";
+```tsx
+import { Route, Routes } from "react-router-dom";
+import Home from "./containers/Home.tsx";
-export default () =>
-
-
- ;
+export default function Links() {
+ return (
+
+ } />
+
+ );
+}
```
-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 `/`.
+This component uses this `Routes` 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`.
+{%change%} Add the following to the header of your `src/App.tsx`.
-``` coffee
-import Routes from "./Routes";
+```tsx
+import Routes from "./Routes.tsx";
```
-And add the following line below our `Navbar` component inside the `render` of `src/App.js`.
+{%change%} And add the following line below our `Navbar` component inside `src/App.tsx`.
-``` coffee
+```tsx
```
-So the `render` method of our `src/App.js` should now look like this.
+So the `App` function component of our `src/App.tsx` should now look like this.
-``` coffee
-render() {
+```tsx
+function App() {
return (
-
-
-
-
- Scratch
-
-
-
+
+
+ Scratch
+
@@ -182,6 +162,6 @@ This ensures that as we navigate to different routes in our app, the portion bel
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.
diff --git a/_chapters/create-the-signup-form.md b/_chapters/create-the-signup-form.md
index 978f639515..8dcd52fe2b 100644
--- a/_chapters/create-the-signup-form.md
+++ b/_chapters/create-the-signup-form.md
@@ -4,8 +4,7 @@ title: Create the Signup Form
date: 2017-01-20 00:00:00
lang: en
ref: create-the-signup-form
-description: We are going to create a signup page for our React.js app. To sign up users with Amazon Cognito, we need to create a form that allows users to enter a cofirmation code that is emailed to them.
-context: true
+description: We are going to create a signup page for our React.js app. To sign up users with Amazon Cognito, we need to create a form that allows users to enter a confirmation code that is emailed to them.
comments_id: create-the-signup-form/52
---
@@ -13,143 +12,136 @@ Let's start by creating the signup form that'll get the user's email and passwor
### Add the Container
-Create a new container at `src/containers/Signup.js` with the following.
+{%change%} Create a new container at `src/containers/Signup.tsx` with the following.
-``` coffee
-import React, { Component } from "react";
-import {
- HelpBlock,
- FormGroup,
- FormControl,
- ControlLabel
-} from "react-bootstrap";
+```tsx
+import React, { useState } from "react";
+import Form from "react-bootstrap/Form";
+import Stack from "react-bootstrap/Stack";
+import { useNavigate } from "react-router-dom";
+import { useFormFields } from "../lib/hooksLib";
+import { useAppContext } from "../lib/contextLib";
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() {
+export default function Signup() {
+ const [fields, handleFieldChange] = useFormFields({
+ email: "",
+ password: "",
+ confirmPassword: "",
+ confirmationCode: "",
+ });
+ const nav = useNavigate();
+ const { userHasAuthenticated } = useAppContext();
+ const [isLoading, setIsLoading] = useState(false);
+ const [newUser, setNewUser] = useState(null);
+
+ function validateForm() {
return (
- this.state.email.length > 0 &&
- this.state.password.length > 0 &&
- this.state.password === this.state.confirmPassword
+ fields.email.length > 0 &&
+ fields.password.length > 0 &&
+ fields.password === fields.confirmPassword
);
}
- validateConfirmationForm() {
- return this.state.confirmationCode.length > 0;
+ function validateConfirmationForm() {
+ return fields.confirmationCode.length > 0;
}
- handleChange = event => {
- this.setState({
- [event.target.id]: event.target.value
- });
- }
-
- handleSubmit = async event => {
+ async function handleSubmit(event: React.FormEvent) {
event.preventDefault();
-
- this.setState({ isLoading: true });
-
- this.setState({ newUser: "test" });
-
- this.setState({ isLoading: false });
+ setIsLoading(true);
+ setNewUser("test");
+ setIsLoading(false);
}
- handleConfirmationSubmit = async event => {
+ async function handleConfirmationSubmit(
+ event: React.FormEvent
+ ) {
event.preventDefault();
-
- this.setState({ isLoading: true });
+ setIsLoading(true);
}
- renderConfirmationForm() {
+ function renderConfirmationForm() {
return (
-
-
- Confirmation Code
-
- Please check your email for the code.
-
-
-
+
+
+
+ Confirmation Code
+
+ Please check your email for the code.
+
+
+ Verify
+
+
+
+ );
}
```
@@ -157,17 +149,36 @@ Most of the things we are doing here are fairly straightforward but let's go ove
1. Since we need to show the user a form to enter the confirmation code, we are conditionally rendering two forms based on if we have a user object or not.
+ ```jsx
+ {
+ newUser === null ? renderForm() : renderConfirmationForm();
+ }
+ ```
+
2. We are using the `LoaderButton` component that we created earlier for our submit buttons.
-3. Since we have two forms we have two validation methods called `validateForm` and `validateConfirmationForm`.
+3. Since we have two forms we have two validation functions called `validateForm` and `validateConfirmationForm`.
4. We are setting the `autoFocus` flags on the email and the confirmation code fields.
+ ``
+
5. For now our `handleSubmit` and `handleConfirmationSubmit` don't do a whole lot besides setting the `isLoading` state and a dummy value for the `newUser` state.
-Also, let's add a couple of styles in `src/containers/Signup.css`.
+6. And you'll notice we are using the `useFormFields` custom React Hook that we [previously created]({% link _chapters/create-a-custom-react-hook-to-handle-form-fields.md %}) to handle our form fields.
+
+ ```typescript
+ const [fields, handleFieldChange] = useFormFields({
+ email: "",
+ password: "",
+ confirmPassword: "",
+ confirmationCode: "",
+ });
+ ```
+
+{%change%} Also, let's add a couple of styles in `src/containers/Signup.css`.
-``` css
+```css
@media all and (min-width: 480px) {
.Signup {
padding: 60px 0;
@@ -178,30 +189,28 @@ Most of the things we are doing here are fairly straightforward but let's go ove
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]({% link _chapters/add-the-session-to-the-state.md %}) chapter.
+{%change%} Finally, add our container as a route in `src/Routes.tsx` below our login route.
-``` coffee
-
+```tsx
+} />
```
-And include our component in the header.
+{%change%} And include our component in the header.
-``` javascript
-import Signup from "./containers/Signup";
+```typescript
+import Signup from "./containers/Signup.tsx";
```
-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 still try to fill in an email address, password, and the confirmation code. It'll give you an idea of how the form will behave once we connect it to Cognito.
+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 still try to fill in an email address, password, and confirmation password.
+
+
+
+Then, after hitting submit, you'll get the confirmation code form. This'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.
diff --git a/_chapters/creating-a-ci-cd-pipeline-for-serverless.md b/_chapters/creating-a-ci-cd-pipeline-for-serverless.md
new file mode 100644
index 0000000000..3215fae5a4
--- /dev/null
+++ b/_chapters/creating-a-ci-cd-pipeline-for-serverless.md
@@ -0,0 +1,51 @@
+---
+layout: post
+title: Creating a CI/CD Pipeline for serverless
+date: 2020-11-04 00:00:00
+lang: en
+description: In this chapter we'll be using the SST Console to set up a CI/CD pipeline for our full-stack serverless app.
+ref: creating-a-ci-cd-pipeline-for-serverless
+redirect_from: /chapters/automating-serverless-deployments.html
+comments_id: creating-a-ci-cd-pipeline-for-serverless/174
+---
+
+So to recap, here's what we've created so far.
+
+- A full-stack serverless app that includes:
+ - [Storage with DynamoDB and S3]({% link _chapters/create-a-dynamodb-table-in-sst.md %})
+ - [API]({% link _chapters/add-an-api-to-create-a-note.md %})
+ - [Auth with Cognito]({% link _chapters/auth-in-serverless-apps.md %})
+ - [Frontend in React]({% link _chapters/create-a-new-reactjs-app.md %})
+- [A way to handle secrets locally]({% link _chapters/handling-secrets-in-sst.md %})
+- [A way to run unit tests]({% link _chapters/unit-tests-in-serverless.md %})
+- [Deployed to a prod environment with a custom domain]({% link _chapters/custom-domains-in-serverless-apis.md %})
+
+All of this is neatly [committed in a Git repo]({{ site.sst_demo_repo }}){:target="_blank"}.
+
+So far we've been deploying our app locally through our command line — `npx sst deploy`. But if we had multiple people on our team, or if we were working on different features at the same time, we won't be able to work on our app because the changes would overwrite each other.
+
+To fix this we are going to implement a CI/CD pipeline for our full-stack serverless app.
+
+### What is a CI/CD Pipeline
+
+CI/CD or Continuous Integration/Continuous Delivery is the process of automating deployments by tying it to our source control system. So that when new code changes are pushed, our app is automatically deployed.
+
+A CI/CD pipeline usually includes multiple environments. An environment is one where there are multiple instances of our deployed app. So we can have an environment called _production_ that our users will be using. And _development_ environments that we can use while developing our app.
+
+Here is what our workflow is going to look like:
+
+- Our repo will be connected to our CI/CD service.
+- Any commits that are pushed to the `production` branch will be automatically deployed to the `production` stage.
+- Any PRs will be automatically deployed as preview environments.
+
+Our workflow is fairly simple. But as your team grows, you'll need to add additional dev, staging, or preview environments.
+
+### CI/CD for Serverless
+
+There are many common CI/CD services, like [GitHub Actions](https://github.com/features/actions){:target="_blank"}, [Travis CI](https://travis-ci.org){:target="_blank"} or [CircleCI](https://circleci.com){:target="_blank"}. These usually require you to manually configure the above pipeline. It involves a fair bit of scripts and configuration.
+
+SST makes this easier with the [SST Console]({{ site.sst_url }}/docs/console/){:target="_blank"}'s [Autodeploy](/docs/console/#autodeploy){:target="_blank"} feature. It also shows you all the resources in your app and allows you to monitor and debug them.
+
+We should mention that you don't have to use the SST Console. And this section is completely optional.
+
+Let's get started with setting up the SST Console.
diff --git a/_chapters/cross-stack-references-in-serverless.md b/_chapters/cross-stack-references-in-serverless.md
deleted file mode 100644
index 22465252ef..0000000000
--- a/_chapters/cross-stack-references-in-serverless.md
+++ /dev/null
@@ -1,92 +0,0 @@
----
-layout: post
-title: Cross-Stack References in Serverless
-description: AWS CloudFormation allows us to link multiple Serverless services using cross-stack references. A cross-stack reference consists of an "Export" and "Fn::ImportValue". Cross-stack references are useful for tracking the dependencies between Serverless services.
-date: 2018-04-02 13:00:00
-context: true
-code: mono-repo
-comments_id: cross-stack-references-in-serverless/405
----
-
-In the previous chapter we looked at the [most common patterns for organizing your Serverless applications]({% link _chapters/organizing-serverless-projects.md %}). Now let's look at how to work with multiple services in your Serverless application.
-
-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 [CloudFormation template](https://aws.amazon.com/cloudformation/aws-cloudformation-templates/) using Serverless Framework. This means that in the case of multiple 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 application]({{ site.backend_github_repo }}) into multiple 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 CloudFormation template to refer to the resource in another CloudFormation template.
-
-### CloudFormation Cross-Stack References
-
-To create a cross-stack reference, you need to:
-
-1. Use the `Export:` flag in the `Outputs:` section 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` CloudFormation function.
-
-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:
-
- ```yml
- 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` function:
-
- ```yml
- 'Fn::ImportValue': NotesTableName
- ```
-
-The `Fn::ImportValue` function 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:` function 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 still being referenced in the API service.
-
-- The services need to be deployed in a specific order. The service that is exporting a value needs to be deployed before the one doing the importing. Using the above example again, the DynamoDB service needs to be deployed before the API service.
-
-### Advantages of Cross-Stack References
-
-As your application grows, it can become hard to track the dependencies between the services in the application. 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 application grows.
-
-The other advantage is that you can easily recreate the entire application (say for testing) with ease. This is because none of the services of your application are statically linked to each other.
-
-### Example Setup
-
-Cross-stack references can be very useful but some aspects of it can be a little confusing and the documentation 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]({{ site.backend_github_repo }}) into a [mono-repo app with multiple services that are connected through cross-stack references]({{ site.backend_mono_github_repo }}).
-
-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 additionally, 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 Identity Pool create an IAM role that gives authenticated users permissions to the resources we created.
-
-We are splitting 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]({{ site.backend_mono_github_repo }}) of the above setup that you can use for reference. We'll be linking to it at the bottom of each of the following chapters.
-
-In the next chapter let's look at setting up DynamoDB as a separate service.
diff --git a/_chapters/custom-domains-for-react-apps-on-aws.md b/_chapters/custom-domains-for-react-apps-on-aws.md
new file mode 100644
index 0000000000..1afc4e2166
--- /dev/null
+++ b/_chapters/custom-domains-for-react-apps-on-aws.md
@@ -0,0 +1,70 @@
+---
+layout: post
+title: Custom Domains for React Apps on AWS
+date: 2021-08-17 00:00:00
+lang: en
+description: In this chapter we are setting a custom domain for our React.js app on AWS. We are using the SST StaticSite component to configure the custom domain.
+ref: custom-domains-for-react-apps-on-aws
+comments_id: custom-domains-for-react-apps-on-aws/2463
+---
+
+In the [previous chapter we configured a custom domain for our serverless API]({% link _chapters/custom-domains-in-serverless-apis.md %}). Now let's do the same for our frontend React app.
+
+{%change%} In the `infra/web.ts` add the following above the `environment: {` line.
+
+```ts
+domain:
+ $app.stage === "production"
+ ? {
+ name: "",
+ redirects: ["www."],
+ }
+ : undefined,
+```
+
+Just like the API case, we want to use our custom domain **if** we are deploying to the `production` stage. This means that when we are using our app locally or deploying to any other stage, it won't be using the custom domain.
+
+Of course, you can change this if you'd like to use a custom domain for the other stages. You can use something like `${app.stage}.my-serverless-app.com`. So for `dev` it'll be `dev.my-serverless-app.com`. But we'll leave this as an exercise for you.
+
+The `redirects` prop is necessary because we want visitors of `www.my-serverless-app.com` to be redirected to the URL we want to use. It's a good idea to support both the `www.` and root versions of our domain. You can switch these around so that the root domain redirects to the `www.` version as well.
+
+You won't need to set the `redirects` for the non-prod versions because we don't need `www.` versions for those.
+
+### Deploy the App
+
+Just like the previous chapter, we need to update these changes in prod.
+
+{%change%} Run the following from your project root.
+
+```bash
+$ npx sst deploy --stage production
+```
+
+{%note%}
+Deploying changes to custom domains can take a few minutes.
+{%endnote%}
+
+At the end of the deploy process you should see something like this.
+
+```bash
++ Complete
+ Api: https://api.my-serverless-app.com
+ Frontend: https://my-serverless-app.com
+ ...
+```
+
+And that's it! Our React.js app is now deployed to prod under our own domain!
+
+
+
+### Commit the Changes
+
+{%change%} Let's commit our code so far and push it to GitHub.
+
+```bash
+$ git add .
+$ git commit -m "Setting up custom domains"
+$ git push
+```
+
+At this stage our full-stack serverless app is pretty much complete. In the next couple of optional sections we are going at how we can automate our deployments. We want to set it up so that when we `git push` our changes, our app should deploy automatically.
diff --git a/_chapters/custom-domains-in-serverless-apis.md b/_chapters/custom-domains-in-serverless-apis.md
new file mode 100644
index 0000000000..eab142b663
--- /dev/null
+++ b/_chapters/custom-domains-in-serverless-apis.md
@@ -0,0 +1,52 @@
+---
+layout: post
+title: Custom Domains in serverless APIs
+date: 2021-08-17 00:00:00
+lang: en
+description: In this chapter we are setting a custom domain for our serverless API on AWS. We are using the SST ApiGatewayV2 component to configure the custom domain.
+ref: custom-domains-in-serverless-apis
+comments_id: custom-domains-in-serverless-apis/2464
+---
+
+In the [previous chapter]({% link _chapters/purchase-a-domain-with-route-53.md %}) we purchased a new domain on [Route 53](https://aws.amazon.com/route53/){:target="_blank"}. Now let's use it for our serverless API.
+
+{%change%} In your `infra/api.ts` add this above the `transform: {` line.
+
+```ts
+domain: $app.stage === "production" ? "" : undefined,
+```
+{%note%}
+Without specifying the API subdomain, the deployment will attempt to create duplicate A (IPv4) and AAAA (IPv6) DNS records and error.
+{%endnote%}
+
+This tells SST that we want to use a custom domain **if** we are deploying to the `production` stage. We are not setting one for our `dev` stage, or any other stage.
+
+We could for example, base it on the stage name, `api-${app.stage}.my-serverless-app.com`. So for `dev` it might be `api-dev.my-serverless-app.com`. But we'll leave that as an exercise for you.
+
+The `$app` is a global variable that's available in our config. You can [learn more about it here]({{ site.sst_url }}/docs/reference/global/#app){:target="_blank"}.
+
+### Deploy the App
+
+Let's deploy these changes to prod.
+
+{%change%} Run the following from **your project root**.
+
+```bash
+$ npx sst deploy --stage production
+```
+
+{%note%}
+Deploying changes to custom domains can take a few minutes.
+{%endnote%}
+
+At the end of the deploy process you should see something like this.
+
+```bash
++ Complete
+ Api: https://api.my-serverless-app.com
+ ...
+```
+
+This is great! We now have our app deployed to prod and our API has a custom domain.
+
+Next, let's use our custom domain for our React app as well.
diff --git a/_chapters/debugging-full-stack-serverless-apps.md b/_chapters/debugging-full-stack-serverless-apps.md
new file mode 100644
index 0000000000..d21d7c2738
--- /dev/null
+++ b/_chapters/debugging-full-stack-serverless-apps.md
@@ -0,0 +1,57 @@
+---
+layout: post
+title: Debugging Full-Stack Serverless Apps
+date: 2020-04-03 00:00:00
+lang: en
+description: In this chapter we look at the debugging setup and workflow for full-stack serverless apps. We'll cover some of the most common errors, including errors inside and outside Lambda functions, timeouts and out-of-memory errors.
+comments_id: debugging-full-stack-serverless-apps/1727
+ref: debugging-full-stack-serverless-apps
+---
+
+Now that we are ready to go live with our app, we need to make sure we are setup to monitor and debug errors. This is important because unlike our local environment where we can look at the console (browser or terminal), make changes and fix errors, we cannot do that when our app is live.
+
+### Debugging Workflow
+
+We need to make sure we have a couple of things setup before we can confidently ask others to use our app:
+
+- Frontend
+ - Be alerted when a user runs into an error.
+ - Get all the error details, including the stack trace.
+ - In the case of a backend error, get the API that failed.
+
+- Backend
+ - Look up the logs for an API endpoint.
+ - Get detailed debug logs for all the AWS services.
+ - Catch any unexpected errors (out-of-memory, timeouts, etc.).
+
+It's important that we have a good view of our production environments. It allows us to keep track of what our users are experiencing.
+
+Note that, for the frontend the setup is pretty much what you would do for any React application. But we are covering it here because we want to go over the entire debugging workflow. Right from when you are alerted that a user has gotten an error while using your app, all the way till figuring out which Lambda function caused it.
+
+### Debugging Setup
+
+Here is what we'll be doing in the next few chapters to help accomplish the above workflow.
+
+- Frontend
+
+ On the frontend, we'll be setting up [Sentry](https://sentry.io); a service for monitoring and debugging errors. Sentry has a great free tier that we can use. We'll be integrating it into our React app by reporting any expected errors and unexpected errors. We'll do this by using the [React Error Boundary](https://reactjs.org/docs/error-boundaries.html).
+
+- Backend
+
+ On the backend, AWS has some great logging and monitoring tools thanks to [CloudWatch](https://aws.amazon.com/cloudwatch/). We'll be using CloudWatch through the [Seed](https://seed.run) console. Note that, you can use CloudWatch directly and don't have to rely on Seed for it. We'll also be configuring some debugging helper functions for our backend code.
+
+### Looking Ahead
+
+Here's what we'll be going over in the next few chapters:
+
+1. [Setting up error reporting in React]({% link _chapters/setup-error-reporting-in-react.md %})
+ - [Reporting API errors in React]({% link _chapters/report-api-errors-in-react.md %})
+ - [Reporting unexpected React errors with an Error Boundary]({% link _chapters/setup-an-error-boundary-in-react.md %})
+2. [Setting up detailed error reporting in Lambda]({% link _chapters/setup-error-logging-in-serverless.md %})
+3. The debugging workflow for the following serverless errors:
+ - [Logic errors in our Lambda functions]({% link _chapters/logic-errors-in-lambda-functions.md %})
+ - [Unexpected errors in our Lambda functions]({% link _chapters/unexpected-errors-in-lambda-functions.md %})
+ - [Errors outside our Lambda functions]({% link _chapters/errors-outside-lambda-functions.md %})
+ - [Errors in API Gateway]({% link _chapters/errors-in-api-gateway.md %})
+
+This should give you a good foundation to be able to monitor your app as it goes into production. There are plenty of other great tools out there that can improve on this setup. We want to make sure we cover the basics here. Let's get started!
diff --git a/_chapters/delete-a-note.md b/_chapters/delete-a-note.md
index 57307a4650..d65d998c3e 100644
--- a/_chapters/delete-a-note.md
+++ b/_chapters/delete-a-note.md
@@ -4,21 +4,20 @@ title: Delete a Note
date: 2017-01-31 00:00:00
lang: en
description: We want users to be able to delete their note in our React.js app. To do this we are going to make a DELETE request to our serverless API backend using AWS Amplify.
-context: true
comments_id: comments-for-delete-a-note/137
ref: delete-a-note
---
The last thing we need to do on the note page is allowing users to delete their note. We have the button 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`.
+{%change%} Replace our `handleDelete` function in `src/containers/Notes.tsx`.
-``` coffee
-deleteNote() {
- return API.del("notes", `/notes/${this.props.match.params.id}`);
+```typescript
+function deleteNote() {
+ return API.del("notes", `/notes/${id}`, {});
}
-handleDelete = async event => {
+async function handleDelete(event: React.FormEvent) {
event.preventDefault();
const confirmed = window.confirm(
@@ -29,28 +28,24 @@ handleDelete = async event => {
return;
}
- this.setState({ isDeleting: true });
+ setIsDeleting(true);
try {
- await this.deleteNote();
- this.props.history.push("/");
+ await deleteNote();
+ nav("/");
} catch (e) {
- alert(e);
- this.setState({ isDeleting: false });
+ onError(e);
+ setIsDeleting(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.
+We are simply making a `DELETE` request to `/notes/:id` where we get the `id` from `useParams` hook provided by React Router. 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 deleting a note you should see it confirm your action and then delete the note.
-
+
Again, you might have noticed that we are not deleting the attachment when we are deleting a note. We are leaving that up to you to keep things simple. Check the [AWS Amplify API Docs](https://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 after we login. Let's look at how to do that next.
+Next, let’s add a settings page to our app. This is where a user will be able to pay for our service!
diff --git a/_chapters/deploy-updates.md b/_chapters/deploy-updates.md
deleted file mode 100644
index 56661eb537..0000000000
--- a/_chapters/deploy-updates.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-layout: post
-title: Deploy Updates
-date: 2017-02-12 00:00:00
-lang: en
-description: Tutorial on how to deploy updates to your React.js single page application hosted on AWS S3 and CloudFront.
-comments_id: deploy-updates/16
-ref: 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 Distributions
-
-We need to do the last step since CloudFront caches our objects in its edge locations. So to make sure that our users see the latest version, we need to tell CloudFront to invalidate it's cache in the edge locations.
-
-Let's start by making a couple of changes to our app and go through the process of deploying them.
diff --git a/_chapters/deploy-your-serverless-infrastructure.md b/_chapters/deploy-your-serverless-infrastructure.md
deleted file mode 100644
index 08699194da..0000000000
--- a/_chapters/deploy-your-serverless-infrastructure.md
+++ /dev/null
@@ -1,73 +0,0 @@
----
-layout: post
-title: Deploy Your Serverless Infrastructure
-date: 2018-03-04 00:00:00
-lang: en
-description: To deploy your Serverless Framework project along with your infrastructure to AWS, use the "serverless deploy -v" command. This will display the Stack Outputs as a part of the deployment.
-code: backend
-ref: deploy-your-serverless-infrastructure
-comments_id: deploy-your-serverless-infrastructure/167
----
-
-Now that we have all our resources configured, let's go ahead and deploy our entire infrastructure.
-
-We should mention though that our current project has all of our resources and the Lambda functions 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 splitting them up. So you might have a separate Serverless Framework project that deploys your infrastructure while a different project just deploys your Lambda functions.
-
-### Deploy Your Project
-
-Deploying our project is fairly straightforward thanks to our `serverless deploy` command. So go ahead and run this from the root of your project.
-
-``` bash
-$ serverless deploy -v
-```
-
-Your output should look something like this:
-
-``` bash
-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` option) 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 CloudFormation will only update the parts that have changed. So you can confidently run this command without worrying about it re-creating your entire infrastructure from scratch.
-
-And that's it! Our entire infrastructure is completely configured and deployed automatically.
-
-Next, we will add a new API (and Lambda function) 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!
diff --git a/_chapters/deploying-multiple-services-in-serverless.md b/_chapters/deploying-multiple-services-in-serverless.md
deleted file mode 100644
index 33a262405d..0000000000
--- a/_chapters/deploying-multiple-services-in-serverless.md
+++ /dev/null
@@ -1,107 +0,0 @@
----
-layout: post
-title: Deploying Multiple Services in Serverless
-description: To deploy multiple Serverless services that are using CloudFormation cross-stack references, we need to ensure that we deploy them in the order of their dependencies.
-date: 2018-04-02 18:00:00
-context: true
-code: mono-repo
-comments_id: deploying-multiple-services-in-serverless/410
----
-
-Over the last few chapters we have looked at how to:
-
-- [Link multiple Serverless services using CloudFormation cross-stack references]({% link _chapters/cross-stack-references-in-serverless.md %})
-- [Create our DynamoDB table as a Serverless service]({% link _chapters/dynamodb-as-a-serverless-service.md %})
-- [Create an S3 bucket as a Serverless service]({% link _chapters/s3-as-a-serverless-service.md %})
-- [Use the same API Gateway domain and resources across multiple Serverless services]({% link _chapters/api-gateway-domains-across-services.md %})
-- [Create a Serverless service for Cognito to authenticate and authorize our users]({% link _chapters/cognito-as-a-serverless-service.md %})
-
-All this is available in a [sample repo that you can deploy and test]({{ site.backend_mono_github_repo }}).
-
-Now we can finally look at how to deploy our services. The addition 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 respectively.
-
-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 automatic 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 often. You might also need to follow some strict policies across your team to make sure no haphazard changes are made to it. So by separating 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](https://seed.run) has a concept of [Deploy Phases](https://seed.run/docs/configuring-deploy-phases) to handle service dependencies.
-
-You can configure this by heading to the app settings and hitting **Manage Deploy Phases**.
-
-
-
-Here you'll notice 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 **Settings** 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 multiple [environments or stages]({% link _chapters/stages-in-serverless-framework.md %}). A good standard practice 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 multiple _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]({% link _chapters/organizing-serverless-projects.md %}).
-
-This combined way of using the multi-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 caution. While the API services can be automated (using [Seed](https://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 applications using CloudFormation cross-stack references. And the [example repo]({{ site.backend_mono_github_repo }}) gives you a clear working demonstration of the concepts we've covered. Give the above setup a try and leave us your feedback in the comments.
-
diff --git a/_chapters/deploying-through-seed.md b/_chapters/deploying-through-seed.md
deleted file mode 100644
index bea8eeb943..0000000000
--- a/_chapters/deploying-through-seed.md
+++ /dev/null
@@ -1,112 +0,0 @@
----
-layout: post
-title: Deploying Through Seed
-lang: en
-date: 2018-03-14 00:00:00
-description: We are going to trigger a deployment in Seed by pushing a commit to our Serverless project in Git. In the Seed console you can view the build logs and look at the CloudFormation output.
-context: true
-ref: deploying-through-seed
-comments_id: deploying-through-seed/177
----
-
-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** button.
-
-Let's do it through Git.
-
-Go back to your project root and run the following.
-
-``` bash
-$ npm version patch
-```
-
-This is simply updating 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.
-
-``` bash
-$ 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.
-
-
-
-Notice 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 promoting to `prod`. This ensures that if we have a tested working build, it should just work when we promote to production.
-
-You might also notice a couple of warnings that look like the following.
-
-``` bash
-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 Identity 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 notice that we are ready to promote to production.
-
-We have a manual promotion step so that you get a chance to review the changes and ensure that you are ready to push to production.
-
-Hit the **Promote** button.
-
-
-
-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 production. It's a great way to compare the infrastructure changes that are being promoted.
-
-
-
-Scroll down and hit **Promote to Production**.
-
-
-
-You'll notice 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 action. It should take a second to deploy to production. And just like before, make a note of the following.
-
-- Region: `region`
-- Cognito User Pool Id: `UserPoolId`
-- Cognito App Client Id: `UserPoolClientId`
-- Cognito Identity Pool Id: `IdentityPoolId`
-- S3 File Uploads Bucket: `AttachmentsBucketName`
-- API Gateway URL: `ServiceEndpoint`
-
-
-
-Next let's configure our serverless API with a custom domain.
diff --git a/_chapters/deploying-through-the-sst-console.md b/_chapters/deploying-through-the-sst-console.md
new file mode 100644
index 0000000000..6dd903ac0f
--- /dev/null
+++ b/_chapters/deploying-through-the-sst-console.md
@@ -0,0 +1,47 @@
+---
+layout: post
+title: Deploying Through the SST Console
+lang: en
+date: 2024-07-24 00:00:00
+redirect_from: /chapters/deploying-through-seed.html
+description: We are going to git push to deploy our app to production with the SST Console.
+ref: deploying-through-the-sst-console
+comments_id: deploying-through-the-sst-console/2958
+---
+
+Now, we are ready to _git push_ to deploy our app to production with the SST Console. If you recall from the [previous chapter]({% link _chapters/setting-up-the-sst-console.md %}), we configured it to auto-deploy the `production` branch.
+
+Let's do that by first creating a production branch.
+
+{%change%} Run the following in the **project root**.
+
+```bash
+$ git checkout -b production
+```
+
+{%change%} Now let's push this to GitHub.
+
+```bash
+$ git push --set-upstream origin production
+$ git push
+```
+
+Now if you head into the **Autodeploy** tab for your app in the SST Console, you'll notice a new deployment in progress.
+
+
+
+Once the deploy is complete, you'll notice the outputs at the bottom.
+
+
+
+### Test Our App in Production
+
+Let's check out our app in production.
+
+
+
+To give it a quick test, sign up for a new account and create a note. You can also test updating and removing a note. And also test out the billing page.
+
+**Congrats! Your app is now live!**
+
+Let's wrap things up next.
diff --git a/_chapters/display-a-note.md b/_chapters/display-a-note.md
index d7d2d4f968..e5177afb35 100644
--- a/_chapters/display-a-note.md
+++ b/_chapters/display-a-note.md
@@ -3,13 +3,12 @@ layout: post
title: Display a Note
date: 2017-01-28 00:00:00
lang: en
-description: We want to create a page in our React.js app that will display a user’s note based on the id in the URL. We are going to use the React Router v4 Route component’s URL parameters to get the id. Using this id we are going to request our note from the serverless backend API. And use AWS Amplify's Storage.vault.get() method to get a secure link to download our attachment.
-context: true
+description: We want to create a page in our React.js app that will display a user’s note based on the id in the URL. We are going to use the React Router v6 Route component’s URL parameters to get the id. Using this id we are going to request our note from the serverless backend API. And use AWS Amplify's Storage.vault.get() method to get a secure link to download our attachment.
comments_id: display-a-note/112
ref: display-a-note
---
-Now that we have a listing of all the notes, let's create a page that displays a note and let's the user edit it.
+Now that we have a listing of all the notes, let's create a page that displays a note and lets 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.
@@ -17,85 +16,79 @@ The first thing we are going to need to do is load the note when our container l
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]({% link _chapters/add-the-session-to-the-state.md %}) chapter.
+{%change%} Add the following line to `src/Routes.tsx` **below** our `/notes/new` route.
-``` coffee
-
+```tsx
+} />
```
This is important because we are going to be pattern 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 pattern matching one.
-And include our component in the header.
+{%change%} And include our component in the header.
-``` javascript
-import Notes from "./containers/Notes";
+```tsx
+import Notes from "./containers/Notes.tsx";
```
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.
+{%change%} Create a new file `src/containers/Notes.tsx` and add the following.
-``` coffee
-import React, { Component } from "react";
+```tsx
+import React, { useRef, useState, useEffect } from "react";
+import { useParams, useNavigate } from "react-router-dom";
import { API, Storage } from "aws-amplify";
+import { onError } from "../lib/errorLib";
+
+export default function Notes() {
+ const file = useRef(null)
+ const { id } = useParams();
+ const nav = useNavigate();
+ const [note, setNote] = useState(null);
+ const [content, setContent] = useState("");
+
+ useEffect(() => {
+ function loadNote() {
+ return API.get("notes", `/notes/${id}`, {});
+ }
-export default class Notes extends Component {
- constructor(props) {
- super(props);
-
- this.file = null;
-
- this.state = {
- note: null,
- content: "",
- attachmentURL: null
- };
- }
+ async function onLoad() {
+ try {
+ const note = await loadNote();
+ const { content, attachment } = note;
- async componentDidMount() {
- try {
- let attachmentURL;
- const note = await this.getNote();
- const { content, attachment } = note;
+ if (attachment) {
+ note.attachmentURL = await Storage.vault.get(attachment);
+ }
- if (attachment) {
- attachmentURL = await Storage.vault.get(attachment);
+ setContent(content);
+ setNote(note);
+ } catch (e) {
+ onError(e);
}
-
- this.setState({
- note,
- content,
- attachmentURL
- });
- } catch (e) {
- alert(e);
}
- }
- getNote() {
- return API.get("notes", `/notes/${this.props.match.params.id}`);
- }
+ onLoad();
+ }, [id]);
- render() {
- return ;
- }
+ return ;
}
```
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 automatically passed to us by React-Router in `this.props.match.params.id`. The keyword `id` is a part of the pattern matching in our route (`/notes/:id`).
+1. We are using the `useEffect` Hook to load the note when our component first loads. We then save it to the state. We get the `id` of our note from the URL using `useParams` hook that comes with React Router. The `id` is a part of the pattern matching in our route (`/notes/:id`).
-2. If there is an attachment, 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`.
+2. If there is an attachment, we use the key to get a secure link to the file we uploaded to S3. We then store this in the new note object as `note.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 notice that the page renders an empty container.
-
+
Next up, we are going to render the note we just loaded.
diff --git a/_chapters/dynamodb-as-a-serverless-service.md b/_chapters/dynamodb-as-a-serverless-service.md
deleted file mode 100644
index aad225037f..0000000000
--- a/_chapters/dynamodb-as-a-serverless-service.md
+++ /dev/null
@@ -1,80 +0,0 @@
----
-layout: post
-title: DynamoDB as a Serverless Service
-description: To use CloudFormation cross-stack references for DynamoDB in Serverless we need to "Export" the table name using the "Ref" and the ARN of the table using "Fn::GetAtt".
-date: 2018-04-02 14:00:00
-context: true
-code: mono-repo
-comments_id: dynamodb-as-a-serverless-service/406
----
-
-While creating a Serverless application with multiple services, you might want to split the DynamoDB portion out separately. This can be useful because you are probably not going to be making changes to this very frequently. Also, if you have multiple 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 application API services.
-
-In the [example repo]({{ site.backend_mono_github_repo }}), you'll notice that we have a `database` service in the `services/` directory. And the `serverless.yml` in this service helps us manage our DynamoDB table.
-
-``` yml
-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]({% link _chapters/configure-dynamodb-in-serverless.md %}), the `Resources:` section should seem familiar. It is creating the Notes table that we use in our [note taking application]({{ site.backend_github_repo }}). The key addition here in regards to the cross-stack references is in the `Outputs:` section. Let's go over them quickly.
-
-1. We are exporting one value here. The `NotesTableArn` is the [ARN]({% link _chapters/what-is-an-arn.md %}) of the DynamoDB table that we are creating. 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 entire application to be easily replicable across multiple stages. If we don't include the stage name the exports will thrash when we deploy to multiple stages.
-
-3. The names of the exported values is `${self:custom.stage}-NotesTableArn`.
-
-4. We get the table ARN by using the `Fn::GetAtt` CloudFormation function. This function takes a reference from the current service and the attribute we need. The reference in this case is `NotesTable`. You'll notice that the table we created in the `Resources:` section is created using `NotesTable` as the name.
-
-When we deploy this service we'll notice 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.
diff --git a/_chapters/errors-in-api-gateway.md b/_chapters/errors-in-api-gateway.md
new file mode 100644
index 0000000000..5ac105f3f5
--- /dev/null
+++ b/_chapters/errors-in-api-gateway.md
@@ -0,0 +1,150 @@
+---
+layout: post
+title: Errors in API Gateway
+date: 2020-04-03 00:00:00
+lang: en
+description: In this chapter we'll look at how to debug errors that happen only in API Gateway in your serverless app. These errors are only logged to your API Gateway access logs, and not your Lambda logs in CloudWatch.
+comments_id: errors-in-api-gateway/1728
+ref: errors-in-api-gateway
+---
+
+In the past few chapters we looked at how to debug errors in our Lambda functions. However, our APIs can fail before our Lambda function has been invoked. In these cases, we won't be able to debug using the Lambda logs. Since there won't be any requests made to our Lambda functions.
+
+The two common causes for these errors are:
+
+1. Invalid API path
+2. Invalid API method
+
+Let's look at how to debug these.
+
+### Invalid API Path
+
+Head over to the `frontend/` directory in your project.
+
+{%change%} Open `src/containers/Home.tsx`, and replace the `loadNotes()` function with:
+
+```tsx
+ function loadNotes() {
+ return API.get("notes", "/invalid_path", {});
+}
+```
+
+{%change%} Let's commit this and push it.
+
+```bash
+$ git add .
+$ git commit -m "Adding faulty paths"
+$ git push
+```
+
+Head over to your Seed dashboard and deploy it.
+
+Then in your notes app, load the home page. You'll notice the page fails with an error alert saying `Network Alert`.
+
+
+
+On Sentry, the error will show that a `GET` request failed with status code `0`.
+
+
+
+What happens here is that:
+
+- The browser first makes an `OPTIONS` request to `/invalid_path`.
+- API Gateway returns a `403` response.
+- The browser throws an error and does not continue to make the `GET` request.
+
+This means that our Lambda function was not invoked. And in the browser it fails as a CORS error.
+
+
+
+### Invalid API method
+
+Now let's look at what happens when we use an invalid HTTP method for our API requests. Instead of a `GET` request we are going to make a `PUT` request.
+
+{%change%} In `src/containers/Home.tsx` replace the `loadNotes()` function with:
+
+```tsx
+function loadNotes() {
+ return API.put("notes", "/notes", {});
+}
+```
+
+{%change%} Let's push our code.
+
+```bash
+$ git add .
+$ git commit -m "Adding invalid method"
+$ git push
+```
+
+Head over to your Seed dashboard and deploy it.
+
+Our notes app should fail to load the home page.
+
+
+
+You should see a similar Network Error as the one above in Sentry. Select the error and you will see that the `PUT` request failed with `0` status code.
+
+
+
+Here's what's going on behind the scenes:
+
+- The browser first makes an `OPTIONS` request to `/notes`.
+- API Gateway returns a successful `200` response with the HTTP methods allowed for the path.
+- The allowed HTTP methods are `GET` and `POST`. This is because we defined:
+ - `GET` request on `/notes` to list all the notes
+ - `POST` request on `/notes` to create a new note
+- The browser reports the error because the request method `PUT` is not allowed.
+
+Similar as to the case above, our Lambda function was not invoked. And in the browser it fails as a CORS error.
+
+
+
+With that we've covered all the major types of serverless errors and how to debug them.
+
+### Rollback the Changes
+
+{%change%} Let's revert all the faulty code that we created.
+
+```bash
+$ git checkout main
+$ git branch -D debug
+```
+
+And rollback the prod build in Seed.
+
+Head to the **Activity** tab in the Seed dashboard. Then click on **prod** over on the right. This shows us all the deployments made to our prod stage.
+
+
+
+Scroll down to the last deployment from the `main` branch, past all the ones made from the `debug` branch. Hit **Rollback**.
+
+
+
+This will rollback our app to the state it was in before we deployed all of our faulty code.
+
+Now you are all set to go live with your brand new full-stack serverless app!
+
+Let's wrap things up next.
diff --git a/_chapters/errors-outside-lambda-functions.md b/_chapters/errors-outside-lambda-functions.md
new file mode 100644
index 0000000000..e541dfa8b4
--- /dev/null
+++ b/_chapters/errors-outside-lambda-functions.md
@@ -0,0 +1,183 @@
+---
+layout: post
+title: Errors Outside Lambda Functions
+date: 2020-04-06 00:00:00
+lang: en
+description: In this chapter we look at how to debug errors that happen outside your Lambda function handler code. We use the CloudWatch logs through Seed to help us debug it.
+comments_id: errors-outside-lambda-functions/1729
+ref: errors-outside-lambda-functions
+---
+
+We've covered debugging [errors in our code]({% link _chapters/logic-errors-in-lambda-functions.md %}) and [unexpected errors]({% link _chapters/unexpected-errors-in-lambda-functions.md %}) in Lambda functions. Now let's look at how to debug errors that happen outside our Lambda functions.
+
+### Initialization Errors
+
+Lambda functions could fail not because of an error inside your handler code, but because of an error outside it. In this case, your Lambda function won't be invoked. Let's add some faulty code outside our handler function.
+
+{%change%} Replace the `main` function in `packages/functions/src/get.ts` with the following.
+
+```typescript
+// Some faulty code
+dynamoDb.notExist();
+
+export const main = handler(async (event: APIGatewayProxyEvent) => {
+ let path_id
+
+ if (!event.pathParameters || !event.pathParameters.id || event.pathParameters.id.length == 0) {
+ throw new Error("Please provide the 'id' parameter.");
+ } else {
+ path_id = event.pathParameters.id
+ }
+
+ const params = {
+ TableName: Table.Notes.tableName,
+ // 'Key' defines the partition key and sort key of
+ // the item to be retrieved
+ Key: {
+ userId: event.requestContext.authorizer?.iam.cognitoIdentity.identityId,
+ noteId: path_id, // The id of the note from the path
+ },
+ };
+
+ const result = await dynamoDb.get(params);
+ if (!result.Item) {
+ throw new Error("Item not found.");
+ }
+
+ // Return the retrieved item
+ return result.Item;
+});
+
+```
+
+{%change%} Commit this code.
+
+```bash
+$ git add .
+$ git commit -m "Adding an init error"
+$ git push
+```
+
+Head over to your Seed dashboard, and deploy it.
+
+Now if you select a note in your notes app, you'll notice that it fails with an error.
+
+
+
+You should see an error in Sentry. And if you head over to the Issues in Seed and click on the new error.
+
+
+
+You'll notice the error message `dynamodb_lib.notExist is not a function`.
+
+Note that, you might see there are 3 events for this error. This is because the Lambda runtime prints out the error message multiple times.
+
+### Handler Function Errors
+
+Another error that can happen outside a Lambda function is when the handler has been misnamed.
+
+{%change%} Replace the `main` function in `packages/functions/src/get.ts` with the following.
+
+```typescript
+// Wrong handler function name
+export const main2 = handler(async (event: APIGatewayProxyEvent) => {
+ let path_id
+
+ if (!event.pathParameters || !event.pathParameters.id || event.pathParameters.id.length == 0) {
+ throw new Error("Please provide the 'id' parameter.");
+ } else {
+ path_id = event.pathParameters.id
+ }
+
+ const params = {
+ TableName: Table.Notes.tableName,
+ // 'Key' defines the partition key and sort key of
+ // the item to be retrieved
+ Key: {
+ userId: event.requestContext.authorizer?.iam.cognitoIdentity.identityId,
+ noteId: path_id, // The id of the note from the path
+ },
+ };
+
+ const result = await dynamoDb.get(params);
+ if (!result.Item) {
+ throw new Error("Item not found.");
+ }
+
+ // Return the retrieved item
+ return result.Item;
+});
+
+```
+
+{%change%} Let's commit this.
+
+```bash
+$ git add .
+$ git commit -m "Adding a handler error"
+$ git push
+```
+
+Head over to your Seed dashboard and deploy it. Then, in your notes app, try and load a note. It should fail with an error alert.
+
+Just as before, you'll see the error in Sentry. Head over to the new error in Seed.
+
+
+
+You should see the error `Runtime.HandlerNotFound`, along with message `get.main is undefined or not exported`.
+
+And that about covers the main Lambda function errors. So the next time you see one of the above error messages, you'll know what's going on.
+
+### Remove the Faulty Code
+
+Let's cleanup all the faulty code.
+
+{%change%} Replace `packages/functions/src/get.ts` with the following.
+
+```typescript
+import handler from "@notes/core/handler";
+import { APIGatewayProxyEvent } from 'aws-lambda';
+import { Table } from "sst/node/table";
+import dynamoDb from "@notes/core/dynamodb";
+
+export const main = handler(async (event: APIGatewayProxyEvent) => {
+ let path_id
+
+ if (!event.pathParameters || !event.pathParameters.id || event.pathParameters.id.length == 0) {
+ throw new Error("Please provide the 'id' parameter.");
+ } else {
+ path_id = event.pathParameters.id
+ }
+
+ const params = {
+ TableName: Table.Notes.tableName,
+ // 'Key' defines the partition key and sort key of
+ // the item to be retrieved
+ Key: {
+ userId: event.requestContext.authorizer?.iam.cognitoIdentity.identityId,
+ noteId: path_id, // The id of the note from the path
+ },
+ };
+
+ const result = await dynamoDb.get(params);
+ if (!result.Item) {
+ throw new Error("Item not found.");
+ }
+
+ // Return the retrieved item
+ return result.Item;
+});
+
+```
+
+Commit and push the code.
+
+```bash
+$ git add .
+$ git commit -m "Reverting faulty code"
+$ git push
+```
+
+Head over to your Seed dashboard and deploy it.
+
+Now let's move on to debugging API Gateway errors.
diff --git a/_chapters/es/add-support-for-es6-and-typescript.md b/_chapters/es/add-support-for-es6-and-typescript.md
new file mode 100644
index 0000000000..4c03666d1e
--- /dev/null
+++ b/_chapters/es/add-support-for-es6-and-typescript.md
@@ -0,0 +1,108 @@
+---
+layout: post
+title: Agregar soporte para ES6 y TypeScript
+date: 2016-12-29 12:00:00
+lang: es
+ref: add-support-for-es6-and-typescript
+description: AWS Lambda soporta Node.js v10.x y v12.x. Sin embargo, para usar características ES6 o de TypeScript en nuestro proyecto Serverless Framework necesitamos usar Babel, Webpack 5 y una gran variedad de otros paquetes. Podemos lograr esto usando el plugin serverless-bundle en nuestro proyecto.
+comments_id: add-support-for-es6-es7-javascript/128
+---
+
+AWS Lambda soporta Node.js v10.x y v12.x. Sin embargo, la sintaxis admitida es un poco diferente en comparación con el sabor más avanzado de JavaScript ECMAScript que admite nuestra aplicación frontend en React. Tiene sentido utilizar funciones ES similares en ambas partes del proyecto – específicamente, confiaremos en las funciones ES de importar/exportar de nuestro controlador (`handler`).
+
+Adicionalmente, nuestra aplicación frontend React soporta automáticamente TypeScript vía [Create React App](https://create-react-app.dev). No vamos a usar TypeScript en esta guía, tiene sentido tener una configuración similar para nuestras funciones backend en Lambda. Así podrás usarlas en tus proyectos futuros.
+
+Para lograr esto normalmente necesitamos instalar [Babel](https://babeljs.io), [TypeScript](https://www.typescriptlang.org), [Webpack](https://webpack.js.org) y una larga lista de otros paquetes. Esto puede significar extra configuracion y complejidad a tu proyecto.
+
+Para ayudar en este proceso hemos creado, [`serverless-bundle`](https://github.com/AnomalyInnovations/serverless-bundle). Es un plugin de Serverless Framework que tiene algunas ventajas clave:
+
+- Una sola dependencia
+- Soporta ES6 y TypeScript
+- Genera paquetes optimizados
+- Analiza (`Linting`) funciones Lambda usando [ESLint](https://eslint.org)
+- Soporta conversiones (`transpiling`) de pruebas unitarias con [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest)
+- Soporta mapeos de código fuente (`Source map`) para mensajes de error apropiados
+
+Este se incluye automáticamente en el proyecto inicial que usamos en el capítulo anterior — [`serverless-nodejs-starter`]({% link _archives/serverless-nodejs-starter.md %}). Para TypeScript, tenemos un iniciador tambien — [`serverless-typescript-starter`](https://github.com/AnomalyInnovations/serverless-typescript-starter).
+
+Sin embargo, si estas buscando agregar soporte ES6 y TypeScript a tus proyectos Serverless Framework existentes, puedes hacerlo instalando [serverless-bundle](https://github.com/AnomalyInnovations/serverless-bundle):
+
+``` bash
+$ npm install --save-dev serverless-bundle
+```
+
+E incluyéndolo en tu archivo `serverless.yml` usando:
+
+``` yml
+plugins:
+ - serverless-bundle
+```
+
+Para ejecutar tus pruebas, agrega esto a tu archivo `package.json`.
+
+``` json
+"scripts": {
+ "test": "serverless-bundle test"
+}
+```
+
+### Funciones ES6 Lambda
+
+Revisemos las funciones Lambda que vienen con nuestro proyecto inicial.
+
+Tu archivo `handler.js` debería verse así.
+
+``` js
+export const hello = async (event, context) => {
+ return {
+ statusCode: 200,
+ body: JSON.stringify({
+ message: `Vamos Serverless v2.0! ${(await message({ time: 1, copy: 'Tu función fue ejecutada exitosamente!'}))}`,
+ }),
+ };
+};
+
+const message = ({ time, ...rest }) => new Promise((resolve, reject) =>
+ setTimeout(() => {
+ resolve(`${rest.copy} (con demora)`);
+ }, time * 1000)
+);
+```
+
+Ejecutemos esto. En tu proyecto raíz ejecuta:
+
+``` bash
+$ serverless invoke local --function hello
+```
+
+Deberías ver algo como esto en tu terminal.
+
+``` bash
+{
+ "statusCode": 200,
+ "body": "{\"message\":\"Vamos Serverless v2.0! Tu función fue ejecutada exitosamente! (con demora)\"}"
+}
+```
+
+En el comando anterior estamos pidiendo al Serverless Framework invocar (localmente) una función Lambda llamada `hello`. Esto a su vez ejecutará el método `hello` que estamos exportando en nuestro archivo `handler.js`.
+
+Aquí estamos invocando directamente a la función Lambda . Aunque una vez desplegado, invocaremos a esta función por medio del API endpoint `/hello` (como hemos [hablado en el último capítulo]({% link _archives/setup-the-serverless-framework.md %})).
+
+En este momento estamos casi listos para desplegar nuestra función Lambda y nuestro API . Pero antes vamos a revisar rápidamente una de las otras cosas que se han configurado por nosotros en este proyecto inicial.
+
+### Paquetes optimizados
+
+Por default el Serverless Framework crea un simple paquete para todas tus funciones Lambda. Esto significa que cuando una función Lambda es invocada, será cargado todo el código en tu aplicación. Incluyendo todas las otras funciones Lambda. Esto afecta negativamente el rendimiento conforme tu aplicación crece en tamaño. Entre más grandes sean tus paquetes de funciones Lambda, más tiempo tardarán en ejecutarse [arranque en frío]({% link _chapters/what-is-serverless.md %}#cold-starts).
+
+Para evitar esto y asegurarse que el Serverless Framework esta empaquetando nuestras funciones individualmente, agreguemos a nuestro archivo `serverless.yml`.
+
+``` yml
+package:
+ individually: true
+```
+
+Esto debería estar por defecto en nuestro proyecto inicial.
+
+Ten en cuenta que con la opción anterior habilitada, serverless-bundle puede usar Webpack para generar paquetes optimizados usando un [algoritmo tree shaking](https://webpack.js.org/guides/tree-shaking/). Solo incluirá el código necesario para ejecutar su función Lambda y nada más!
+
+Ahora estamos listos para desplegar nuestro API backend.
diff --git a/_chapters/es/configure-the-aws-cli.md b/_chapters/es/configure-the-aws-cli.md
index 38fdee4809..2b6a8f8fac 100644
--- a/_chapters/es/configure-the-aws-cli.md
+++ b/_chapters/es/configure-the-aws-cli.md
@@ -18,7 +18,7 @@ AWS CLI necesita Python 2 versión 2.6.5+ o Python 3 versión 3.3+ y [Pip](https
- [Instalar Python](https://www.python.org/downloads/)
- [Instalar Pip](https://pip.pypa.io/en/stable/installing/)
-Ahora utilizando Pip puedes instalar la CLI de AWS (en Linux, macOS o Unix) ejecutando:
+{%change%} Ahora utilizando Pip puedes instalar la CLI de AWS (en Linux, macOS o Unix) ejecutando:
``` bash
$ sudo pip install awscli
@@ -32,7 +32,7 @@ $ brew install awscli
Si tienes problemas para instalar la CLI de AWS o necesitas instrucciones de instalación en Windows, consulta las [instrucciones de instalación completas](http://docs.aws.amazon.com/cli/latest/userguide/installing.html).
-### Agregua tu clave de acceso a AWS CLI
+### Agrega tu clave de acceso a AWS CLI
Ahora debemos decirle a AWS CLI que use las claves de acceso del capítulo anterior.
@@ -41,7 +41,7 @@ Debería verse algo como esto:
- ID de clave de acceso **AKIAIOSFODNN7EXAMPLE**
- Clave de acceso secreta **wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY**
-Simplemente ejecuta lo siguiente con tu ID de clave secreta y tu clave de acceso.
+{%change%} Simplemente ejecuta lo siguiente con tu ID de clave secreta y tu clave de acceso.
``` bash
$ aws configure
diff --git a/_chapters/es/create-a-dynamodb-table.md b/_chapters/es/create-a-dynamodb-table.md
index e63f2abb6f..24c300a5ff 100644
--- a/_chapters/es/create-a-dynamodb-table.md
+++ b/_chapters/es/create-a-dynamodb-table.md
@@ -54,7 +54,7 @@ Se ha creado la tabla `notes`. Si te encuentras atascado con el mensaje **La tab

-También es una buena idea configurar copias de seguridad para tu tabla de DynamoDB, especialmente si planeas usarla en producción. Cubriremos esto en un capítulo adicional, [Copias de seguridad en DynamoDB]({% link _chapters/backups-in-dynamodb.md %}).
+También es una buena idea configurar copias de seguridad para tu tabla de DynamoDB, especialmente si planeas usarla en producción. Cubriremos esto en un capítulo adicional, [Copias de seguridad en DynamoDB]({% link _archives/backups-in-dynamodb.md %}).
A continuación, configuraremos un bucket S3 para manejar las carga de archivos.
diff --git a/_chapters/es/create-an-iam-user.md b/_chapters/es/create-an-iam-user.md
index 80df3fb1bf..8ede9451c4 100644
--- a/_chapters/es/create-an-iam-user.md
+++ b/_chapters/es/create-an-iam-user.md
@@ -39,11 +39,11 @@ Selecciona **Añadir directamente las políticas existentes**.
Busca **AdministratorAccess** y selecciona la política, luego selecciona **Siguiente: Etiquetas**.
-Podemos proporcionar una política más detallada aquí y lo cubriremos más adelante en el capitulo [Personalizar la política de Serverless IAM]({% link _chapters/customize-the-serverless-iam-policy.md %}). Pero por ahora, continuemos con esto.
+Podemos proporcionar una política más detallada aquí y lo cubriremos más adelante en el capitulo [Personalizar la política de Serverless IAM]({% link _archives/customize-the-serverless-iam-policy.md %}). Pero por ahora, continuemos con esto.

-Este paso es opcional, aquí puedes agregar las etiquetas que creas necesarias al nuevo usuario de IAM, para organizar, seguir o controlar el acceso de los usuarios, por ejemplo, la etiqueta _Application_ tiene el valor _Serverless Stack Guide_ para saber que este usuario fue creado para esta aplicación específica. Luego selecciona **Siguiente: Revisar**.
+Este paso es opcional, aquí puedes agregar las etiquetas que creas necesarias al nuevo usuario de IAM, para organizar, seguir o controlar el acceso de los usuarios, por ejemplo, la etiqueta _Application_ tiene el valor _SST Guide_ para saber que este usuario fue creado para esta aplicación específica. Luego selecciona **Siguiente: Revisar**.

diff --git a/_chapters/es/create-an-s3-bucket-for-file-uploads.md b/_chapters/es/create-an-s3-bucket-for-file-uploads.md
new file mode 100644
index 0000000000..aae580bf61
--- /dev/null
+++ b/_chapters/es/create-an-s3-bucket-for-file-uploads.md
@@ -0,0 +1,43 @@
+---
+layout: post
+title: Crear un repositorio S3 Bucket para almacenar archivos
+date: 2016-12-27 00:00:00
+lang: es
+ref: create-an-s3-bucket-for-file-uploads
+description: Para permitir a los usuarios que puedan subir sus archivos a nuestra aplicación serverless vamos a usar Amazon S3 (Simple Storage Service). S3 te permite guardar archivos y organizarlos en repositorios ó buckets.
+redirect_from: /chapters/create-a-s3-bucket-for-file-uploads.html
+comments_id: create-an-s3-bucket-for-file-uploads/150
+---
+
+Ahora que tenemos nuestra tabla en la base de datos lista vamos a configurar lo necesario para manejar la carga de los archivos. Esto es necesario porque cada nota puede tener un archivo cargado como adjunto.
+
+[Amazon S3](https://aws.amazon.com/s3/) (Simple Storage Service) ofrece un servicio de almacenamiento a través de interfaces de servicios web como REST. Puedes almacenar cualquier objeto en S3 incluyendo imágenes, videos, archivos, etc. Los objectos son organizados en buckets y pueden ser identificados dentro de cada bucket con una única llave asignada de usuario.
+
+En este capítulo vamos a crear un bucket S3 el cual va a ser usado para guardar archivos cargados desde nuestra aplicación de notas.
+
+### Crear Bucket
+
+Primero, ingresa a tu [Consola AWS](https://console.aws.amazon.com) y selecciona **S3** de la lista de servicios.
+
+
+
+Selecciona **Crear bucket**.
+
+
+
+Escribe el nombre del bucket y selecciona una región. Después selecciona **Crear**.
+
+- **Nombres de Bucket** son globalmente únicos, lo cual significa que no puedes elegir el mismo nombre que en este tutorial.
+- **Región** es la región geográfica fisica donde los archivos son almacenados. Vamos a usar **US East (N. Virginia)** para esta guía.
+
+Anota el nombre y la región ya que lo usaremos más tarde en esta guía.
+
+
+
+Luego ve hacia la parte más abajo y dá click en **Crear bucket**.
+
+
+
+Esto deberá crear tu nuevo bucket S3.
+
+Ahora, antes de comenzar a trabajar en nuestro Serverless API backend, tengamos una idea rápida de como encajan todos nuestros recursos.
diff --git a/_chapters/es/deploy-your-hello-world-api.md b/_chapters/es/deploy-your-hello-world-api.md
new file mode 100644
index 0000000000..adbf8c48ab
--- /dev/null
+++ b/_chapters/es/deploy-your-hello-world-api.md
@@ -0,0 +1,54 @@
+---
+layout: post
+title: Desplegando tu API Hola Mundo
+date: 2020-10-16 00:00:00
+lang: es
+ref: deploy-your-first-serverless-api
+description: En este capítulo vamos a estar desplegando nuestro primer API Serverless Hola Mundo. Vamos a usar el comando `serverless deploy` para desplegarlo a AWS.
+comments_id: deploy-your-hello-world-api/2173
+---
+
+Hasta ahora hemos configurado nuestra cuenta AWS y nuestro cliente AWS CLI. Tambien hemos creado nuestra aplicación Serverless Framework. Una gran ventaja de trabajar con Serverless es que no existe ninguna infraestructura o servidores que instalar o configurar. Solo debes desplegar tu aplicación directamente y estará lista para servir a millones de usuarios inmediatamente.
+
+Hagamos un pequeño despliegue para ver como funciona.
+
+{%change%} En la raíz de tu proyecto executa lo siguiente.
+
+``` bash
+$ serverless deploy
+```
+
+La primera vez que tu aplicación Serverless es desplegada se crea un repositorio (`bucket`) S3 (para guardar tu código de la funcion Lambda), Lambda, API Gateway, y algunos otros recursos. Esto puede tomar un minuto o dos.
+
+Una vez completado, deberías ver algo como esto:
+
+``` bash
+Service Information
+service: notes-api
+stage: prod
+region: us-east-1
+stack: notes-api-prod
+resources: 11
+api keys:
+ None
+endpoints:
+ GET - https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/hello
+functions:
+ hello: notes-api-prod-hello
+layers:
+ None
+```
+
+Ten en cuenta que hemos creado un nuevo endpoint GET. En nuestro caso, este apunta hacia — [https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/hello](https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod/hello)
+
+Si te dirijes a esa URL, deberías ver algo como esto:
+
+``` bash
+{"message":"Vamos Serverless v2.0! Tu función fue ejecutada exitosamente! (con demora)"}
+```
+
+Recordarás que es la misma salida que hemos recibido cuando invocamos a nuestra función Lambda localmente en el último capítulo. En este caso estamos invocando a la función mediante el API endpoint `/hello`.
+
+Ahora tenemos un API endpoint en Serverless. Solo pagarás por petición a este endpoint y este escalará automáticamente. Este es un gran primer paso!
+
+Ahora estamos listos para escribir nuestro código backend. Pero antes de eso, vamos a crear un repositorio GitHub para almacenar nuestro codigo.
diff --git a/_chapters/es/how-to-get-help.md b/_chapters/es/how-to-get-help.md
index f6e9aad7bf..70e5372d5d 100644
--- a/_chapters/es/how-to-get-help.md
+++ b/_chapters/es/how-to-get-help.md
@@ -12,7 +12,7 @@ En caso de que tengas problemas con un determinado paso, queremos asegurarnos de
- Usamos los foros de [Discourse]({{site.forum_url}}) como sistema de comentarios y hemos ayudado a resolver algunos problemas antes. Así que asegúrate de revisar los comentarios debajo de cada capítulo para ver si alguien más se ha encontrado con el mismo problema que tú.
- Publica en los comentarios del capítulo específico, detallando tu problema y uno de nosotros responderá.
-
+
Esta guía completa está alojada en [GitHub]({{site.github_repo}}). Así que si encuentras un error siempre puedes:
diff --git a/_chapters/es/initialize-the-backend-repo.md b/_chapters/es/initialize-the-backend-repo.md
new file mode 100644
index 0000000000..f1ad0dbd0e
--- /dev/null
+++ b/_chapters/es/initialize-the-backend-repo.md
@@ -0,0 +1,72 @@
+---
+layout: post
+title: Inicializando el repositorio backend
+date: 2016-12-29 18:00:00
+lang: es
+description: Para esta guia vamos a enviar nuestra aplicación Serverless Framework a un repositorio Git. Hacemos esto para, más tarde, poder automatizar nuestros despliegues con tan solo enviarlo (`pushing`) a Git.
+code: backend
+ref: initialize-the-backend-repo
+comments_id: initialize-the-backend-repo/159
+---
+
+Antes de comenzar a trabajar en nuestra app, vamos a crear un repositorio Git para este proyecto. Es una gran forma de guardar nuestro código y usaremos este repositorio más tarde para automatizar el proceso de despliegue de nuestra aplicación.
+
+### Creando un nuevo repositorio GitHub
+
+Vayamos a [GitHub](https://github.com). Asegurate que has ingresado y da click en **Nuevo repositorio** (`**New repository**`).
+
+
+
+Nombra tu repositorio, en nuestro caso lo hemos llamado `serverless-stack-api`. Luego da click en **Crear repositorio** (`**Create repository**`).
+
+
+
+Una vez que tu repositorio se ha creado, copia la URL del repositorio. Lo necesitaremos a continuación.
+
+
+
+En nuestro caso la URL es:
+
+``` txt
+https://github.com/jayair/serverless-stack-api.git
+```
+
+### Inicializando tu nuevo repositorio
+
+{%change%} Ahora regresa a tu proyecto y usa el siguiente comando para inicializar tu nuevo repositorio.
+
+``` bash
+$ git init
+```
+
+{%change%} Agrega los archivos existentes.
+
+``` bash
+$ git add .
+```
+
+{%change%} Crea tu primer commit.
+
+``` bash
+$ git commit -m "Mi primer commit"
+```
+
+{%change%} Únelo al repositorio que acabas de crear en GitHub.
+
+``` bash
+$ git remote add origin REPO_URL
+```
+
+Aquí `REPO_URL` es la URL que copiamos de GitHub en los pasos anteriores. Puedes verificar que ha sido configurado correctamente haciendo lo siguiente.
+
+``` bash
+$ git remote -v
+```
+
+{%change%} Finalmente, vamos a enviar (`push`) nuestro primer commit a GitHub usando:
+
+``` bash
+$ git push -u origin master
+```
+
+Ahora estamos listos para construir nuestro backend!
diff --git a/_chapters/es/review-our-app-architecture.md b/_chapters/es/review-our-app-architecture.md
new file mode 100644
index 0000000000..b84a30cbfc
--- /dev/null
+++ b/_chapters/es/review-our-app-architecture.md
@@ -0,0 +1,38 @@
+---
+layout: post
+title: Revisar la arquitectura de nuestra aplicación
+date: 2020-10-28 00:00:00
+lang: es
+ref: review-our-app-architecture
+description: En este capítulo vamos a hacer un pequeño repaso de nuestra API Serverless que estamos a punto de construir. Vamos a estar usando la tabla en DynamoDB y el bucket S3 que creamos anteriormente.
+comments_id: review-our-app-architecture/2178
+---
+
+Hasta ahora hemos [desplegado nuestro API Hola Mundo]({% link _archives/deploy-your-hello-world-api.md %}), [creado una base de datos (DynamoDB)]({% link _archives/create-a-dynamodb-table.md %}), y [creado un bucket S3 para la carga de nuestros archivos]({% link _archives/create-an-s3-bucket-for-file-uploads.md %}). Estamos listos para comenzar a trabajar en nuestra API backend pero tengamos una rápida idea de como encaja todo lo antes mencionado.
+
+### Arquitectura del API Hola Mundo
+
+Aqui lo que hemos construido hasta ahora en nuestra API Hola Mundo.
+
+
+
+API Gateway controla el `https://0f7jby961h.execute-api.us-east-1.amazonaws.com/prod` endpoint por nosotros. Y cualquier petición GET hecha a `/hello`, será enviada a la función Lambda `hello.js`.
+
+### Notas de la arquitectura de la aplicación API
+
+Ahora vamos a agregar DynamoDB y S3 a la mezcla. También vamos a estar agregando algunas funciones Lambda.
+
+Ahora nuestra nuevo diseño de la aplicación backend de notas se verá como esto.
+
+
+
+Hay un par de cosas que tomar en cuenta aquí:
+
+1. Nuestra base de datos no está expuesta públicamente y sólo es invocada por nuestras funciones Lambda.
+2. Pero nuestros usuarios estarán cargando archivos directamente a nuestro bucket S3 que creamos.
+
+El segundo punto es algo que es diferente de muchas arquitecturas tradicionales basadas en servidor. Estamos acostumbrados a cargar archivos a nuestro servidor y luego moverlos al servidor de archivos. Pero vamos a cargarlos directamente a nuestro bucket S3. Veremos esto con más detalle cuando estemos en la carga de archivos.
+
+En las secciones siguientes estaremos viendo como asegurar el acceso a esos recursos. Vamos a configurarlos para que solo nuestros usuarios autenticados tengan permitido acceder a esos recursos.
+
+Ahora que tenemos una buena idea de como nuestra aplicación será diseñada, volvamos al trabajo!
diff --git a/_chapters/es/setup-the-serverless-framework.md b/_chapters/es/setup-the-serverless-framework.md
new file mode 100644
index 0000000000..322f1e469d
--- /dev/null
+++ b/_chapters/es/setup-the-serverless-framework.md
@@ -0,0 +1,115 @@
+---
+layout: post
+title: Configurar el Serverless Framework
+date: 2016-12-29 00:00:00
+lang: es
+ref: setup-the-serverless-framework
+description: Para crear nuestro serverless api backend usando AWS Lambda y API Gateway vamos a usar el Serverless Framework (https://serverless.com). El Serverless Framework ayuda a los desarrolladores a construir y manejar aplicaciones serverless en AWS y otros proveedores en la nube. Podemos instalar el Serverless Framework CLI desde su paquete NPM y usarlo para crear un nuevo proyecto con Serverless Framework.
+comments_id: set-up-the-serverless-framework/145
+---
+
+Vamos a estar usando [AWS Lambda](https://aws.amazon.com/lambda/) y [Amazon API Gateway](https://aws.amazon.com/api-gateway/) para crear nuestro backend. AWS Lambda es un servicio computable que permite ejecutar tu código sin proveer o administrar servidores. Tú sólo pagas por el tiempo computable que consumes - no hay cargo alguno cuando tu código no se está ejecutando. Y el servicio API Gateway facilita a los desarrolladores crear, publicar, mantener, monitorear, y volver seguras las APIs. Trabajar directamente con AWS Lambda y configurar el servicio de API Gateway puede ser un poco complicado; por eso vamos a usar el [Serverless Framework](https://serverless.com) para ayudarnos.
+
+El Serverless Framework permite a los desarrolladores desplegar aplicaciones backend como funciones independientes que serán desplegadas hacia AWS Lambda. También configura AWS Lambda para ejecutar tu código en respuesta a peticiones HTTP usando Amazon API Gateway.
+
+En este capítulo vamos a configurar el Serverless Framework en nuestro ambiente local de desarrollo.
+
+### Instalando Serverless
+
+{%change%} Instalar Serverless globalmente.
+
+``` bash
+$ npm install serverless -g
+```
+
+El comando anterior necesita [NPM](https://www.npmjs.com), un administrador de paquetes para JavaScript. Click [aquí](https://docs.npmjs.com/getting-started/installing-node) si necesitas ayuda para instalar NPM.
+
+{%change%} En tu directorio de trabajo; crea un proyecto usando Node.js. Vamos a repasar algunos detalles de este proyecto inicial en el siguiente capítulo.
+
+``` bash
+$ serverless install --url https://github.com/AnomalyInnovations/serverless-nodejs-starter --name notes-api
+```
+
+{%change%} Ve al directorio de nuestro proyecto api backend.
+
+``` bash
+$ cd notes-api
+```
+
+Ahora, el directorio deberá contener algunos archivos incluyendo **handler.js** y **serverless.yml**.
+
+- El archivo **handler.js** contiene el código real para los servicios/funciones que van a ser desplegados hacía AWS Lambda.
+- El archivo **serverless.yml** contiene la configuración sobre que servicios AWS Serverless proporcionará y como deben ser configurados.
+
+También tenemos el directorio `tests/` donde podemos agregar nuestras pruebas unitarias.
+
+### Instalando paquetes Node.js
+
+El proyecto inicial se basa en algunas dependencias listadas en el archivo `package.json`.
+
+{%change%} En la raíz del proyecto ejecuta.
+
+``` bash
+$ npm install
+```
+
+{%change%} A continuación, instalaremos un par de paquetes especificos para nuestro backend.
+
+``` bash
+$ npm install aws-sdk --save-dev
+$ npm install uuid@7.0.3 --save
+```
+
+- **aws-sdk** nos permite comunicarnos con diferentes servicios AWS.
+- **uuid** genera ids únicos. Los necesitamos para guardar información en DynamoDB.
+
+### Actualizando el nombre del servicio
+
+Cambiemos el nombre de nuestro servicio del que inicialmente teniamos.
+
+{%change%} Abre el archivo `serverless.yml` y reemplaza el contenido con lo siguiente.
+
+``` yaml
+service: notes-api
+
+# Creando un paquete optimizado para nuestras funciones
+package:
+ individually: true
+
+plugins:
+ - serverless-bundle # Empaquetar nuestras funciones con Webpack
+ - serverless-offline
+ - serverless-dotenv-plugin # Cargar el archivo .env como variables de ambiente
+
+provider:
+ name: aws
+ runtime: nodejs12.x
+ stage: prod
+ region: us-east-1
+
+functions:
+ hello:
+ handler: handler.hello
+ events:
+ - http:
+ path: hello
+ method: get
+```
+
+El nombre del servicio (`service`) es muy importante. Vamos a llamar a nuestro servicio `notes-api`. Serverless Framework crea tu ambiente de trabajo en AWS usando este nombre. Esto significa que si cambias el nombre y despliegas tu proyecto, se va a crear un **proyecto completamente nuevo**!
+
+Vamos a definir también una función Lambda llamada `hello`. Esta tiene un controlador llamado `handler.hello` y sigue el siguiente formato:
+
+``` text
+handler: {filename}-{export}
+```
+
+En este caso el controlador para nuestra función Lambda llamada `hello` es la función `hello` que se exporta en el archivo `handler.js`.
+
+Nuestra función Lambda también responde a un evento HTTP GET con la ruta `/hello`. Esto tendrá más sentido una vez que despleguemos nuestro API.
+
+Notarás que hemos incluido los plugins — `serverless-bundle`, `serverless-offline`, y `serverless-dotenv-plugin`. El plugin [serverless-offline](https://github.com/dherault/serverless-offline) es útil para nuestro desarrollo local. Mientras que el plugin [serverless-dotenv-plugin](https://github.com/colynb/serverless-dotenv-plugin) será usado después para cargar archivos `.env` como variables de ambiente Lambda.
+
+Por otro lado, usaremos el plugin [serverless-bundle](https://github.com/AnomalyInnovations/serverless-bundle) para permitirnos escribir nuestras funciones Lambda usando un sabor de JavaScript que es similar a aquel que vamos a usar en nuestra aplicación frontend en React.
+
+Veamos esto en detalle.
diff --git a/_chapters/es/what-does-this-guide-cover.md b/_chapters/es/what-does-this-guide-cover.md
index f903019926..43d420b448 100644
--- a/_chapters/es/what-does-this-guide-cover.md
+++ b/_chapters/es/what-does-this-guide-cover.md
@@ -9,7 +9,7 @@ context: true
comments_id: what-does-this-guide-cover/83
---
-Para repasar los principales conceptos involucrados en la creación de aplicaciones web, vamos a crear una aplicación sencilla para tomar notas llamada [**Scratch**](https://demo2.serverless-stack.com).
+Para repasar los principales conceptos involucrados en la creación de aplicaciones web, vamos a crear una aplicación sencilla para tomar notas llamada [**Scratch**](https://demo2.sst.dev).

@@ -58,7 +58,7 @@ Necesitas [Node v8.10 + y NPM v5.5 +](https://nodejs.org/en/). También debes te
### Cómo está estructurada esta guía
-La guía se divide en dos partes. Ambas son relativamente independientes. La primera parte cubre lo básico, mientras que la segunda cubre un par de temas avanzados junto con una forma de automatizar la configuración. Lanzamos esta guía a principios de 2017 con solo la primera parte. La comunidad de Serverless Stack ha crecido y muchos de nuestros lectores han usado la configuración descrita en esta guía para crear aplicaciones que impulsan sus negocios.
+La guía se divide en dos partes. Ambas son relativamente independientes. La primera parte cubre lo básico, mientras que la segunda cubre un par de temas avanzados junto con una forma de automatizar la configuración. Lanzamos esta guía a principios de 2017 con solo la primera parte. La comunidad de SST ha crecido y muchos de nuestros lectores han usado la configuración descrita en esta guía para crear aplicaciones que impulsan sus negocios.
Así que decidimos ampliar la guía y añadirle una segunda parte. Esta está dirigida a las personas que tienen la intención de utilizar esta configuración para sus proyectos. Automatiza todos los pasos manuales de la parte 1 y lo ayuda a crear un flujo de trabajo listo para producción que puede utilizar para todos sus proyectos sin servidor. Esto es lo que cubrimos en las dos partes.
@@ -91,7 +91,7 @@ Para el frontend:
#### Parte II
-Dirigido a personas que buscan utilizar Serverless Stack para sus proyectos del día a día. Automatizamos todos los pasos de la primera parte. Aquí está lo que se cubre en orden.
+Dirigido a personas que buscan utilizar SST para sus proyectos del día a día. Automatizamos todos los pasos de la primera parte. Aquí está lo que se cubre en orden.
Para el backend:
diff --git a/_chapters/es/what-is-aws-lambda.md b/_chapters/es/what-is-aws-lambda.md
index 3279f920b3..307eb3d4d9 100644
--- a/_chapters/es/what-is-aws-lambda.md
+++ b/_chapters/es/what-is-aws-lambda.md
@@ -14,13 +14,13 @@ comments_id: what-is-aws-lambda/308
Comencemos rápidamente por las especificaciones técnicas de AWS Lambda. Lambda soporta los siguientes entornos de ejecución:
-- Node.js: v8.10 y v6.10
-- Java 8
-- Python: 3.6 y 2.7
-- .NET Core: 1.0.1 y 2.0
+- Node.js 18.x, 16.x, y 14.x
+- Java 17, 11 y 8
+- Python 3.11, 3.10, 3.9, 3.8, y 3.7
+- .NET 7 y 6
- Go 1.x
-- Ruby 2.5
-- Rust
+- Ruby 3.2 y 2.7
+- [Rust](https://docs.aws.amazon.com/es_es/lambda/latest/dg/lambda-rust.html)
Cada función corre en un contenedor con Amazon Linux AMI a 64-bit. Y el entorno de ejecución cuenta con:
@@ -64,10 +64,10 @@ Sin embargo, debido a la optimización mencionada anteriormente, la función Lam
Por ejemplo, el método `createNewDbConnection` a continuación se llama una vez por instanciación de contenedor y no cada vez que se invoca la función Lambda. La función `myHandler` por otra parte se llama en cada invocación.
-``` javascript
+```js
var dbConnection = createNewDbConnection();
-exports.myHandler = function(event, context, callback) {
+exports.myHandler = function (event, context, callback) {
var result = dbConnection.makeQuery();
callback(null, result);
};
diff --git a/_chapters/es/who-is-this-guide-for.md b/_chapters/es/who-is-this-guide-for.md
index 2983e67b47..82a2134658 100644
--- a/_chapters/es/who-is-this-guide-for.md
+++ b/_chapters/es/who-is-this-guide-for.md
@@ -14,6 +14,6 @@ Por lo tanto, podrías ser un desarrollador de backend que le gustaría aprender
También estamos, por ahora, enfocando esto únicamente a los desarrolladores de JavaScript. Podríamos apuntar a otros idiomas y entornos en el futuro. Pero creemos que este es un buen punto de partida porque puede ser realmente beneficioso para un desarrollador fullstack usar un solo lenguaje (JavaScript) y un entorno (Node.js) para crear toda la aplicación.
-Como nota personal, el enfoque sin servidor ha sido una revelación gigante para nosotros y queríamos crear un recurso donde pudiéramos compartir lo que hemos aprendido. Puedes leer más sobre nosotros [**aquí**]({% link about.md%}). Y [echar un vistazo a una muestra de lo que la gente ha creado con Serverless Stack]({% link showcase.md%}).
+Como nota personal, el enfoque sin servidor ha sido una revelación gigante para nosotros y queríamos crear un recurso donde pudiéramos compartir lo que hemos aprendido. Puedes leer más sobre nosotros [**aquí**]({{ site.sst_url }}). Y [echar un vistazo a una muestra de lo que la gente ha creado con SST]({% link showcase.md %}).
Comencemos mirando lo que estaremos cubriendo.
diff --git a/_chapters/es/why-create-serverless-apps.md b/_chapters/es/why-create-serverless-apps.md
index 5c48d98360..d37f5d2b25 100644
--- a/_chapters/es/why-create-serverless-apps.md
+++ b/_chapters/es/why-create-serverless-apps.md
@@ -40,4 +40,4 @@ Así que cuesta $6.10 por mes. Además, un dominio .com nos costaría $12 por a
Finalmente, la facilidad de escalamiento se debe en parte a DynamoDB, que nos brinda una escala casi infinita y Lambda que simplemente se amplía para satisfacer la demanda. Y, por supuesto, nuestra interfaz es una SPA (single page application) estática simple que, casi siempre, responde de manera instantánea gracias a CloudFront.
-!Muy bien! Ahora que estás convencido del por qué debes crear aplicaciones serverless, empecemos.
+¡Muy bien! Ahora que estás convencido del por qué debes crear aplicaciones serverless, empecemos.
diff --git a/_chapters/fr/how-to-get-help.md b/_chapters/fr/how-to-get-help.md
index 20f8f612ae..59b63ab532 100644
--- a/_chapters/fr/how-to-get-help.md
+++ b/_chapters/fr/how-to-get-help.md
@@ -12,9 +12,9 @@ Si vous rencontrez des problèmes avec une étape en particulier, nous voulons n
- Nous utilisons [les sujets d'un forum de discussion Discourse]({{ site.forum_url }}) pour recevoir des commentaires et nous avons réussi à résoudre un certain nombre de problèmes dans le passé. Vérifiez donc les commentaires sous chaque chapitre pour voir si quelqu'un a déjà rencontré le même problème que vous (en anglais).
- Postez votre problème dans les commentaires pour le chapitre spécifique et l'un d'entre nous vous répondra (en anglais).
-
+
Le guide complet est hébergé sur[GitHub]({{ site.github_repo }}). Si vous trouvez une erreur, vous pouvez toujours:
- Ouvrir un [nouveau ticket]({{ site.github_repo }}/issues/new)
-- Ou si vous avez trouvé une coquille, éditer la page et soumettre une pull request!
\ No newline at end of file
+- Ou si vous avez trouvé une coquille, éditer la page et soumettre une pull request!
diff --git a/_chapters/fr/what-does-this-guide-cover.md b/_chapters/fr/what-does-this-guide-cover.md
index 4007843aab..667f7fcdff 100644
--- a/_chapters/fr/what-does-this-guide-cover.md
+++ b/_chapters/fr/what-does-this-guide-cover.md
@@ -9,7 +9,7 @@ context: true
comments_id: what-does-this-guide-cover/83
---
-Pour parcourir les concepts principaux impliqués dans la construction d'une application web, nous allons construire une application basique de prise de notes appelée [**Scratch**](https://demo2.serverless-stack.com).
+Pour parcourir les concepts principaux impliqués dans la construction d'une application web, nous allons construire une application basique de prise de notes appelée [**Scratch**](https://demo2.sst.dev).

@@ -58,7 +58,7 @@ Vous avez besoin de [Node v8.10+ and NPM v5.5+](https://nodejs.org/en/). Vous au
### Comment ce guide est structuré
-Le guide est séparé en deux parties distinctes. Elles sont toutes deux relativement autoportantes. La première partie couvre les fondamentaux, la deuxième couvre des sujets plus avancés ainsi qu'un moyen d'automatiser l'infrastructure. Nous avons lancé ce guide début 2017 avec uniquement la première partie. La communauté Serverless Stack a grandi et beaucoup de nos lecteurs ont utilisé ce setup pour leur business.
+Le guide est séparé en deux parties distinctes. Elles sont toutes deux relativement autoportantes. La première partie couvre les fondamentaux, la deuxième couvre des sujets plus avancés ainsi qu'un moyen d'automatiser l'infrastructure. Nous avons lancé ce guide début 2017 avec uniquement la première partie. La communauté SST a grandi et beaucoup de nos lecteurs ont utilisé ce setup pour leur business.
Nous avons donc décider d'étendre ce guide et d'y ajouter une sconde partie. Cela cible les personnes qui pensent utiliser ce setup pour leurs projets. Elle automatise les étapes manuelle de la partie 1 et aide à la création d'un workfow prêt pour la production que vous pouvez utiliser pour tous vos projets serverless. Voici ce que nous aobrdons dans les deux parties.
diff --git a/_chapters/fr/what-is-aws-lambda.md b/_chapters/fr/what-is-aws-lambda.md
index 6247626ca4..885b58bd2b 100644
--- a/_chapters/fr/what-is-aws-lambda.md
+++ b/_chapters/fr/what-is-aws-lambda.md
@@ -14,13 +14,13 @@ comments_id: what-is-aws-lambda/308
Voici les spécifications techniques d'AWS Lambda. Lambda supporte les langages suivants :
-- Node.js: v8.10 et v6.10
-- Java 8
-- Python: 3.6 et 2.7
-- .NET Core: 1.0.1 et 2.0
+- Node.js 18.x, 16.x, et 14.x
+- Java 17, 11 et 8
+- Python 3.11, 3.10, 3.9, 3.8, et 3.7
+- .NET 7 et 6
- Go 1.x
-- Ruby 2.5
-- Rust
+- Ruby 3.2 et 2.7
+- [Rust](https://docs.aws.amazon.com/fr_fr/lambda/latest/dg/lambda-rust.html)
Chaque fonction s'exécute dans un conteneur 64-bit Amazon Linux AMI. Et l'environnement d'exécution a :
@@ -38,7 +38,7 @@ Le temps maximum d'exécution signifie que les fonctions Lambda ne peuvent pas t
La taille du package correspond à tout le code nécessaire pour exécuter la fonction. Cela inclut toutes les dépendances (le dossier `node_modules/` dans le cas de Node.js) dont votre fonction a besoin, Il y a une limite à 250MB non-compressé et 50MB après compression. On va s'intéresser au processus de packaging un peu plus tard.
-### Fonction Lambda
+### Fonction Lambda
Voici enfin ce à quoi ressemble une fonction Lambda (en Node.js).
@@ -64,10 +64,10 @@ Cependant, en raison de l'optimisation précédemment décrite, la fonction Lamb
Par exemple, la méthode `createNewDbConnection` ci-dessous est appelée une fois par instanciation de conteneur et non à chaque fois que la fonction Lambda est appelée. En revanche, la fonction `myHandler` est appelée à chaque appel.
-``` javascript
+```js
var dbConnection = createNewDbConnection();
-exports.myHandler = function(event, context, callback) {
+exports.myHandler = function (event, context, callback) {
var result = dbConnection.makeQuery();
callback(null, result);
};
diff --git a/_chapters/fr/what-is-serverless.md b/_chapters/fr/what-is-serverless.md
index 11494f5e4c..e783b78a58 100644
--- a/_chapters/fr/what-is-serverless.md
+++ b/_chapters/fr/what-is-serverless.md
@@ -50,4 +50,4 @@ La durée des démarrages à froid dépend de la mise en œuvre du fournisseur d
Outre l'optimisation de vos fonctions, vous pouvez utiliser des astuces simples, comme une fonction planifiée, pour appeler votre fonction toutes les minutes afin de la maintenir au chaud. [Serverless Framework](https://serverless.com) que nous allons utiliser dans ce tutoriel contient quelques plugins pour vous [aider à garder vos fonctions au chaud](https://github.com/FidelLimited/serverless-plugin-warmup).
-Maintenant qu'on a une bonne idée de l'architecture serverless, regardons de plus près ce qu'est une fonction Lambda et comment notre code va être exécuté.
\ No newline at end of file
+Maintenant qu'on a une bonne idée de l'architecture serverless, regardons de plus près ce qu'est une fonction Lambda et comment notre code va être exécuté.
diff --git a/_chapters/fr/who-is-this-guide-for.md b/_chapters/fr/who-is-this-guide-for.md
index 37b36c7bf1..190cbe21c9 100644
--- a/_chapters/fr/who-is-this-guide-for.md
+++ b/_chapters/fr/who-is-this-guide-for.md
@@ -1,6 +1,6 @@
---
layout: post
-title: Pour qui est ce guide?
+title: Pour qui est ce guide?
date: 2016-12-21 00:00:00
lang: fr
ref: who-is-this-guide-for
@@ -8,12 +8,12 @@ context: true
comments_id: who-is-this-guide-for/96
---
-Ce guide est à destination des développeurs full-stack, ou des développeurs qui souhaitent construire une application full-stack. En fournissant un guide étape par étape pour le frontend et le backend, nous espérons couvrir tous les aspects du développement d'une application serverless. Il y a un certain nombre de tutoriels existants sur le we mais nous pensons qu'il serait utile d'avoir un point d'entrée unique pour le processus entier. Ce guide est construit pour servir de ressource pour apprendre comment construire et déployer des applications serverless, pas nécessairement le seul ni le meilleur moyen possible.
+Ce guide est à destination des développeurs full-stack, ou des développeurs qui souhaitent construire une application full-stack. En fournissant un guide étape par étape pour le frontend et le backend, nous espérons couvrir tous les aspects du développement d'une application serverless. Il y a un certain nombre de tutoriels existants sur le web mais nous pensons qu'il serait utile d'avoir un point d'entrée unique pour le processus entier. Ce guide est construit pour servir de ressource pour apprendre comment construire et déployer des applications serverless, pas nécessairement le seul ni le meilleur moyen possible.
En tant que développeur backend qui souhaite en apprendre plus sur la partie frontend du développement d'une application serverless ou en tant que développeur backend qui souhaite en apprendre plus à propos du backend; ce guide couvre vos besoins.
Nous couvrons uniquement les besoins des développeurs JavaScript pour le moment. Nous ciblerons peut-être d'autres langages et environnements dans le future. Mais nous pensons que c'est un bon point de départ, car cela peut être réellement bénéfique d'utiiser un seul langage (JavaScript) et environnement (Node.js) pour construire votre application complète.
-D'un point de vue personnel, l'approche serverless a été un énorme révélation pour nous et nous souhaitions créer une ressource pour partager ce que nous avons appris. Vous pouvez en lire plus sur nous [**ici**]({% link about.md %}). Et [voir un échantillon de ce qui nous avons fait avec Serverless Stack]({% link showcase.md %}).
+D'un point de vue personnel, l'approche serverless a été un énorme révélation pour nous et nous souhaitions créer une ressource pour partager ce que nous avons appris. Vous pouvez en lire plus sur nous [**ici**]({{ site.sst_url }}). Et [voir un échantillon de ce qui nous avons fait avec SST]({% link showcase.md %}).
Commençons par regarder les aspects que nous aborderons.
diff --git a/_chapters/further-reading.md b/_chapters/further-reading.md
index d12e424e92..5e8e222d46 100644
--- a/_chapters/further-reading.md
+++ b/_chapters/further-reading.md
@@ -8,34 +8,24 @@ ref: further-reading
comments_id: further-reading/209
---
-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.
+Once you've completed the guide, you are probably going to use SST 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 Documentation](https://serverless.com/framework/docs/): Documentation for the Serverless Framework
+- [SST Documentation]({{ site.sst_url }}/docs/): Documentation for SST
- [DynamoDB, explained](https://www.dynamodbguide.com): A Primer on the DynamoDB NoSQL database
-- [React JS Docs](https://reactjs.org/docs/hello-world.html): The official React docs
+- [Learn React](https://react.dev/learn): The official React docs
-- [JSX In Depth](https://reactjs.org/docs/jsx-in-depth.html): Learn JSX in a bit more detail
+- [Vite docs](https://vitejs.dev/guide/): The official Vite docs
-- [Create React App User Guide](https://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](https://react-bootstrap.github.io/docs/getting-started/introduction): The official React-Bootstrap docs
-- [React-Bootstrap Docs](https://react-bootstrap.github.io/getting-started/introduction): The official React-Bootstrap docs
-
-- [Bootstrap v3 Docs](http://getbootstrap.com/docs/3.3/getting-started/): The Bootstrap v3 docs that React-Bootstrap is based on
-
-- [React Router Docs](https://reacttraining.com/react-router/web/guides/philosophy): The official React Router v4 docs
-
-- [AWS Amplify Developer Guide](https://aws.github.io/aws-amplify/media/developer_guide): The AWS Amplify developer guide
+- [Learn React Router](https://reactrouter.com/en/6.15.0/start/tutorial): The official React Router docs
- [AWS Amplify API Reference](https://aws.github.io/aws-amplify/api/): The AWS Amplify API reference
-- [AWS CloudFormation Docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/GettingStarted.Walkthrough.html): The AWS user guide for CloudFormation
-
-- [Jest Unit Test Docs](https://facebook.github.io/jest/docs/en/getting-started.html): The official Jest docs
-
-- [Seed Docs](https://seed.run/docs/): The official Seed docs
+- [Vitest Docs](https://vitest.dev/guide/): The official Vitest docs
-- [Netlify Docs](https://www.netlify.com/docs/): The official Netlify docs
+- [SST Console Docs]({{ site.sst_url }}/docs/console/): The SST Console 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.
diff --git a/_chapters/getting-production-ready.md b/_chapters/getting-production-ready.md
index 7ded1b023b..6be3f53226 100644
--- a/_chapters/getting-production-ready.md
+++ b/_chapters/getting-production-ready.md
@@ -1,41 +1,72 @@
---
layout: post
title: Getting Production Ready
-date: 2018-02-23 00:00:00
+date: 2021-08-24 00:00:00
lang: en
-description: To get our serverless app production ready, we are going to have to configure it using Infrastructure as Code. We are also going to need to configure separate environments for dev/production and automate our deployments.
+description: To get our full-stack serverless app ready for production, we want to use a custom domain and to automate our deployments.
ref: getting-production-ready
comments_id: getting-production-ready/158
---
-Now that we've gone through the basics of creating a Serverless Stack, you are probably wondering if you need to do all these manual steps everytime 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:
+Now that we've gone through the basics of creating a full-stack serverless app, you are ready to deploy it to production.
-- **Infrastructure as code**
+### Deploy to Prod
- Currently, you go through a bunch of manual steps with a lot of clicking around to configure your backend. This makes it pretty 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 converting this entire stack into code. This means that it can automatically re-create the entire project from scratch without ever touching the AWS Console.
+We are now going to deploy our app to prod. You can go ahead and stop the local development environments for SST and React.
-- **Working with 3rd party APIs**
+{%change%} Run the following **in your project root**.
- 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.
+```bash
+$ npx sst deploy --stage production
+```
-- **Unit tests**
+This command will take a few minutes as it'll deploy your app to a completely new environment. Recall that we are deploying to a separate prod environment because we don't want to affect our users while we are actively developing our app. This ensures that we have a separate local dev environment and a separate prod environment.
- We will also look at how to configure unit tests for our backend using [Jest](https://facebook.github.io/jest/).
+{%info%}
+The `production` name that we are using is arbitrary. We can call it `prod` or `live`. SST just uses the string internally to create a new version of your app.
+{%endinfo%}
-- **Automating deployments**
+At the end of the deploy process you should see something like this.
- 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](https://seed.run) (for the backend) and [Netlify](https://netlify.com) (for the frontend).
+```bash
++ Complete
+ Api: https://7qdwu0iuga.execute-api.us-east-1.amazonaws.com
+ Frontend: https://d1wyq16hczgtjw.cloudfront.net
+ ...
+```
-- **Configuring environments**
+### Set Secrets in Prod
- Typically while working on projects you end up creating multiple 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.
+We also need to configure out secrets for production. You'll recall we had previously [configured secrets for our local stage]({% link _chapters/handling-secrets-in-sst.md %}).
-- **Working with secrets**
+We'll do the same here but for `production`.
- We will look at how to work with secret environment variables in our local environment and in production.
+{%change%} Run the following **in your project root**.
-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.
+```bash
+$ npx sst secret set --stage production StripeSecretKey
+```
-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.
+{%note%}
+For this guide we are using the same Stripe secret key in production but you'll likely be using different values in production.
+{%endnote%}
-Let's get started by first converting our backend infrastructure into code.
+You can run `npx sst secret list --stage production` to see the secrets for prod.
+
+Our full-stack serverless app is almost ready to go. You can play around with the prod version.
+
+### Custom Domains
+
+However the API is currently on an endpoint that's auto-generated by [API Gateway](https://aws.amazon.com/api-gateway/).
+
+``` txt
+https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
+```
+
+And the frontend React app is hosted on an auto-generated [CloudFront](https://aws.amazon.com/cloudfront/) domain.
+
+``` txt
+https://d3j4c16hczgtjw.cloudfront.net
+```
+
+We want to host these on our own domain. Let's look at that next.
diff --git a/_chapters/give-feedback-while-logging-in.md b/_chapters/give-feedback-while-logging-in.md
index efc3166767..e9f6924581 100644
--- a/_chapters/give-feedback-while-logging-in.md
+++ b/_chapters/give-feedback-while-logging-in.md
@@ -4,8 +4,7 @@ title: Give Feedback While Logging In
date: 2017-01-18 00:00:00
lang: en
ref: give-feedback-while-logging-in
-description: We should give users some feedback while we are logging them in to our React.js app. To do so we are going to create a component that animates a Glyphicon refresh icon inside a React-Bootstrap Button component. We’ll do the animation while the log in call is in progress.
-context: true
+description: We should give users some feedback while we are logging them in to our React.js app. To do so we are going to create a component that animates a Glyphicon refresh icon inside a React-Bootstrap Button component. We’ll do the animation while the log in call is in progress. We'll also add some basic error handling to our app.
comments_id: give-feedback-while-logging-in/46
---
@@ -13,31 +12,31 @@ It's important that we give the user some feedback while we are logging them in.
### 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 initial state in the `constructor` looks like the following.
+{%change%} To do this we are going to add an `isLoading` flag to the state of our `src/containers/Login.tsx`. Add the following to the top of our `Login` function component.
-``` javascript
-this.state = {
- isLoading: false,
- email: "",
- password: ""
-};
+```tsx
+const [isLoading, setIsLoading] = useState(false);
```
-And we'll update it while we are logging in. So our `handleSubmit` method now looks like so:
+{%change%} And we'll update it while we are logging in. So our `handleSubmit` function now looks like so:
-``` javascript
-handleSubmit = async event => {
+```tsx
+async function handleSubmit(event: React.FormEvent) {
event.preventDefault();
- this.setState({ isLoading: true });
+ setIsLoading(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 });
+ await Auth.signIn(email, password);
+ userHasAuthenticated(true);
+ nav("/");
+ } catch (error) {
+ if (error instanceof Error) {
+ alert(error.message);
+ } else {
+ alert(String(error));
+ }
+ setIsLoading(false);
}
}
```
@@ -46,93 +45,174 @@ handleSubmit = async event => {
Now to reflect the state change in our button 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`.
+{%change%} Create a `src/components/` directory by running this command in the `frontend/` directory.
-``` coffee
-import React from "react";
-import { Button, Glyphicon } from "react-bootstrap";
+```bash
+$ mkdir src/components/
+```
+
+Here we'll be storing all our React components that are not dealing directly with our API or responding to routes.
+
+{%change%} Create a new file and add the following in `src/components/LoaderButton.tsx`.
+
+```tsx
+import Button from "react-bootstrap/Button";
+import { BsArrowRepeat } from "react-icons/bs";
import "./LoaderButton.css";
-export default ({
- isLoading,
- text,
- loadingText,
+export default function LoaderButton({
className = "",
disabled = false,
+ isLoading = false,
...props
-}) =>
- ;
+}) {
+ return (
+
+ );
+}
```
-This is a really simple component that takes an `isLoading` flag and the text that the button 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` button. And we ensure that the button 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.
+This is a really simple component that takes an `isLoading` prop and `disabled` prop. The latter is a result of what we have currently in our `Login` button. And we ensure that the button 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.
+
+The `className` prop that we have is to ensure that a CSS class that's set for this component, doesn't override the `LoaderButton` CSS class that we are using internally.
+
+When the `isLoading` flag is on, we show an icon. The icon we include is from the Bootstrap icon set of [React Icons](https://react-icons.github.io/icons?name=bs){:target="_blank"}.
And let's add a couple of styles to animate our loading icon.
-Add the following to `src/components/LoaderButton.css`.
+{%change%} Add the following to `src/components/LoaderButton.css`.
-``` css
-.LoaderButton .spinning.glyphicon {
+```css
+.LoaderButton {
+ margin-top: 12px;
+}
+
+.LoaderButton .spinning {
margin-right: 7px;
- top: 2px;
+ margin-bottom: 1px;
animation: spin 1s infinite linear;
}
+
@keyframes spin {
- from { transform: scale(1) rotate(0deg); }
- to { transform: scale(1) rotate(360deg); }
+ 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.
+This spins the icon 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 `