├── .github
└── PULL_REQUEST_TEMPLATE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── assets
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── readmeImages
│ ├── ArchDiagram.png
│ ├── BackendDiagram.png
│ ├── DeveloperTools.png
│ ├── SummaryDiagram.png
│ └── images.md
├── src
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── Routes.tsx
│ ├── common
│ │ └── PropsRoute.tsx
│ ├── config.ts
│ ├── images
│ │ └── full-stack.png
│ ├── index.css
│ ├── index.tsx
│ ├── modules
│ │ ├── goal
│ │ │ ├── AddEditGoal.css
│ │ │ └── AddEditGoal.tsx
│ │ ├── notFound
│ │ │ ├── NotFound.tsx
│ │ │ └── notFound.css
│ │ └── signup
│ │ │ ├── Home.tsx
│ │ │ ├── Login.tsx
│ │ │ ├── Signup.tsx
│ │ │ ├── home.css
│ │ │ ├── login.css
│ │ │ └── signup.css
│ ├── react-app-env.d.ts
│ ├── registerServiceWorker.ts
│ └── types
│ │ ├── aws-amplify-react.d.ts
│ │ └── aws-amplify-ui.d.ts
└── tsconfig.json
├── extensions
├── cw-slack-chime
│ ├── README.md
│ ├── architecture.png
│ ├── functions
│ │ ├── chime.js
│ │ ├── modifyAlarm.js
│ │ └── slack.js
│ └── master.yaml
└── search-api
│ ├── README.md
│ ├── architecture.png
│ ├── functions
│ ├── searchCluster.py
│ └── updateSearchCluster.py
│ └── master.yaml
├── functions
├── CreateGoal.js
├── DeleteGoal.js
├── GetGoal.js
├── ListGoals.js
└── UpdateGoal.js
├── readmeImages
├── ArchDiagram.png
├── BackendDiagram.png
├── DeveloperTools.png
├── SummaryDiagram.png
└── images.md
├── template
└── master-fullstack.yaml
└── workshop
└── readme.md
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | *Issue #, if available:*
2 |
3 | *Description of changes:*
4 |
5 |
6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
7 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check [existing open](https://github.com/awslabs/aws-full-stack-template/issues), or [recently closed](https://github.com/awslabs/aws-full-stack-template/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *master* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-full-stack-template/labels/help%20wanted) issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](https://github.com/awslabs/aws-full-stack-template/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this
4 | software and associated documentation files (the "Software"), to deal in the Software
5 | without restriction, including without limitation the rights to use, copy, modify,
6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7 | permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## AWS Full-Stack Template
2 |
3 | AWS Full-Stack Template is a full-stack sample web application that creates a simple CRUD (create, read, update, delete) app, and provides the foundational services, components, and plumbing needed to get a basic web application up and running. **[Try out the deployed application here](https://d2k5b8bzo1vefz.cloudfront.net/)**!
4 |
5 | The entire application - frontend, backend, and all configuration - can deployed in your AWS account with a single CloudFormation template. Get started with building your own below!
6 |
7 |
8 | ## License Summary
9 |
10 | This sample code is made available under a modified MIT license. See the LICENSE file.
11 |
12 |
13 |
14 | ## Outline
15 |
16 | - [Overview](#overview)
17 | - [Instructions](#instructions)
18 | - [Getting started](#getting-started)
19 | - [Cleaning up](#cleaning-up)
20 | - [Architecture](#architecture)
21 | - [Implementation details](#implementation-details)
22 | - [Amazon DynamoDB](#amazon-dynamodb)
23 | - [Amazon API Gateway](#amazon-api-gateway)
24 | - [AWS Lambda](#aws-lambda)
25 | - [AWS IAM](#aws-iam)
26 | - [Amazon Cognito](#amazon-cognito)
27 | - [Amazon Cloudfront and Amazon S3](#amazon-cloudfront-and-amazon-s3)
28 | - [Amazon Cloudwatch](#amazon-cloudwatch)
29 | - [AWS CodeCommit, AWS CodePipeline, AWS CodeBuild](#aws-codecommit-aws-codepipeline-aws-codebuild)
30 | - [Running your web application locally](#running-your-web-application-locally)
31 | - [Considerations for demo purposes](#considerations-for-demo-purposes)
32 | - [Known limitations](#known-limitations)
33 | - [Additions, forks, and contributions](#additions-forks-and-contributions)
34 | - [Extensions](#extensions)
35 | - [Questions and contact](#questions-and-contact)
36 |
37 |
38 |
39 | ## Overview
40 |
41 | The goal of AWS Full-Stack Template is to provide a fully-functional web application that helps users accelerate building apps on AWS by providing an out-of-the-box template. This template is production-ready and pre-loaded with best practices. Applications today have an increasing number of building blocks and infrastructure components, and AWS Full-Stack Template will help educate professionals and students alike to design software in a modern cloud computing world. With AWS Full-Stack Template, developers can create a cohesive, production-ready application on the cloud in minutes, allowing them to focus on building the pieces that matter and add value.
42 |
43 | The provided CloudFormation template automates the entire creation and deployment of AWS Full-Stack Template. The template includes the following components:
44 |
45 | **Database components**
46 |
47 | * Goals list – Amazon DynamoDB offers fast, predictable performance for the key-value lookups needed in the goals app, and enormous scale so you can build on top of it. In this implementation, we have unique identifiers for each goal, the goal name, and description.
48 |
49 | **Application components**
50 |
51 | * Serverless service backend – Amazon API Gateway powers the interface layer between the frontend and backend, and invokes serverless compute with AWS Lambda.
52 | * Web application blueprint – We include a React web application pre-integrated out-of-the-box with best practices and tools such as React Bootstrap, React Router, Typescript, and more.
53 |
54 | **Infrastructure components**
55 |
56 | * Continuous deployment code pipeline – AWS CodePipeline and AWS CodeBuild help you build, test, and release your application code.
57 | * Serverless web application – Amazon CloudFront and Amazon S3 provide a globally-distributed application.
58 |
59 | You can choose to customize the template to create your own goals app, modify it to make a different type of simple notes or to-do application, or add onto it to make a completely different type of web application.
60 |
61 | Users can build on top of AWS Full-Stack Template to create any application they envision, whether a travel booking tool, a blog, or another web app. **[AWS Bookstore Demo App](https://github.com/aws-samples/aws-bookstore-demo-app)** is just one full-fledged example of what you might create using AWS Full-Stack Template.
62 |
63 | Once you've set up AWS Full-Stack Template, check out how you can build on top of it with [Extensions](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions).
64 |
65 |
66 |
67 | ---
68 |
69 |
70 |
71 | ## Instructions
72 |
73 | ***IMPORTANT NOTE:** Creating this application in your AWS account will create and consume AWS resources, which **will cost money**. We estimate that running this demo application will cost **<$0.10/hour** with light usage. Be sure to shut down/remove all resources once you are finished to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down below).*
74 |
75 |
76 |
77 | ### Getting started
78 |
79 | To get AWS Full-Stack Template up and running in your own AWS account, follow these steps (if you do not have an AWS account, please see [How do I create and activate a new Amazon Web Services account?](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/)):
80 |
81 | 1. Log into the [AWS console](https://console.aws.amazon.com/) if you are not already.
82 | *Note: If you are logged in as an IAM user, ensure your account has permissions to create and manage the necessary resources and components for this application.*
83 | 2. Choose one of the **Launch Stack** buttons below for your desired AWS region to open the AWS CloudFormation console and create a new stack. AWS Full-Stack Template is supported in the following regions:
84 |
85 | Region name | Region code | Launch
86 | --- | --- | ---
87 | US East (N. Virginia) | us-east-1 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
88 | US West (Oregon) | us-west-2 | [](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
89 | EU (Ireland) | eu-west-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
90 | EU (Frankfurt) | eu-central-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
91 |
92 |
93 | 3. Continue through the CloudFormation wizard steps
94 | 1. Name your stack, e.g. MyGoalsApp
95 | 2. Provide a project name, e.g. goalsapp (must be lowercase, letters only, and **under twelve (12) characters**). This is used when naming your resources, e.g. tables, etc.
96 | 3. After reviewing, check the blue box for creating IAM resources.
97 | 4. Choose **Create stack**. This will take ~15 minutes to complete.
98 | 5. Once the CloudFormation deployment is complete, check the status of the build in the [CodePipeline](https://console.aws.amazon.com/codesuite/codepipeline/pipelines) console and ensure it has succeeded.
99 | 6. Sign into your application
100 | 1. The output of the CloudFormation stack creation will provide a CloudFront URL (in the **Outputs** table of the stack details page). Click the link or copy and paste the CloudFront URL into your browser.
101 | 2. You can sign into your application by registering an email address and a password. Choose **Sign up to explore the demo** to register. The registration/login experience is run in your AWS account, and the supplied credentials are stored in Amazon Cognito.
102 | *Note: given that this is a demo application, we highly suggest that you do not use an email and password combination that you use for other purposes (such as an AWS account, email, or e-commerce site).*
103 | 3. Once you provide your credentials, you will receive a verification code at the email address you provided. Upon entering this verification code, you will be signed into the application.
104 |
105 |
106 |
107 | ### Cleaning up
108 |
109 | To tear down your application and remove all resources associated with AWS Full-Stack Template, follow these steps:
110 |
111 | 1. Log into the AWS CloudFormation Console and find the stack you created for the demo app
112 | 3. Delete the stack
113 |
114 | *Remember to shut down/remove all related resources once you are finished to avoid ongoing charges to your AWS account.*
115 |
116 |
117 |
118 | ---
119 |
120 |
121 |
122 | ## Architecture
123 |
124 | **Summary diagram**
125 |
126 | 
127 |
128 |
129 |
130 | **High-level, end-to-end diagram**
131 |
132 | 
133 |
134 |
135 |
136 | **Frontend**
137 |
138 | Build artifacts are stored in a S3 bucket where web application assets are maintained (web graphics, etc.). Amazon CloudFront caches the frontend content from S3, presenting the application to the user via a CloudFront distribution. The frontend interacts with Amazon Cognito and Amazon API Gateway only. Amazon Cognito is used for all authentication requests, whereas API Gateway (and Lambda) is used for all API calls to DynamoDB.
139 |
140 | **Backend**
141 |
142 | The core of the backend infrastructure consists of Amazon Cognito, Amazon DynamoDB, AWS Lambda, and Amazon API Gateway. The application leverages Amazon Cognito for user authentication, and Amazon DynamoDB to store all of the data for the goals.
143 |
144 | 
145 |
146 |
147 |
148 | **Developer Tools**
149 |
150 | The code is hosted in AWS CodeCommit. AWS CodePipeline builds the web application using AWS CodeBuild. After successfully building, CodeBuild copies the build artifacts into a S3 bucket where the web application assets are maintained (web graphics, etc.). Along with uploading to Amazon S3, CodeBuild invalidates the cache so users always see the latest experience when accessing the storefront through the Amazon CloudFront distribution. AWS CodeCommit. AWS CodePipeline, and AWS CodeBuild are used in the deployment and update processes only, not while the application is in a steady-state of use.
151 |
152 | 
153 |
154 |
155 |
156 | ---
157 |
158 |
159 |
160 | ## Implementation details
161 |
162 | *Note: The provided CloudFormation template contains only a portion of the resources needed to create and run the application. There are web assets (images, etc.), Lambda functions, and other resources called from the template to create the full experience. These resources are stored in a public-facing S3 bucket and referenced in the template.*
163 |
164 |
165 |
166 | ### Amazon DynamoDB
167 |
168 | The backend of AWS Full-Stack Template leverages Amazon DynamoDB to enable dynamic scaling and the ability to add features as we improve our goals application. The application creates one table in DynamoDB; the table name will match the "ProjectName" you used when creating the stack in CloudFormation. DynamoDB's primary key consists of a partition (hash) key and an optional sort (range) key. The primary key (partition and sort key together) must be unique.
169 |
170 | **Goals Table**
171 |
172 | ```js
173 | GoalsTable {
174 | userId: string (primary partition key)
175 | goalId: string (primary sort key)
176 | title: string
177 | content: string
178 | createdAt: number
179 | }
180 | ```
181 |
182 | The table's primary key is made up of the user ID (partition key) and the goal ID (sort key). Given that this composite primary key has both pieces of information, we can query on the user ID (partition key) alone, which will return only the goals that are in the specified user's account. It also allows us to query DynamoDB on the total composite key (user ID and goal ID), which will return goal details without additional data processing.
183 |
184 |
185 |
186 | ### Amazon API Gateway
187 |
188 | Amazon API Gateway acts as the interface layer between the frontend (Amazon CloudFront, Amazon S3) and AWS Lambda, which calls the backend (database). Below are the different APIs the application uses:
189 |
190 | **Goals (DynamoDB)**
191 |
192 | GET /goals (ListGoals)
193 | POST /goals (CreateGoal)
194 | GET /goals/{:id} (GetGoal)
195 | PUT /goals/{:id} (UpdateGoal)
196 | DELETE /goals/{:id} (DeleteGoal)
197 |
198 |
199 |
200 | ### AWS Lambda
201 |
202 | AWS Lambda is used in a few different places to run the application, as shown in the architecture diagram. The important Lambda functions that are deployed as part of the template are shown below, and available in the [functions](/functions) folder. In the cases where the response fields are blank, the application will return a statusCode 200 or 500 for success or failure, respectively.
203 |
204 |
205 |
206 | **ListGoals**
207 |
208 | Lambda function that lists the user's goals. The user's account ID is retrieved through the request context (does not need to be explicitly provided in the request).
209 |
210 | ```js
211 | ListGoalsRequest {
212 |
213 | }
214 | ```
215 |
216 | ```js
217 | ListGoalsResponse {
218 | goals: goal[]
219 | }
220 | ```
221 |
222 | ```js
223 | goal {
224 | goalId: string
225 | title: string
226 | content: string
227 | createdAt: number
228 | }
229 | ```
230 |
231 |
232 |
233 | **GetGoal**
234 |
235 | Lambda function that returns the properties of a goal.
236 |
237 | ```js
238 | GetGoalRequest {
239 | goalId: string
240 | }
241 | ```
242 |
243 | ```js
244 | GetGoalResponse {
245 | goalId: string
246 | title: string
247 | content: string
248 | createdAt: number
249 | }
250 | ```
251 |
252 |
253 |
254 | **CreateGoal**
255 |
256 | Lambda function that creates a specified goal in the user's account.
257 |
258 | ```js
259 | CreateGoalRequest {
260 | title: string
261 | content: string
262 | }
263 | ```
264 |
265 | ```js
266 | CreateGoalResponse {
267 |
268 | }
269 | ```
270 |
271 |
272 |
273 | **DeleteGoal**
274 |
275 | Lambda function that removes a given goal from the user's account.
276 |
277 | ```js
278 | DeleteGoalRequest {
279 | goalId: string
280 | }
281 | ```
282 |
283 | ```js
284 | DeleteGoalResponse {
285 |
286 | }
287 | ```
288 |
289 |
290 |
291 | **UpdateGoal**
292 |
293 | Lambda function that updates the user's goal with a new title and/or content.
294 |
295 | ```js
296 | UpdateGoalRequest {
297 | title: string
298 | content: string
299 | }
300 | ```
301 |
302 | ```js
303 | UpdateGoalResponse {
304 |
305 | }
306 | ```
307 |
308 |
309 |
310 |
311 | ### AWS IAM
312 |
313 | The following IAM role (and included policies) is needed to run the application:
314 |
315 | **DynamoDbLambda**
316 | *AWS managed policy*
317 | AWSLambdaBasicExecutionRole
318 | *Inline policy*
319 | GoalsPolicy
320 | dynamodb:PutItem - table/Goals
321 | dynamodb:Query - table/Goals
322 | dynamodb:UpdateItem - table/Goals
323 | dynamodb:GetItem - table/Goals
324 | dynamodb:Scan - table/Goals
325 | dynamodb:DeleteItem - table/Goals
326 |
327 |
328 |
329 | ### Amazon Cognito
330 |
331 | Amazon Cognito handles user account creation and login for the goals application. For the purposes of this template, you can only browse your goals after login, which could represent the architecture of different types of web apps. Users can also choose to separate the architecture, where portions of the web app are publicly available and others are available upon login.
332 |
333 | User Authentication
334 | * Email address
335 |
336 | Amazon Cognito passes the CognitoIdentityID (which AWS Full-Stack Template uses as the Customer ID) for every user along with every request from Amazon API Gateway to Lambda, which helps the services authenticate against which user is doing what.
337 |
338 |
339 |
340 | ### Amazon CloudFront and Amazon S3
341 |
342 | Amazon CloudFront hosts the web application frontend that users interface with. This includes web assets like pages and images. For demo purposes, CloudFormation pulls these resources from S3.
343 |
344 |
345 |
346 | ### Amazon CloudWatch
347 |
348 | The capabilities provided by CloudWatch are not exposed to the end users of the web app, rather the developer/administrator can use CloudWatch logs, alarms, and graphs to track the usage and performance of their web application.
349 |
350 |
351 |
352 | ### AWS CodeCommit, AWS CodePipeline, AWS CodeBuild
353 |
354 | Similar to CloudWatch, the capabilities provided by CodeCommit, CodePipeline, and CodeBuild are not exposed to the end users of the web app. The developer/administrator can use these tools to help stage and deploy the application as it is updated and improved.
355 |
356 | ## Running your web application locally
357 |
358 | 1. If you haven't setup Git credentials for AWS CodeCommit before, head to the IAM Console. If you have already you can skip to step 5.
359 | 2. Click on your IAM user.
360 | 3. Click on the **Security credentials tab**. Scroll to the bottom and click **Generate** underneath the **HTTPS Git credentials for AWS CodeCommit**.
361 | 4. Download and save these credentials. You will use these credentials when cloning your repository.
362 |
363 |
364 | 5. Go to the CodeCommit console and find your code repository.
365 | 5. Click the HTTPS button underneath the **Clone URL** column.
366 | 6. Open up your terminal, type `git clone ` paste the Clone URL and hit enter.
367 |
368 |
369 | 7. Once the repository has created, run `npm install`.
370 | 8. After all dependencies have been downloaded, run `npm run start`.
371 |
372 |
373 | Your done! Any future updates you make to your repository get pushed to your code pipeline automatically and published to your web application endpoint.
374 |
375 |
376 |
377 | ---
378 |
379 |
380 |
381 | ## Considerations for demo purposes
382 |
383 | 1. Web assets (pages, images, etc.) are pulled from a public S3 bucket via the CloudFormation template to create the frontend for AWS Full-Stack Template. When building your own web application (or customizing this one), you will likely pull from your own S3 buckets. If you customize the lambda functions, you will want to store these separately, as well.
384 |
385 | ## Known limitations
386 |
387 | * The application was written for demonstration purposes and not for production use.
388 | * Validation is working properly from an end-user standpoint, but is not cleanly implemented. For instance, the submit buttons (to create a goal, update a goal, login, signup, and enter confirmation code) are disabled (as designed) when validation fails, but we added an extra helper function to support this. This issue occured when the app was upgraded to Bootstrap 4. We plan to fix this in a future revision.
389 | * In today's implementation, we have all of the Lambda functions associated with one IAM role. Ideally, each Lambda function would have its own scoped-down IAM role and policies.
390 | * Upon the first use of a Lambda function, cold start times can be slow. Once the Lambda function has been warmed up, performance will improve.
391 |
392 | ## Additions, forks, and contributions
393 |
394 | We are excited that you are interested in using AWS Full-Stack Template! This is a great place to start if you are just beginning with AWS and want to get a functional application up and running. It is equally useful if you are looking for a sample full-stack application to fork off of and build your own custom application. We encourage developer participation via contributions and suggested additions. Of course you are welcome to create your own version!
395 |
396 | Please see the [contributing guidelines](CONTRIBUTING.md) for more information.
397 |
398 | For just one example of how you can build on top of this, check out **[AWS Bookstore Demo App](https://github.com/aws-samples/aws-bookstore-demo-app)** which was built on top of AWS Full-Stack Template.
399 |
400 | ## Extensions
401 |
402 | Are you looking for a few extensions that you can use to build on top of the Full-Stack ecosystem? Check them out in the [Extensions](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions) folder!
403 |
404 | Want to contribute an extension? Leave us a comment or submit a PR!
405 |
406 | ## Questions and contact
407 |
408 | For questions on AWS Full-Stack Template, or to contact the team, please leave a comment on GitHub.
409 |
--------------------------------------------------------------------------------
/assets/README.md:
--------------------------------------------------------------------------------
1 | ## AWS Full-Stack Template
2 |
3 | AWS Full-Stack Template is a full-stack sample web application that creates a simple CRUD (create, read, update, delete) app, and provides the foundational services, components, and plumbing needed to get a basic web application up and running. [Try out the deployed application here](https://d2k5b8bzo1vefz.cloudfront.net/)!
4 |
5 | The entire application - frontend, backend, and all configuration - can deployed in your AWS account with a single CloudFormation template. Get started with building your own below!
6 |
7 |
8 | ## License Summary
9 |
10 | This sample code is made available under a modified MIT license. See the LICENSE file.
11 |
12 |
13 |
14 | ## Outline
15 |
16 | - [Overview](#overview)
17 | - [Instructions](#instructions)
18 | - [Getting started](#getting-started)
19 | - [Cleaning up](#cleaning-up)
20 | - [Architecture](#architecture)
21 | - [Implementation details](#implementation-details)
22 | - [Amazon DynamoDB](#amazon-dynamodb)
23 | - [Amazon API Gateway](#amazon-api-gateway)
24 | - [AWS Lambda](#aws-lambda)
25 | - [AWS IAM](#aws-iam)
26 | - [Amazon Cognito](#amazon-cognito)
27 | - [Amazon Cloudfront and Amazon S3](#amazon-cloudfront-and-amazon-s3)
28 | - [Amazon Cloudwatch](#amazon-cloudwatch)
29 | - [AWS CodeCommit, AWS CodePipeline, AWS CodeBuild](#aws-codecommit-aws-codepipeline-aws-codebuild)
30 | - [Considerations for demo purposes](#considerations-for-demo-purposes)
31 | - [Known limitations](#known-limitations)
32 | - [Additions, forks, and contributions](#additions-forks-and-contributions)
33 | - [Questions and contact](#questions-and-contact)
34 |
35 |
36 |
37 | ## Overview
38 |
39 | The goal of the AWS Full-Stack Template is to provide a fully-functional web application that helps users accelerate building apps on AWS by providing an out-of-the-box template. This template is production-ready and pre-loaded with best practices. Applications today have an increasing number of building blocks and infrastructure components, and AWS Full-Stack Template will help educate professionals and students alike to design software in a modern cloud computing world. With AWS Full-Stack Template, developers can create a cohesive, production-ready application on the cloud in minutes, allowing them to focus on building the pieces that matter and add value.
40 |
41 | The provided CloudFormation template automates the entire creation and deployment of the AWS Full-Stack Template. The template includes the following components:
42 |
43 | **Database components**
44 |
45 | * Goals list – Amazon DynamoDB offers fast, predictable performance for the key-value lookups needed in the goals app, and enormous scale so you can build on top of it.. In this implementation, we have unique identifiers for each goal, the goal name, and description.
46 |
47 | **Application components**
48 |
49 | * Serverless service backend – Amazon API Gateway powers the interface layer between the frontend and backend, and invokes serverless compute with AWS Lambda.
50 | * Web application blueprint – We include a React web application pre-integrated out-of-the-box with best practices and tools such as React Bootstrap, React Router, Typescript, and more.
51 |
52 | **Infrastructure components**
53 |
54 | * Continuous deployment code pipeline – AWS CodePipeline and AWS CodeBuild help you build, test, and release your application code.
55 | * Serverless web application – Amazon CloudFront and Amazon S3 provide a globally-distributed application.
56 |
57 | You can choose to customize the template to create your own goals app, modify it to make a different type of simple notes or to-do application, or add onto it to make a completely different type of web application.
58 |
59 | Users can build on top of AWS Full-Stack Template to create any application they envision, whether a travel booking tool, a blog, or another web app. The **AWS Bookstore Demo App** (available at [https://github.com/aws-samples/aws-bookstore-demo-app](https://github.com/aws-samples/aws-bookstore-demo-app)) is just one full-fledged example of what you might create using AWS Full-Stack Template.
60 |
61 |
62 |
63 | ---
64 |
65 |
66 |
67 | ## Instructions
68 |
69 | ***IMPORTANT NOTE:** Creating this application in your AWS account will create and consume AWS resources, which **will cost money**. We estimate that running this demo application will cost **<$0.10/hour** with light usage. Be sure to shut down/remove all resources once you are finished to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down below).*
70 |
71 |
72 |
73 | ### Getting started
74 |
75 | To get the AWS Full-Stack Template up and running in your own AWS account, follow these steps (if you do not have an AWS account, please see [How do I create and activate a new Amazon Web Services account?](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/)):
76 |
77 | 1. Log into the [AWS console](https://console.aws.amazon.com/) if you are not already
78 | 2. Choose **Launch Stack** to open the AWS CloudFormation console and create a new stack.
79 | [](https://console.aws.amazon.com/cloudformation/home#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
80 | 3. Continue through the CloudFormation wizard steps
81 | 1. Name your stack, i.e. MyGoalsApp
82 | 2. Name your S3 bucket (must be lowercase and has to unique across all existing bucket names in Amazon S3). See [bucket naming rules](https://docs.aws.amazon.com/AmazonS3/latest/dev//BucketRestrictions.html#bucketnamingrules).
83 | 3. Provide a project name (must be lowercase, letters only, and **under ten characters**). This is used when naming your resources, e.g. tables, etc.).
84 | 4. After reviewing, check the blue box for creating IAM resources.
85 | 4. Choose **Create stack**. This will take ~15 minutes to complete.
86 | 5. Sign into your application
87 | 1. The output of the CloudFormation stack creation will provide a CloudFront URL (in the *Outputs* section of your stack details page). Copy and paste the CloudFront URL into your browser.
88 | 2. You can sign into your application by registering an email address and a password. Choose **Sign up to explore the demo** to register. The registration/login experience is run in your AWS account, and the supplied credentials are stored in Amazon Cognito.
89 | *Note: given that this is a demo application, we highly suggest that you do not use an email and password combination that you use for other purposes (such as an AWS account, email, or e-commerce site).*
90 | 3. Once you provide your credentials, you will receive a verification code at the email address you provided. Upon entering this verification code, you will be signed into the application.
91 |
92 |
93 |
94 | ### Cleaning up
95 |
96 | To tear down your application and remove all resources associated with the AWS Full-Stack Template, follow these steps:
97 |
98 | 1. Log into the AWS CloudFormation Console and find the stack you created for the demo app
99 | 2. Delete the stack
100 | 1. Double-check that the S3 buckets created for the stack were successfully removed.
101 |
102 | *Remember to shut down/remove all related resources once you are finished to avoid ongoing charges to your AWS account.*
103 |
104 |
105 |
106 | ---
107 |
108 |
109 |
110 | ## Architecture
111 |
112 | **Summary diagram**
113 |
114 | 
115 |
116 |
117 |
118 | **High-level, end-to-end diagram**
119 |
120 | 
121 |
122 |
123 |
124 | **Frontend**
125 |
126 | Build artifacts are stored in a S3 bucket where web application assets are maintained (web graphics, etc.). Amazon CloudFront caches the frontend content from S3, presenting the application to the user via a CloudFront distribution. The frontend interacts with Amazon Cognito and Amazon API Gateway only. Amazon Cognito is used for all authentication requests, whereas API Gateway (and Lambda) is used for all API calls to DynamoDB.
127 |
128 | **Backend**
129 |
130 | The core of the backend infrastructure consists of Amazon Cognito, Amazon DynamoDB, AWS Lambda, and Amazon API Gateway. The application leverages Amazon Cognito for user authentication, and Amazon DynamoDB to store all of the data for the goals.
131 |
132 | 
133 |
134 |
135 |
136 | **Developer Tools**
137 |
138 | The code is hosted in AWS CodeCommit. AWS CodePipeline builds the web application using AWS CodeBuild. After successfully building, CodeBuild copies the build artifacts into a S3 bucket where the web application assets are maintained (web graphics, etc.). Along with uploading to Amazon S3, CodeBuild invalidates the cache so users always see the latest experience when accessing the storefront through the Amazon CloudFront distribution. AWS CodeCommit. AWS CodePipeline, and AWS CodeBuild are used in the deployment and update processes only, not while the application is in a steady-state of use.
139 |
140 | 
141 |
142 |
143 |
144 | ---
145 |
146 |
147 |
148 | ## Implementation details
149 |
150 | *Note: The provided CloudFormation template contains only a portion of the resources needed to create and run the application. There are web assets (images, etc.), Lambda functions, and other resources called from the template to create the full experience. These resources are stored in a public-facing S3 bucket and referenced in the template.*
151 |
152 |
153 |
154 | ### Amazon DynamoDB
155 |
156 | The backend of the AWS Full-Stack Template leverages Amazon DynamoDB to enable dynamic scaling and the ability to add features as we improve our goals application. The application creates one table in DynamoDB: *Goals.* DynamoDB's primary key consists of a partition (hash) key and an optional sort (range) key. The primary key (partition and sort key together) must be unique.
157 |
158 | **Goals Table**
159 |
160 | ```js
161 | GoalsTable {
162 | userId: string (primary partition key)
163 | goalId: string (primary sort key)
164 | title: string
165 | content: string
166 | createdAt: number
167 | }
168 | ```
169 |
170 | The table's primary key is made up of the user ID (partition key) and the goal ID (sort key). This composite primary key allows us to use DynamoDB's scan capability - with only the user ID set - and return only the goals that that are in the user's account. It also allows us to query DynamoDB on a user ID and goal ID, returning goal details without additional data processing.
171 |
172 |
173 |
174 | ### Amazon API Gateway
175 |
176 | Amazon API Gateway acts as the interface layer between the frontend (Amazon CloudFront, Amazon S3) and AWS Lambda, which calls the backend (database). Below are the different APIs the application uses:
177 |
178 | **Goals (DynamoDB)**
179 |
180 | GET /goals (ListGoals)
181 | POST /goals (CreateGoal)
182 | GET /goals/{:id} (GetGoal)
183 | PUT /goals/{:id} (UpdateGoal)
184 | DELETE /goals/{:id} (DeleteGoal)
185 |
186 |
187 |
188 | ### AWS Lambda
189 |
190 | AWS Lambda is used in a few different places to run the application, as shown in the architecture diagram. The important Lambda functions that are deployed as part of the template are shown below, and available in the [functions](/functions) folder. In the cases where the response fields are blank, the application will return a statusCode 200 or 500 for success or failure, respectively.
191 |
192 |
193 |
194 | **ListGoals**
195 |
196 | Lambda function that lists the user's goals. The user's account ID is retrieved through the request context (does not need to be explicity provided in the request).
197 |
198 | ```js
199 | ListGoalsRequest {
200 |
201 | }
202 | ```
203 |
204 | ```js
205 | ListGoalsResponse {
206 | goals: goal[]
207 | }
208 | ```
209 |
210 | ```js
211 | goal {
212 | goalId: string
213 | title: string
214 | content: string
215 | createdAt: number
216 | }
217 | ```
218 |
219 |
220 |
221 | **GetGoal**
222 |
223 | Lambda function that returns the properties of a goal.
224 |
225 | ```js
226 | GetGoalRequest {
227 | goalId: string
228 | }
229 | ```
230 |
231 | ```js
232 | GetGoalResponse {
233 | goalId: string
234 | title: string
235 | content: string
236 | createdAt: number
237 | }
238 | ```
239 |
240 |
241 |
242 | **CreateGoal**
243 |
244 | Lambda function that creates a specified goal in the user's account.
245 |
246 | ```js
247 | CreateGoalRequest {
248 | title: string
249 | content: string
250 | }
251 | ```
252 |
253 | ```js
254 | CreateGoalResponse {
255 |
256 | }
257 | ```
258 |
259 |
260 |
261 | **DeleteGoal**
262 |
263 | Lambda function that removes a given goal from the user's account.
264 |
265 | ```js
266 | DeleteGoalRequest {
267 | goalId: string
268 | }
269 | ```
270 |
271 | ```js
272 | DeleteGoalResponse {
273 |
274 | }
275 | ```
276 |
277 |
278 |
279 | **UpdateGoal**
280 |
281 | Lambda function that updates the user's goal with a new title and/or content.
282 |
283 | ```js
284 | UpdateGoalRequest {
285 | title: string
286 | content: string
287 | }
288 | ```
289 |
290 | ```js
291 | UpdateGoalResponse {
292 |
293 | }
294 | ```
295 |
296 |
297 |
298 |
299 | ### AWS IAM
300 |
301 | The following IAM role (and included policies) is needed to run the application:
302 |
303 | **DynamoDbLambda**
304 | *AWS managed policy*
305 | AWSLambdaBasicExecutionRole
306 | *Inline policy*
307 | GoalsPolicy
308 | dynamodb:PutItem - table/Goals
309 | dynamodb:Query - table/Goals
310 | dynamodb:UpdateItem - table/Goals
311 | dynamodb:GetItem - table/Goals
312 | dynamodb:Scan - table/Goals
313 | dynamodb:DeleteItem - table/Goals
314 |
315 |
316 |
317 | ### Amazon Cognito
318 |
319 | Amazon Cognito handles user account creation and login for the goals application. For the purposes of this template, you can only browse your goals after login, which could represent the architecture of different types of web apps. Users can also choose to separate the architecture, where portions of the web app are publicly available and others are available upon login.
320 |
321 | User Authentication
322 | * Email address
323 |
324 | Amazon Cognito passes the CognitoIdentityID (which AWS Full-Stack Template uses as the Customer ID) for every user along with every request from Amazon API Gateway to Lambda, which helps the services authenticate against which user is doing what.
325 |
326 |
327 |
328 | ### Amazon CloudFront and Amazon S3
329 |
330 | Amazon CloudFront hosts the web application frontend that users interface with. This includes web assets like pages and images. For demo purposes, CloudFormation pulls these resources from S3.
331 |
332 |
333 |
334 | ### Amazon CloudWatch
335 |
336 | The capabilities provided by CloudWatch are not exposed to the end users of the web app, rather the developer/administrator can use CloudWatch logs, alarms, and graphs to track the usage and performance of their web application.
337 |
338 |
339 |
340 | ### AWS CodeCommit, AWS CodePipeline, AWS CodeBuild
341 |
342 | Similar to CloudWatch, the capabilities provided by CodeCommit, CodePipeline, and CodeBuild are not exposed to the end users of the web app. The developer/administrator can use these tools to help stage and deploy the application as it is updated and improved.
343 |
344 |
345 |
346 | ---
347 |
348 |
349 |
350 | ## Considerations for demo purposes
351 |
352 | 1. Web assets (pages, images, etc.) are pulled from a public S3 bucket via the CloudFormation template to create the frontend for the AWS Full-Stack Template. When building your own web application (or customizing this one), you will likely pull from your own S3 buckets. If you customize the lambda functions, you will want to store these separately, as well.
353 |
354 |
355 |
356 | ---
357 |
358 |
359 |
360 | ## Known limitations
361 |
362 | * The application was written for demonstration purposes and not for production use.
363 | * Validation is working properly from an end-user standpoint, but is not cleanly implemented. For instance, the submit buttons (to create a goal, update a goal, login, signup, and enter confirmation code) are disabled (as designed) when validation fails, but we added an extra helper function to support this. This issue occured when the app was upgraded to Bootstrap 4. We plan to fix this in a future revision.
364 | * In today's implementation, we have all of the Lambda functions associated with one IAM role. Ideally, each Lambda function would have its own scoped-down IAM role and policies.
365 | * Upon the first use of a Lambda function, cold start times can be slow. Once the Lambda function has been warmed up, performance will improve.
366 |
367 |
368 |
369 | ---
370 |
371 |
372 |
373 | ## Additions, forks, and contributions
374 |
375 | We are excited that you are interested in using the AWS Full-Stack Template! This is a great place to start if you are just beginning with AWS and want to get a functional application up and running. It is equally useful if you are looking for a sample full-stack application to fork off of and build your own custom application. We encourage developer participation via contributions and suggested additions. Of course you are welcome to create your own version!
376 |
377 | Please see the [contributing guidelines](CONTRIBUTING.md) for more information.
378 |
379 | For just one example of how you can build on top of this, check out **AWS Bookstore Demo App** (available at [https://github.com/aws-samples/aws-bookstore-demo-app](https://github.com/aws-samples/aws-bookstore-demo-app)), which was built on top of AWS Full Stack Template.
380 |
381 |
382 |
383 | ---
384 |
385 |
386 |
387 | ## Questions and contact
388 |
389 | For questions on the AWS Full-Stack Template, or to contact the team, please leave a comment on GitHub.
390 |
--------------------------------------------------------------------------------
/assets/index.html:
--------------------------------------------------------------------------------
1 |
This is a sample application that creates a simple CRUD (create, read, update, delete) app, and provides the foundational services, components, and plumbing needed to get a basic web application up and running. In this "goals" app, users can create goals, add descriptions, and update or remove their goals. You can get this sample application up and running in your own environment and learn more about the architecture of the app by looking at the github repository.
181 | );
182 | }
183 | }
--------------------------------------------------------------------------------
/assets/src/modules/signup/home.css:
--------------------------------------------------------------------------------
1 | .Home .lander {
2 | padding: 80px 0;
3 | text-align: center;
4 | }
5 |
6 | .Home .lander h1 {
7 | font-family: "Open Sans", sans-serif;
8 | font-weight: 600;
9 | }
10 |
11 | .Home .lander p {
12 | color: white;
13 | }
14 |
15 | .Home .goals h4 {
16 | font-family: "Open Sans", sans-serif;
17 | font-weight: 600;
18 | overflow: hidden;
19 | line-height: 1.5;
20 | white-space: nowrap;
21 | text-overflow: ellipsis;
22 | }
23 |
24 | .Home .goals table {
25 | margin-top: 30px;
26 | color: white;
27 | table-layout: fixed;
28 | }
29 |
30 | .Home .goals table a {
31 | color: #4eacf9;
32 | }
33 |
34 | .list-group {
35 | background: transparent !important;
36 | box-shadow: none !important;
37 | }
38 |
39 | .list-group-item h4 {
40 | font-size: 24px;
41 | font-weight: 100 !important;
42 | }
43 |
44 | .list-group-item-text {
45 | font-weight: 100 !important;
46 | font-size: 18px !important;
47 | padding-left: 30px;
48 | }
49 |
50 | .list-group-item-heading {
51 | font-weight: 200 !important;
52 | font-size: 24px !important;
53 | padding-left:30px;
54 | }
55 |
56 | .container-fluid {
57 | padding-right: 0 !important;
58 | }
59 |
60 | .description {
61 | overflow: hidden;
62 | text-overflow: ellipsis;
63 | white-space: nowrap;
64 | }
65 |
66 | .orange-link {
67 | color: #ff9900 !important;
68 | }
69 |
70 | .center-spinner {
71 | position: absolute;
72 | margin-top: 20px;
73 | left: 50%;
74 | }
--------------------------------------------------------------------------------
/assets/src/modules/signup/login.css:
--------------------------------------------------------------------------------
1 | @media all and (min-width: 480px) {
2 | .Login {
3 | max-width: 350px;
4 | height: 100%;
5 | padding: 30px;
6 | display: block;
7 | margin-left: auto;
8 | margin-right: auto;
9 | }
10 |
11 | .Login form {
12 | margin: 0 auto;
13 | max-width: 320px;
14 | }
15 | }
16 |
17 | .control-label{
18 | color: #fff !important;
19 | text-shadow: none !important;
20 | font-size: 20px !important;
21 | font-weight: 200;
22 | }
--------------------------------------------------------------------------------
/assets/src/modules/signup/signup.css:
--------------------------------------------------------------------------------
1 | @media all and (min-width: 480px) {
2 | .Signup {
3 | padding: 60px 0;
4 | }
5 |
6 | .Signup form {
7 | margin: 0 auto;
8 | max-width: 320px;
9 | }
10 | }
11 |
12 | .Signup form span.help-block {
13 | font-size: 14px;
14 | padding-bottom: 10px;
15 | color: #999;
16 | }
--------------------------------------------------------------------------------
/assets/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/assets/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.toString());
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl: string) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | if (installingWorker) {
62 | installingWorker.onstatechange = () => {
63 | if (installingWorker.state === 'installed') {
64 | if (navigator.serviceWorker.controller) {
65 | // At this point, the old content will have been purged and
66 | // the fresh content will have been added to the cache.
67 | // It's the perfect time to display a "New content is
68 | // available; please refresh." message in your web app.
69 | console.log('New content is available; please refresh.');
70 | } else {
71 | // At this point, everything has been precached.
72 | // It's the perfect time to display a
73 | // "Content is cached for offline use." message.
74 | console.log('Content is cached for offline use.');
75 | }
76 | }
77 | };
78 | }
79 | };
80 | })
81 | .catch(error => {
82 | console.error('Error during service worker registration:', error);
83 | });
84 | }
85 |
86 | function checkValidServiceWorker(swUrl: string) {
87 | // Check if the service worker can be found. If it can't reload the page.
88 | fetch(swUrl)
89 | .then(response => {
90 | // Ensure service worker exists, and that we really are getting a JS file.
91 | if (
92 | response.status === 404 ||
93 | response.headers.get('content-type')!.indexOf('javascript') === -1
94 | ) {
95 | // No service worker found. Probably a different app. Reload the page.
96 | navigator.serviceWorker.ready.then(registration => {
97 | registration.unregister().then(() => {
98 | window.location.reload();
99 | });
100 | });
101 | } else {
102 | // Service worker found. Proceed as normal.
103 | registerValidSW(swUrl);
104 | }
105 | })
106 | .catch(() => {
107 | console.log(
108 | 'No internet connection found. App is running in offline mode.'
109 | );
110 | });
111 | }
112 |
113 | export function unregister() {
114 | if ('serviceWorker' in navigator) {
115 | navigator.serviceWorker.ready.then(registration => {
116 | registration.unregister();
117 | });
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/assets/src/types/aws-amplify-react.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'aws-amplify-react';
2 |
--------------------------------------------------------------------------------
/assets/src/types/aws-amplify-ui.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@aws-amplify/ui'
--------------------------------------------------------------------------------
/assets/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "allowJs": false,
5 | "skipLibCheck": false,
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "preserve",
16 | "lib": ["es2015", "es2017", "dom", "esnext.asynciterable"],
17 | "typeRoots": [
18 | "./src/types",
19 | "./node_modules/@types"
20 | ]
21 | },
22 | "include": [
23 | "src"
24 | ],
25 | "exclude": [
26 | "./node_modules/*"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/extensions/cw-slack-chime/README.md:
--------------------------------------------------------------------------------
1 | ## CloudWatch to Slack/Chime notifications
2 |
3 | This extension enables you to send alerts from CloudWatch alarms to Slack or Amazon Chime and creates everything necessary in a single CloudFormation template. All you need is to specify your CloudWatch alarm name and a Slack or Chime webhook URL.
4 |
5 | As part of the CloudFormation template, this extension will setup an SNS Topic, attach it to your CloudWatch alarm, and create a Lambda function that will send alerts to your Slack or Amazon Chime channel.
6 |
7 |
8 | ## License Summary
9 |
10 | This sample code is made available under a modified MIT license. See the LICENSE file.
11 |
12 | ## Outline
13 |
14 | - [Overview](#overview)
15 | - [Instructions](#instructions)
16 | - [Getting started](#getting-started)
17 | - [Cleaning up](#cleaning-up)
18 | - [Architecture](#architecture)
19 | - [Implementation details](#implementation-details)
20 | - [Amazon SNS](#amazon-sns)
21 | - [AWS Lambda](#aws-lambda)
22 | - [Limitations](#limitations)
23 | - [Suggestions](#suggestions)
24 | - [Additions, forks, and contributions](#additions-forks-and-contributions)
25 |
26 | ## Overview
27 |
28 | The goal of the CloudWatch alarms to Slack or Chime extension is to simplify and automate the process of getting your CloudWatch alarms to post in your messaging application. The provided CloudFormation template automates the entire creation and deployment of the extension. The template will:
29 |
30 | * Create a SNS topic
31 | * Add the SNS topic as an action to your CloudWatch alarm
32 | * Create a Lambda function to process the alarm and call your webhook url
33 | * Subscribe the Lambda function to the SNS topic
34 |
35 | ## Instructions
36 |
37 | *IMPORTANT NOTE*: Creating this application in your AWS account will create and consume AWS resources, which will cost *money*. We estimate that running this extension will cost *<$0.05/year* with light usage. This will vary on the number of notifications from your CloudWatch alarm. AWS Lambda offers 1M free requests per month. Be sure to shut down/remove all resources once you are finished to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down below).
38 |
39 | ## Getting started
40 |
41 | To get the CloudWatch alarm to Slack or Chime extension up and running in your AWS account, follow these steps (if you do not have an AWS account, please see [How do I create and activate a new Amazon Web Services account?](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/)):
42 |
43 | 1. Log into the [AWS console](https://console.aws.amazon.com/) if you are not already
44 | 2. Choose one of the **Launch Stack** buttons below for your desired AWS region to open the AWS CloudFormation console and create a new stack. The CloudWatch alarm to Slack or Chime extension is supported in the following regions:
45 |
46 | Region name | Region code | Launch
47 | --- | --- | ---
48 | US East (N. Virginia) | us-east-1 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=CWToSlack&templateURL=https://aws-dmas.s3.amazonaws.com/alarm-to-alert/master.yaml)
49 | US West (Oregon) | us-west-2 | [](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=CWToSlack&templateURL=https://aws-dmas.s3.amazonaws.com/alarm-to-alert/master.yaml)
50 | EU (Ireland) | eu-west-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=CWToSlack&templateURL=https://aws-dmas.s3.amazonaws.com/alarm-to-alert/master.yaml)
51 | EU (Frankfurt) | eu-central-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=CWToSlack&templateURL=https://aws-dmas.s3.amazonaws.com/alarm-to-alert/master.yaml)
52 |
53 |
54 | 3. Continue through the CloudFormation wizard steps
55 | 1. Name your stack, e.g. CWToSlack
56 | 2. Enter the name of the CloudWatch alarm to integrate with
57 | 3. Specify the Slack or Amazon Chime webhook you want to send alerts to
58 | 4. After reviewing, check the blue box for creating IAM resources.
59 | 4. Choose Create stack. This will take ~2 minutes to complete.
60 | 5. You're done!
61 | 1. Want to recieve alerts for additional CloudWatch alarms, see the section below.
62 |
63 | ### Adding alerts for additional CloudWatch alarms
64 |
65 | Once the stack has been created, you can add additional CloudWatch alarms that you want to receive alerts on.
66 | 1. Go to the CloudWatch console and find the alarm you would like to subscribe to.
67 | 2. Add a Notification Action to your alarm and choose the SNS Topic that was created for you. The topic name will be [CloudwatchAlarm]-topic-[Slack/Chime]
68 |
69 |
70 | ### Cleaning up
71 |
72 | To tear down your extension and remove all resources associated with the AWS CloudWatch to Slack/Chime extension, follow these steps:
73 |
74 | 1. Log into the AWS CloudFormation Console and find the stack you created for the demo app
75 | 2. Delete the stack
76 |
77 | *Remember to shut down/remove all related resources once you are finished to avoid ongoing charges to your AWS account.*
78 |
79 | ## Architecture
80 |
81 | 
82 |
83 | ## Implementation details
84 |
85 | ### Amazon SNS
86 |
87 | Amazon Simple Notification Service is a managed pub/sub messaging service we use to receive alerts from CloudWatch and trigger the Lambda function that sends a message to the Slack/Chime channel.
88 |
89 | ### AWS Lambda
90 |
91 | AWS Lambda is used in a few different places to run the application. The Lambda functions that are deployed as part of the template are shown below, and available in the [functions](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions/cw-slack-chime/functions) folder.
92 |
93 | *[CloudWatch Alarm Name]-SendAlert* - Lambda function processes the CloudWatch alarm message and calls the configured Slack/Chime webhook URL.
94 | *[CloudWatch Alarm Name]-FunctionSetupAlert* - Lambda function that intakes the newly created SNS topic and adds it as an action to your CloudWatch alarm.
95 |
96 | ## Limitations
97 |
98 | - This extension only handles the SNS formatter for CloudWatch alerts. Want to contribute other formatters? Leave us a comment or a PR request!
99 | - Currenly, this extension is limited to support one webhook URL. To send to multiple webhook URLs, you will need to deploy the template again and specify the additional webhook URL.
100 |
101 | ## Suggestions
102 |
103 | Have other ideas for extensions we should build? Leave a comment on GitHub!
104 |
105 | ## Additions, forks, and contributions
106 |
107 | We are excited that you are interested in using [AWS Full-Stack Template](https://github.com/awslabs/aws-full-stack-template) and some of these extensions! This is a great place to start if you are just beginning with AWS and want to get a functional application up and running. It is equally useful if you are looking for a sample full-stack application to fork off of and build your own custom application. We encourage developer participation via contributions and suggested additions. Of course you are welcome to create your own version!
108 | Please see the [contributing guidelines](https://github.com/awslabs/aws-full-stack-template/blob/master/CONTRIBUTING.md) for more information.
109 |
110 | For just one example of how you can build on top of this, check out [AWS Bookstore Demo App](https://github.com/aws-samples/aws-bookstore-demo-app), which was built on top of AWS Full-Stack Template and the [Search API extension](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions/search-api).
111 |
--------------------------------------------------------------------------------
/extensions/cw-slack-chime/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-full-stack-template/ac72318d8689203b1c3fa8c71f3fed16e90248c3/extensions/cw-slack-chime/architecture.png
--------------------------------------------------------------------------------
/extensions/cw-slack-chime/functions/chime.js:
--------------------------------------------------------------------------------
1 | var https = require('https');
2 | var url = require('url');
3 |
4 | const CLOUDWATCH_URL = "https://console.aws.amazon.com/cloudwatch/home#alarmsV2:alarm/";
5 |
6 | exports.handler = function (event, context) {
7 | var record = event.Records[0]['Sns'];
8 |
9 | var message = getJsonFromString(record.Message);
10 |
11 | if (message == null) {
12 | context.fail('CloudWatch Alarm messsage is not valid JSON');
13 | return;
14 | }
15 |
16 | var trigger = message.Trigger;
17 | var data = "@Present AWS Alarm\n\n :rotating_light: " + message.AlarmName;
18 |
19 | data += "\n :earth_americas: " + message.Region + " :clock1: " + new Date(message.StateChangeTime).toGMTString();
20 |
21 | if (trigger != null) {
22 | data += "\n :information_source: " + trigger.Namespace;
23 | data += " | " + trigger.MetricName;
24 | }
25 |
26 | data += "\n :hash: " + message.AWSAccountId;
27 | data += "\n\n :link: " + CLOUDWATCH_URL + message.AlarmName;
28 |
29 | postMessage({ Content: data }, function (response) {
30 | if (response.statusCode == 200) {
31 | context.succeed();
32 | } else {
33 | context.fail("Error " + response.statusCode + " - " + response.statusMessage);
34 | }
35 | });
36 | };
37 |
38 | var getJsonFromString = function (str) {
39 | try {
40 | return JSON.parse(str);
41 | } catch (e) {
42 | return null;
43 | }
44 | };
45 |
46 | var postMessage = function (message, callback) {
47 | var body = JSON.stringify(message);
48 | var options = url.parse(process.env.WEBHOOK_URL);
49 | options.method = 'POST';
50 | options.headers = {
51 | 'Content-Type': 'application/json'
52 | };
53 |
54 | var postReq = https.request(options);
55 |
56 | postReq.write(body);
57 | postReq.end();
58 | };
59 |
--------------------------------------------------------------------------------
/extensions/cw-slack-chime/functions/modifyAlarm.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | const cw = new AWS.CloudWatch();
3 | const sns = new AWS.SNS();
4 | const https = require('https');
5 | const url = require('url');
6 |
7 | const SUCCESS = "SUCCESS";
8 | const FAILED = "FAILED";
9 |
10 | exports.handler = function (event, context, callback) {
11 | console.log('Received event:', JSON.stringify(event, null, 2));
12 |
13 | if (event.RequestType === 'Create') {
14 | describeAlarm().then(function (data) {
15 | modifyAlertAndSubscribe(event, callback, context, data);
16 | }).catch(function (err) {
17 | var responseData = { Error: "Failed to modify alarm and subscribe SNS topic " + err.message };
18 | sendResponse(event, callback, context.logStreamName, FAILED, responseData);
19 | }
20 | );
21 | } else { // Delete
22 | describeAlarm().then(function (data) {
23 | removeSubscription(event, callback, context, data);
24 | }).catch(function (err) {
25 | var responseData = { Error: "Failed to modify alarm and unsubscribe SNS topic " + err.message };
26 | sendResponse(event, callback, context.logStreamName, FAILED, responseData);
27 | }
28 | ); return;
29 | }
30 | };
31 |
32 | function describeAlarm() {
33 | const params = {
34 | AlarmNames: [process.env.ALARM_NAME],
35 | };
36 |
37 | return cw.describeAlarms(params).promise();
38 | }
39 |
40 | function modifyAlertAndSubscribe(event, callback, context, data) {
41 | if (data.MetricAlarms && data.MetricAlarms.length > 0) {
42 | const origAlarm = data.MetricAlarms[0];
43 |
44 | const params = {
45 | "Namespace": origAlarm.Namespace,
46 | "MetricName": origAlarm.MetricName,
47 | "Dimensions": origAlarm.Dimensions,
48 | "AlarmActions": [...origAlarm.AlarmActions, process.env.SNS_TOPIC],
49 | "ComparisonOperator": origAlarm.ComparisonOperator,
50 | "DatapointsToAlarm": origAlarm.DatapointsToAlarm,
51 | "EvaluationPeriods": origAlarm.EvaluationPeriods,
52 | "Period": origAlarm.Period,
53 | "Statistic": origAlarm.Statistic,
54 | "Threshold": origAlarm.Threshold,
55 | "AlarmDescription": origAlarm.AlarmDescription,
56 | "AlarmName": origAlarm.AlarmName
57 | };
58 |
59 |
60 | return cw.putMetricAlarm(params).promise().then(function (data) {
61 | console.log(data);
62 | confirmAlert(event, callback, context);
63 | }).catch(function (err) {
64 | var responseData = { Error: "Failed to modify alarm and subscribe SNS topic" + err };
65 | sendResponse(event, callback, context.logStreamName, FAILED, responseData);
66 | });
67 | }
68 | }
69 |
70 | function removeSubscription(event, callback, context, data) {
71 | if (data.MetricAlarms && data.MetricAlarms.length > 0) {
72 | const origAlarm = data.MetricAlarms[0];
73 |
74 | const params = {
75 | "Namespace": origAlarm.Namespace,
76 | "MetricName": origAlarm.MetricName,
77 | "Dimensions": origAlarm.Dimensions,
78 | "AlarmActions": origAlarm.AlarmActions.filter(item => item !== process.env.SNS_TOPIC),
79 | "ComparisonOperator": origAlarm.ComparisonOperator,
80 | "DatapointsToAlarm": origAlarm.DatapointsToAlarm,
81 | "EvaluationPeriods": origAlarm.EvaluationPeriods,
82 | "Period": origAlarm.Period,
83 | "Statistic": origAlarm.Statistic,
84 | "Threshold": origAlarm.Threshold,
85 | "AlarmDescription": origAlarm.AlarmDescription,
86 | "AlarmName": origAlarm.AlarmName
87 | };
88 |
89 |
90 | return cw.putMetricAlarm(params).promise().then(function (data) {
91 | sendResponse(event, callback, context.logStreamName, SUCCESS);
92 | }).catch(function (err) {
93 | var responseData = { Error: "Failed to modify alarm and unsubscribe SNS topic" + err };
94 | sendResponse(event, callback, context.logStreamName, FAILED, responseData);
95 | });
96 | }
97 | }
98 |
99 | function confirmAlert(event, callback, context) {
100 | const message = { "AlarmName": process.env.ALARM_NAME, "AlarmDescription": null, "AWSAccountId": "XXX", "NewStateValue": "ALARM", "NewStateReason": "", "StateChangeTime": new Date().toISOString(), "Region": process.env.AWS_REGION, "OldStateValue": "", "Trigger": { "MetricName": "Ready!", "Namespace": "AWS", "StatisticType": "", "Statistic": "", "Unit": null, "Dimensions": [{ "name": "Currency", "value": "USD" }], "Period": 86400, "EvaluationPeriods": 1, "ComparisonOperator": "GreaterThanThreshold", "Threshold": 1.0, "TreatMissingData": "", "EvaluateLowSampleCountPercentile": "", "Timestamp": "2017-10-30T13: 20: 35.855Z", "SignatureVersion": "1", "Signature": "", "SigningCertUrl": "", "UnsubscribeUrl": "", "MessageAttributes": null } };
101 |
102 | var params = {
103 | Message: JSON.stringify(message),
104 | TopicArn: process.env.SNS_TOPIC
105 | };
106 |
107 | sns.publish(params).promise().then(function (data) {
108 | sendResponse(event, callback, context.logStreamName, SUCCESS);
109 | }).catch(function (err) {
110 | var responseData = { Error: "Failed to send test message" + err };
111 | sendResponse(event, callback, context.logStreamName, FAILED, responseData);
112 | });
113 | }
114 |
115 | function sendResponse(event, callback, logStreamName, responseStatus, responseData) {
116 | const responseBody = JSON.stringify({
117 | Status: responseStatus,
118 | Reason: `See the details in CloudWatch Log Stream: ${logStreamName}`,
119 | PhysicalResourceId: logStreamName,
120 | StackId: event.StackId,
121 | RequestId: event.RequestId,
122 | LogicalResourceId: event.LogicalResourceId,
123 | Data: responseData,
124 | });
125 |
126 | console.log('RESPONSE BODY:\n', responseBody);
127 |
128 | const parsedUrl = url.parse(event.ResponseURL);
129 | const options = {
130 | hostname: parsedUrl.hostname,
131 | port: 443,
132 | path: parsedUrl.path,
133 | method: 'PUT',
134 | headers: {
135 | 'Content-Type': '',
136 | 'Content-Length': responseBody.length,
137 | },
138 | };
139 |
140 | const req = https.request(options, (res) => {
141 | console.log('STATUS:', res.statusCode);
142 | console.log('HEADERS:', JSON.stringify(res.headers));
143 | callback(null, 'Successfully modified alarm.');
144 | });
145 |
146 | req.on('error', (err) => {
147 | console.log('sendResponse Error:\n', err);
148 | callback(err);
149 | });
150 |
151 | req.write(responseBody);
152 | req.end();
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/extensions/cw-slack-chime/functions/slack.js:
--------------------------------------------------------------------------------
1 | var https = require('https');
2 | var url = require('url');
3 |
4 | const CLOUDWATCH_URL = "https://console.aws.amazon.com/cloudwatch/home#alarmsV2:alarm/";
5 |
6 | exports.handler = function (event, context) {
7 | var record = event.Records[0]['Sns'];
8 |
9 | var message = getJsonFromString(record.Message);
10 |
11 | if (message == null) {
12 | context.fail('CloudWatch Alarm messsage is not valid JSON');
13 | return;
14 | }
15 | var data = {
16 | attachments: [{
17 | title: "AWS Alarm",
18 | title_link: CLOUDWATCH_URL + message.AlarmName,
19 | fields: [
20 | {
21 | title: 'Alarm',
22 | value: message.AlarmName,
23 | short: true,
24 | }, {
25 | title: 'Time',
26 | value: new Date(message.StateChangeTime).toGMTString(),
27 | short: true,
28 | }, {
29 | title: 'Namespace',
30 | value: message.Trigger.Namespace,
31 | short: true,
32 | }, {
33 | title: 'MetricName',
34 | value: message.Trigger.MetricName,
35 | short: true,
36 | }, {
37 | title: 'Account',
38 | value: message.AWSAccountId,
39 | short: true,
40 | }, {
41 | title: 'Region',
42 | value: message.Region,
43 | short: true,
44 | }],
45 | }],
46 | };
47 |
48 | postMessage(data, function (response) {
49 | if (response.statusCode == 200) {
50 | context.succeed();
51 | } else {
52 | context.fail("Error " + response.statusCode + " - " + response.statusMessage);
53 | }
54 | });
55 | };
56 |
57 | var getJsonFromString = function (str) {
58 | try {
59 | return JSON.parse(str);
60 | } catch (e) {
61 | return null;
62 | }
63 | };
64 |
65 | var postMessage = function (message, callback) {
66 | var body = JSON.stringify(message);
67 | var options = url.parse(process.env.WEBHOOK_URL);
68 | options.method = 'POST';
69 | options.headers = {
70 | 'Content-Type': 'application/json'
71 | };
72 |
73 | var postReq = https.request(options);
74 |
75 | postReq.write(body);
76 | postReq.end();
77 | };
78 |
--------------------------------------------------------------------------------
/extensions/cw-slack-chime/master.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: 2010-09-09
2 | Parameters:
3 | CloudwatchAlarm:
4 | Description: Name of the Cloudwatch Alarm
5 | Type: String
6 | WebhookURL:
7 | Description: Webhook URL to call
8 | Type: String
9 | WebhookType:
10 | Type: String
11 | Default: Slack
12 | AllowedValues:
13 | - Slack
14 | - Chime
15 | Description: Select the destination of your webhook
16 | Conditions:
17 | IsSlack: !Equals [!Ref WebhookType, "Slack"]
18 | Mappings:
19 | S3Buckets:
20 | us-east-1:
21 | Bucket: aws-dmas
22 | us-west-2:
23 | Bucket: aws-dmas-us-west-2
24 | eu-central-1:
25 | Bucket: aws-dmas-eu-central-1
26 | eu-west-1:
27 | Bucket: aws-dmas-eu-west-1
28 | Constants:
29 | S3Keys:
30 | SendAlertCode: alarm-to-alert/functions/SendAlertCode.zip
31 | SetupAlertCode: alarm-to-alert/functions/SetupAlertCode.zip
32 | Resources:
33 | FunctionSendAlert:
34 | Type: 'AWS::Lambda::Function'
35 | DependsOn: WebhookRole
36 | Properties:
37 | FunctionName: !Sub '${CloudwatchAlarm}-SendAlert'
38 | Description: Sends message when CloudWatch Alarm is triggered
39 | Handler: !Join
40 | - ''
41 | - - !If [IsSlack, 'slack', 'chime']
42 | - '.handler'
43 | Runtime: nodejs8.10
44 | Role: !GetAtt
45 | - WebhookRole
46 | - Arn
47 | Timeout: 30
48 | MemorySize: 256
49 | Environment:
50 | Variables:
51 | WEBHOOK_URL: !Ref WebhookURL
52 | Code:
53 | S3Bucket: !FindInMap
54 | - S3Buckets
55 | - !Ref 'AWS::Region'
56 | - Bucket
57 | S3Key: !FindInMap
58 | - Constants
59 | - S3Keys
60 | - SendAlertCode
61 | SNSTopic:
62 | Type: 'AWS::SNS::Topic'
63 | Properties:
64 | TopicName: !Sub '${CloudwatchAlarm}-topic-${WebhookType}'
65 | Subscription:
66 | - Endpoint: !GetAtt FunctionSendAlert.Arn
67 | Protocol: lambda
68 | DependsOn: FunctionSendAlert
69 | SendAlertPermissions:
70 | Type: 'AWS::Lambda::Permission'
71 | Properties:
72 | Action: "lambda:InvokeFunction"
73 | Principal: "sns.amazonaws.com"
74 | SourceArn: !Ref SNSTopic
75 | FunctionName: !GetAtt FunctionSendAlert.Arn
76 | DependsOn: FunctionSendAlert
77 | WebhookRole:
78 | Type: 'AWS::IAM::Role'
79 | Properties:
80 | RoleName: !Sub '${CloudwatchAlarm}-WebhookRole'
81 | AssumeRolePolicyDocument:
82 | Version: 2012-10-17
83 | Statement:
84 | - Effect: Allow
85 | Principal:
86 | Service:
87 | - lambda.amazonaws.com
88 | Action:
89 | - 'sts:AssumeRole'
90 | ManagedPolicyArns:
91 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
92 | SetupAlertRole:
93 | Type: 'AWS::IAM::Role'
94 | DependsOn: SNSTopic
95 | Properties:
96 | RoleName: !Sub '${CloudwatchAlarm}-SetupAlertRole'
97 | AssumeRolePolicyDocument:
98 | Version: 2012-10-17
99 | Statement:
100 | - Effect: Allow
101 | Principal:
102 | Service:
103 | - lambda.amazonaws.com
104 | Action:
105 | - 'sts:AssumeRole'
106 | ManagedPolicyArns:
107 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
108 | Policies:
109 | - PolicyName: !Sub '${CloudwatchAlarm}-policy'
110 | PolicyDocument:
111 | Version: 2012-10-17
112 | Statement:
113 | - Effect: Allow
114 | Action:
115 | - 'cloudwatch:PutMetricAlarm'
116 | - 'cloudwatch:DescribeAlarms'
117 | Resource: !Join [ "", [ "arn:aws:cloudwatch:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":alarm:", !Ref CloudwatchAlarm ]]
118 | - Effect: Allow
119 | Action:
120 | - 'sns:Publish'
121 | Resource: !Ref SNSTopic
122 |
123 | FunctionSetupAlert:
124 | Type: 'AWS::Lambda::Function'
125 | DependsOn:
126 | - SetupAlertRole
127 | - SNSTopic
128 | Properties:
129 | FunctionName: !Sub '${CloudwatchAlarm}-FunctionSetupAlert'
130 | Description: Setup subscription to alarm with SNS and trigger Send to Alert Lambda function.
131 | Handler: index.handler
132 | Runtime: nodejs8.10
133 | Role: !GetAtt
134 | - SetupAlertRole
135 | - Arn
136 | Timeout: 30
137 | MemorySize: 256
138 | Environment:
139 | Variables:
140 | ALARM_NAME: !Ref CloudwatchAlarm
141 | SNS_TOPIC: !Ref SNSTopic
142 | Code:
143 | S3Bucket: !FindInMap
144 | - S3Buckets
145 | - !Ref 'AWS::Region'
146 | - Bucket
147 | S3Key: !FindInMap
148 | - Constants
149 | - S3Keys
150 | - SetupAlertCode
151 | SetupAlertPermissions:
152 | Type: 'AWS::Lambda::Permission'
153 | Properties:
154 | Action: "lambda:InvokeFunction"
155 | Principal: "cloudwatch.amazonaws.com"
156 | SourceArn: !Join [ "", [ "arn:aws:cloudwatch:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":alarm:", !Ref CloudwatchAlarm ]]
157 | FunctionName: !GetAtt FunctionSetupAlert.Arn
158 | DependsOn: FunctionSetupAlert
159 | RunSetupAlert:
160 | Type: 'Custom::CustomResource'
161 | Properties:
162 | ServiceToken: !GetAtt FunctionSetupAlert.Arn
163 | DependsOn: FunctionSetupAlert
164 | Metadata:
165 | 'AWS::CloudFormation::Designer':
166 | id: 63b08124-fccb-4874-ab13-ce6cfe6ce885
167 | Outputs:
168 | LambdaFunction:
169 | Description: Lambda function that sends a message when CloudWatch Alarm is triggered.
170 | Export:
171 | Name: !Sub '${CloudwatchAlarm}SendAlert'
172 | Value: !Ref FunctionSendAlert
173 |
--------------------------------------------------------------------------------
/extensions/search-api/README.md:
--------------------------------------------------------------------------------
1 | ## Search API Extension
2 |
3 | The Search API Extension enables you to add search functionality on top of your data in DynamoDB powered by Elasticsearch and API Gateway. The extension can be created with a single CloudFormation template!
4 |
5 | This extension takes in a DynamoDB table as a parameter. It will spin up an Elasticsearch cluster, stream changes from DynamoDB to Elasticsearch, and create a Search API. You can choose an existing API Gateway ID to integrate with or have the extension create a new one.
6 |
7 | Get started with adding search to your DynamoDB data below!
8 |
9 | ## License Summary
10 |
11 | ## Outline
12 |
13 | - [Overview](#overview)
14 | - [Instructions](#instructions)
15 | - [Getting started](#getting-started)
16 | - [Cleaning up](#cleaning-up)
17 | - [Architecture](#architecture)
18 | - [Implementation details](#implementation-details)
19 | - [Amazon DynamoDB](#amazon-dynamodb-streams)
20 | - [Amazon API Gateway](#amazon-api-gateway)
21 | - [AWS Lambda](#aws-lambda)
22 | - [Using the extension](#using-the-extension)
23 | - [Limitations](#limitations)
24 | - [Suggestions](#suggestions)
25 | - [Additions, forks, and contributions](#additions-forks-and-contributions)
26 |
27 | ## Overview
28 |
29 | The provided CloudFormation template automates the entire creation and deployment of the Search API. The template includes the following features:
30 |
31 | * Enables streams on your DynamoDB table to push updates
32 | * Creates an Elasticsearch cluster with best practices
33 | * Processes records and update Elasticsearch cluster via a Lambda function
34 | * Creates a Search API with Lambda
35 | * Creates or integrates with an existing API Gateway
36 |
37 | ## Instructions
38 |
39 | ***IMPORTANT NOTE**: Creating this application in your AWS account will create and consume AWS resources, which **will cost money**. We estimate that running this extension in development mode (t2.small) will cost **<$0.10/hour** with light usage. In production mode (m4.large), we estimate this will cost around **$0.20/hr** with an additional $0.20/hr for each additional instance. Be sure to shut down/remove all resources once you are finished to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down below).*
40 |
41 | ## Getting started
42 |
43 | To get the Search API Extension up and running in your AWS account, follow these steps (if you do not have an AWS account, please see [How do I create and activate a new Amazon Web Services account?](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/)):
44 |
45 | 1. Log into the [AWS console](https://console.aws.amazon.com/) if you are not already
46 | 2. Choose one of the **Launch Stack** buttons below for your desired AWS region to open the AWS CloudFormation console and create a new stack. The Search API extension is supported in the following regions:
47 |
48 | Region name | Region code | Launch
49 | --- | --- | ---
50 | US East (N. Virginia) | us-east-1 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
51 | US West (Oregon) | us-west-2 | [](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
52 | EU (Ireland) | eu-west-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
53 | EU (Frankfurt) | eu-central-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
54 |
55 | 3. Continue through the CloudFormation wizard steps
56 | 1. Name your stack, e.g. SearchAPI
57 | 2. Enter the name of the DynamoDB table to integrate with
58 | 3. Specify an existing API Gateway ID to integrate with, or we will auto-create an API Gateway Resource for you
59 | 4. After reviewing, check the blue box for creating IAM resources.
60 | 4. Choose Create stack. This will take ~15 minutes to complete.
61 | 5. After the stack completes, go to the Lambda console and find the Search function ([DynamoDBTableName]-Search)
62 | 1. Modify the fields array on line 24 with fields from your DynamoDB table to query on in the format of [column name].[column type] (S, N, BOOL, etc)
63 | 2. This allows us to achieve optimal performance as its best to query Elasticsearch on specific fields.
64 | 3. If you are deploying this on top of the Full Stack app template with the existing goals schema, no additional modification is needed.
65 |
66 | Note: As described in the [limitations](#limitations), this template does not backfill data to the Elasticsearch cluster. To test the extension, create a new item and see [Using the extension](#using-the-extension) for instructions on testing your Search API.
67 |
68 | ### Cleaning up
69 |
70 | To tear down your extension and remove all resources associated with the AWS Search API Extension, follow these steps:
71 |
72 | 1. Log into the AWS CloudFormation Console and find the stack you created for the Search API Extension
73 | 2. Delete the stack
74 |
75 | *Remember to shut down/remove all related resources once you are finished to avoid ongoing charges to your AWS account.*
76 |
77 | ## Architecture
78 |
79 | 
80 |
81 | ## Implementation details
82 |
83 | ### Amazon DynamoDB Streams
84 |
85 | Amazon DynamoDB Streams push updates to the *UpdateSearchCluster* Lambda function that updates the Amazon Elasticsearch cluster.
86 |
87 | ### Amazon API Gateway
88 |
89 | Amazon API Gateway acts as the interface layer between your frontend and AWS Lambda, which calls the backend (database). Below is the different APIs the extension creates:
90 |
91 | *Search*
92 |
93 | GET /search/{:q} (Search)
94 |
95 | ### AWS Lambda
96 |
97 | AWS Lambda is used in a few different places to run the application, as shown in the architecture diagram. The important Lambda functions that are deployed as part of the template are shown below, and available in the [functions](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions/search-api/functions) folder. In the cases where the response fields are blank, the application will return a statusCode 200 or 500 for success or failure, respectively.
98 |
99 | *Search* - Lambda function that returns a list of books based on provided search parameters in the request.
100 |
101 | *UpdateSearchCluster* - Lambda function that updates the Elasticsearch cluster when new items are added to the DynamoDB table.
102 |
103 | ### Amazon VPC
104 |
105 | The Elasticsearch cluster is secured in an Amazon VPC (Virtual Private Cloud) for production-level security. The Lambda functions interact with the cluster through an ENI (Elastic Network Interface) that is automatically setup for you.
106 |
107 | ## Using the extension
108 |
109 | 1. Head to the API Gateway console and click on your API Gateway resource
110 | 2. Select the search GET method
111 | 3. Click test and enter a search query q={query term}
112 |
113 | ## Limitations
114 |
115 | - This extension does not backfill your Elasticsearch cluster with previous data. Want to contribute? Leave us a comment or a PR request!
116 | - Upon the first use of a Lambda function, cold start times in a VPC can be slow. Once the Lambda function has been warmed up, performance will improve. Consider creating triggers to create your Lambda "warm" for production purposes.
117 |
118 | ## Suggestions
119 |
120 | Have other ideas for extensions we should build? Leave a comment on GitHub!
121 |
122 | ## Additions, forks, and contributions
123 |
124 | We are excited that you are interested in using [AWS Full-Stack Template](https://github.com/awslabs/aws-full-stack-template) and some of these extensions! This is a great place to start if you are just beginning with AWS and want to get a functional application up and running. It is equally useful if you are looking for a sample full-stack application to fork off of and build your own custom application. We encourage developer participation via contributions and suggested additions. Of course you are welcome to create your own version!
125 | Please see the [contributing guidelines](https://github.com/awslabs/aws-full-stack-template/blob/master/CONTRIBUTING.md) for more information.
126 |
127 | For just one example of how you can build on top of this, check out [AWS Bookstore Demo App](https://github.com/aws-samples/aws-bookstore-demo-app), which was built on top of AWS Full-Stack Template and the [Search API extension](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions/search-api).
128 |
--------------------------------------------------------------------------------
/extensions/search-api/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-full-stack-template/ac72318d8689203b1c3fa8c71f3fed16e90248c3/extensions/search-api/architecture.png
--------------------------------------------------------------------------------
/extensions/search-api/functions/searchCluster.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import json
3 | import os
4 | import requests
5 | from requests_aws4auth import AWS4Auth
6 |
7 | region = os.environ["REGION"]
8 | service = "es"
9 | credentials = boto3.Session().get_credentials()
10 | awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
11 |
12 | index = "lambda-index"
13 | type = "lambda-type"
14 | url = "https://" + os.environ["ESENDPOINT"] + "/_search" # ElasticSearch cluster URL
15 |
16 | def handler(event, context):
17 | # Put the user query into the query DSL for more accurate search results.
18 | query = {
19 | "size": 25,
20 | "query": {
21 | "bool" : {
22 | "must": {
23 | "match" : {
24 | "userId.S" : event["requestContext"]["identity"]["cognitoIdentityId"]
25 | }
26 | },
27 | "must": {
28 | "multi_match" : {
29 | "query" : event["queryStringParameters"]["q"],
30 | "fields" : ["title.S"] # TODO: Fill with columns to search on. Format: [column name].[column type]
31 | }
32 | }
33 | }
34 | }
35 | }
36 | print query
37 |
38 | # ES 6.x requires an explicit Content-Type header
39 | headers = { "Content-Type": "application/json" }
40 |
41 | # Make the signed HTTP request
42 | r = requests.get(url, auth=awsauth, headers=headers, data=json.dumps(query))
43 |
44 | # Create the response and add some extra content to support CORS
45 | response = {
46 | "statusCode": r.status_code,
47 | "headers": {
48 | "Access-Control-Allow-Origin": "*",
49 | "Access-Control-Allow-Credentials": True
50 | },
51 | "body": r.text
52 | }
53 |
54 | # Add the search results to the response
55 | print response
56 | return response
57 |
--------------------------------------------------------------------------------
/extensions/search-api/functions/updateSearchCluster.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import os
3 | import json
4 | from botocore.vendored import requests
5 | from requests_aws4auth import AWS4Auth
6 |
7 | region = os.environ["REGION"]
8 | service = "es"
9 | credentials = boto3.Session().get_credentials()
10 | awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)
11 |
12 | host = "https://" + os.environ["ESENDPOINT"] # the Amazon ElaticSearch domain, with https://
13 | index = "lambda-index"
14 | type = "lambda-type"
15 |
16 | url = host + "/" + index + "/" + type + "/"
17 |
18 | headers = { "Content-Type": "application/json" }
19 |
20 | # UpdateSearchCluster - Updates Elasticsearch as new items are added to DynamoDB
21 | def handler(event, context):
22 | count = 0
23 | for record in event["Records"]:
24 | # Generate a UUID from the item primary key for use as the Elasticsearch ID
25 | id = generateUUID(record)
26 |
27 | if record['eventName'] == 'REMOVE':
28 | r = requests.delete(url + id, auth=awsauth)
29 | else:
30 | document = record['dynamodb']['NewImage']
31 | r = requests.put(url + id, auth=awsauth, json=document, headers=headers)
32 | count += 1
33 |
34 | return str(count) + " records processed."
35 |
36 | def generateUUID(record):
37 | keys = record['dynamodb']['Keys']
38 |
39 | # If range key exists, concatenate hash and range key with -
40 | id = ""
41 | i = 0
42 | for key, value in keys.items():
43 | if (i > 0):
44 | id += "-"
45 | valueAttr = list(value.values());
46 | id += valueAttr[0]
47 | i += 1
48 |
49 | print(id)
50 | return id
51 |
--------------------------------------------------------------------------------
/extensions/search-api/master.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: 2010-09-09
2 | Parameters:
3 | DynamoDBTable:
4 | Description: Name of your existing DynamoDBTable
5 | Type: String
6 | ExistingAPIGatewayId:
7 | Description: OPTIONAL - Specify an existing API Gateway ID to integrate with or we will auto-create an API Gateway Resource for you.
8 | Type: String
9 | Conditions:
10 | IADRegion: !Equals [!Ref "AWS::Region", "us-east-1"]
11 | CreateNewAPIGateway: !Equals [ !Ref ExistingAPIGatewayId, "" ]
12 | Mappings:
13 |
14 | S3Buckets:
15 | us-east-1:
16 | Bucket: aws-dmas
17 | SeederFunctionBucket: fsd-aws-wildrydes-us-east-1
18 | us-west-2:
19 | Bucket: aws-dmas-us-west-2
20 | SeederFunctionBucket: fsd-aws-wildrydes-us-west-2
21 | eu-central-1:
22 | Bucket: aws-dmas-eu-central-1
23 | SeederFunctionBucket: fsd-aws-wildrydes-eu-central-1
24 | eu-west-1:
25 | Bucket: aws-dmas-eu-west-1
26 | SeederFunctionBucket: fsd-aws-wildrydes-eu-west-1
27 | Constants:
28 | S3Keys:
29 | EnableStreamsCode: ddb-es/functions/EnableStreamsCode.zip
30 | CreateESRoleCode: ddb-es/functions/CreateESRole.zip
31 | UpdateSearchCode: ddb-es/functions/UpdateSearchCode.zip
32 | SearchCode: ddb-es/functions/SearchCode.zip
33 | GetAPIGatewayInfoCode: ddb-es/functions/GetAPIGatewayInfoCode.zip
34 | Metadata:
35 | 'AWS::CloudFormation::Designer':
36 | 82b269e2-158a-4a8c-9262-b695e74e50ea:
37 | size:
38 | width: 60
39 | height: 60
40 | position:
41 | x: 251
42 | 'y': 103
43 | z: 0
44 | embeds: []
45 | Resources:
46 | FunctionEnableStreams:
47 | Type: 'AWS::Lambda::Function'
48 | Properties:
49 | FunctionName: !Sub '${DynamoDBTable}-EnableStreams'
50 | Description: Enable streams for DynamoDB table
51 | Handler: index.handler
52 | Runtime: nodejs12.x
53 | Role: !GetAtt
54 | - DynamoDBRole
55 | - Arn
56 | Timeout: 120
57 | Environment:
58 | Variables:
59 | TABLE_NAME: !Ref DynamoDBTable
60 | Code:
61 | S3Bucket: !FindInMap
62 | - S3Buckets
63 | - !Ref 'AWS::Region'
64 | - Bucket
65 | S3Key: !FindInMap
66 | - Constants
67 | - S3Keys
68 | - EnableStreamsCode
69 |
70 | RunEnableStreams:
71 | Type: 'Custom::CustomResource'
72 | Properties:
73 | ServiceToken: !GetAtt FunctionEnableStreams.Arn
74 | ParameterOne: Parameter to pass into Custom Lambda Function
75 | DependsOn:
76 | - FunctionEnableStreams
77 |
78 | # ---------- ROLE FOR DYNAMODB ---------
79 | DynamoDBRole:
80 | Type: 'AWS::IAM::Role'
81 | Properties:
82 | RoleName: !Sub '${DynamoDBTable}-StreamingRole'
83 | AssumeRolePolicyDocument:
84 | Version: 2012-10-17
85 | Statement:
86 | - Effect: Allow
87 | Principal:
88 | Service:
89 | - lambda.amazonaws.com
90 | Action:
91 | - 'sts:AssumeRole'
92 | ManagedPolicyArns:
93 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
94 | Policies:
95 | - PolicyName: EnableStreamsPolicy
96 | PolicyDocument:
97 | Version: 2012-10-17
98 | Statement:
99 | - Effect: Allow
100 | Action:
101 | - 'dynamodb:DescribeTable'
102 | - 'dynamodb:UpdateTable'
103 | Resource: !Join [ "", [ "arn:aws:dynamodb:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":table/", !Ref DynamoDBTable ]]
104 | # ---------- ROLE FOR CREATING ELASTICSEARCH ROLE ---------
105 | RoleForCreateESRoleFunction:
106 | Type: 'AWS::IAM::Role'
107 | Properties:
108 | RoleName: !Sub '${DynamoDBTable}-RoleForCreateESRoleFunction'
109 | AssumeRolePolicyDocument:
110 | Version: 2012-10-17
111 | Statement:
112 | - Effect: Allow
113 | Principal:
114 | Service:
115 | - lambda.amazonaws.com
116 | Action:
117 | - 'sts:AssumeRole'
118 | ManagedPolicyArns:
119 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
120 | Policies:
121 | - PolicyName: CreateRolePolicy
122 | PolicyDocument:
123 | Version: 2012-10-17
124 | Statement:
125 | - Effect: Allow
126 | Action:
127 | - 'iam:CreateServiceLinkedRole'
128 | Resource: 'arn:aws:iam::*:role/aws-service-role/es.amazonaws.com/AWSServiceRoleForAmazonElasticsearchService'
129 | CreateESRoleFunction:
130 | Properties:
131 | Code:
132 | S3Bucket: !FindInMap
133 | - S3Buckets
134 | - !Ref 'AWS::Region'
135 | - Bucket
136 | S3Key: !FindInMap
137 | - Constants
138 | - S3Keys
139 | - CreateESRoleCode
140 | Description: Create Elasticsearch role
141 | Handler: index.handler
142 | Role:
143 | 'Fn::GetAtt':
144 | - RoleForCreateESRoleFunction
145 | - Arn
146 | Runtime: nodejs12.x
147 | Timeout: 300
148 | Type: 'AWS::Lambda::Function'
149 | ESRoleCreator:
150 | Type: 'Custom::CustomResource'
151 | Properties:
152 | ServiceToken: !GetAtt CreateESRoleFunction.Arn
153 | ParameterOne: Parameter to pass into Custom Lambda Function
154 | DependsOn: CreateESRoleFunction
155 | # ---------- VPC - SUBNET - SECURITY GROUPS ---------
156 | VPC:
157 | Type: "AWS::EC2::VPC"
158 | Properties:
159 | CidrBlock: '172.31.0.0/16'
160 | VPCSubnetforES:
161 | Type: "AWS::EC2::Subnet"
162 | Properties:
163 | CidrBlock:
164 | Fn::Select:
165 | - 0
166 | - Fn::Cidr:
167 | - Fn::GetAtt: [VPC, CidrBlock]
168 | - 3
169 | - 8
170 | VpcId:
171 | Ref: VPC
172 | AvailabilityZone:
173 | Fn::Select:
174 | - 0
175 | - Fn::GetAZs:
176 | Ref: "AWS::Region"
177 | ElasticsearchDomain:
178 | Type: 'AWS::Elasticsearch::Domain'
179 | DependsOn:
180 | - ESRoleCreator
181 | # TODO: Dev and Prod recommended settings
182 | Properties:
183 | DomainName: !Ref DynamoDBTable
184 | ElasticsearchVersion: 6.5
185 | ElasticsearchClusterConfig:
186 | DedicatedMasterEnabled: 'false'
187 | InstanceCount: '1'
188 | ZoneAwarenessEnabled: 'false'
189 | InstanceType: t2.small.elasticsearch
190 | VPCOptions:
191 | SubnetIds:
192 | - Ref: VPCSubnetforES
193 | EBSOptions:
194 | EBSEnabled: true
195 | Iops: 0
196 | VolumeSize: 10
197 | VolumeType: gp2
198 | NodeToNodeEncryptionOptions:
199 | Enabled: true
200 | AccessPolicies:
201 | Version: 2012-10-17
202 | Statement:
203 | - Effect: Allow
204 | Principal:
205 | AWS: '*'
206 | Action:
207 | - 'es:*'
208 | Resource: !Join [ "", [ "arn:aws:es:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":domain/", !Ref DynamoDBTable, "/*" ]]
209 | AdvancedOptions:
210 | rest.action.multi.allow_explicit_index: true
211 |
212 | # ---------- LAMBDA FUNCTION AND ROLE FOR ELASTICSEARCH ---------
213 | StreamingAndSearchRole:
214 | Type: 'AWS::IAM::Role'
215 | Properties:
216 | RoleName: !Sub '${DynamoDBTable}-StreamingAndSearchRole'
217 | AssumeRolePolicyDocument:
218 | Version: 2012-10-17
219 | Statement:
220 | - Effect: Allow
221 | Principal:
222 | Service:
223 | - lambda.amazonaws.com
224 | Action:
225 | - 'sts:AssumeRole'
226 | Path: /
227 | ManagedPolicyArns:
228 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
229 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole'
230 | Policies:
231 | - PolicyName: !Sub '${DynamoDBTable}-lambda-policy'
232 | PolicyDocument:
233 | Version: 2012-10-17
234 | Statement:
235 | - Effect: Allow
236 | Action:
237 | - 'es:ESHttpPost'
238 | - 'es:ESHttpGet'
239 | Resource: !Join [ "", [ "arn:aws:es:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":domain/", !Ref ElasticsearchDomain, "/*"]]
240 | - Effect: Allow
241 | Action:
242 | - 'dynamodb:DescribeStream'
243 | - 'dynamodb:GetRecords'
244 | - 'dynamodb:GetShardIterator'
245 | - 'dynamodb:ListStreams'
246 | Resource: !Join [ "", [ "arn:aws:dynamodb:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":table/", !Ref DynamoDBTable, "/stream/*"]]
247 | UpdateSearchCluster:
248 | Type: 'AWS::Lambda::Function'
249 | DependsOn:
250 | - StreamingAndSearchRole
251 | - ElasticsearchDomain
252 | Properties:
253 | FunctionName: !Sub '${DynamoDBTable}-UpdateSearchCluster'
254 | Description: 'Update Elasticsearch cluster as books are added'
255 | Handler: index.handler
256 | Role: !GetAtt
257 | - StreamingAndSearchRole
258 | - Arn
259 | Runtime: python2.7
260 | Timeout: '60'
261 | VpcConfig:
262 | SecurityGroupIds:
263 | - Fn::GetAtt: [VPC, DefaultSecurityGroup]
264 | SubnetIds:
265 | - Ref: VPCSubnetforES
266 | Code:
267 | S3Bucket: !FindInMap
268 | - S3Buckets
269 | - !Ref 'AWS::Region'
270 | - Bucket
271 | S3Key: !FindInMap
272 | - Constants
273 | - S3Keys
274 | - UpdateSearchCode
275 | Environment:
276 | Variables:
277 | ESENDPOINT: !GetAtt
278 | - ElasticsearchDomain
279 | - DomainEndpoint
280 | REGION: !Ref 'AWS::Region'
281 | FunctionSearch:
282 | Type: 'AWS::Lambda::Function'
283 | DependsOn:
284 | - StreamingAndSearchRole
285 | - ElasticsearchDomain
286 | Properties:
287 | FunctionName: !Sub '${DynamoDBTable}-Search'
288 | Description: Lambda function to query ElasticSearch
289 | Handler: index.handler
290 | MemorySize: 256
291 | Role: !GetAtt
292 | - StreamingAndSearchRole
293 | - Arn
294 | Runtime: python2.7
295 | Timeout: '60'
296 | VpcConfig:
297 | SecurityGroupIds:
298 | - Fn::GetAtt: [VPC, DefaultSecurityGroup]
299 | SubnetIds:
300 | - Ref: VPCSubnetforES
301 | Code:
302 | S3Bucket: !FindInMap
303 | - S3Buckets
304 | - !Ref 'AWS::Region'
305 | - Bucket
306 | S3Key: !FindInMap
307 | - Constants
308 | - S3Keys
309 | - SearchCode
310 | Environment:
311 | Variables:
312 | ESENDPOINT: !GetAtt
313 | - ElasticsearchDomain
314 | - DomainEndpoint
315 | REGION: !Ref 'AWS::Region'
316 |
317 | # ---------- STREAMING TRIGGERS ---------
318 | DataTableStream:
319 | DependsOn:
320 | - UpdateSearchCluster
321 | - FunctionEnableStreams
322 | - RunEnableStreams
323 | Type: 'AWS::Lambda::EventSourceMapping'
324 | Properties:
325 | BatchSize: 1
326 | Enabled: true
327 | EventSourceArn: !GetAtt
328 | - RunEnableStreams
329 | - StreamArn
330 | FunctionName: !GetAtt
331 | - UpdateSearchCluster
332 | - Arn
333 | StartingPosition: TRIM_HORIZON
334 |
335 | # ---------- SEARCH API ---------
336 | FunctionSearchPermissions:
337 | Type: 'AWS::Lambda::Permission'
338 | Properties:
339 | Action: 'lambda:InvokeFunction'
340 | FunctionName: !Ref FunctionSearch
341 | Principal: apigateway.amazonaws.com
342 | SourceArn: !Join [ "", [ "arn:aws:execute-api:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId" , ":", !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId], "/*"]]
343 | Metadata:
344 | 'AWS::CloudFormation::Designer':
345 | id: 06a821b7-638e-471c-ab50-51710b73767c
346 | SearchApiResource:
347 | Type: 'AWS::ApiGateway::Resource'
348 | Properties:
349 | RestApiId: !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId]
350 | ParentId: !GetAtt
351 | - RunGetAPIGatewayInfo
352 | - RootResourceId
353 | PathPart: search
354 | SearchApiRequestGET:
355 | DependsOn:
356 | - FunctionSearchPermissions
357 | - FunctionSearch
358 | - SearchApiResource
359 | Type: 'AWS::ApiGateway::Method'
360 | Properties:
361 | AuthorizationType: AWS_IAM
362 | HttpMethod: GET
363 | Integration:
364 | Type: AWS_PROXY
365 | IntegrationHttpMethod: POST
366 | Uri: !Join
367 | - ''
368 | - - 'arn:aws:apigateway:'
369 | - !Ref 'AWS::Region'
370 | - ':lambda:path/2015-03-31/functions/'
371 | - !GetAtt
372 | - FunctionSearch
373 | - Arn
374 | - /invocations
375 | IntegrationResponses:
376 | - StatusCode: 200
377 | RequestParameters:
378 | method.request.querystring.q: false
379 | ResourceId: !Ref SearchApiResource
380 | RestApiId: !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId]
381 | MethodResponses:
382 | - StatusCode: 200
383 | ResponseModels:
384 | application/json: Empty
385 | SearchApiRequestOPTIONS:
386 | Type: 'AWS::ApiGateway::Method'
387 | Properties:
388 | ResourceId: !Ref SearchApiResource
389 | RestApiId: !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId]
390 | AuthorizationType: None
391 | HttpMethod: OPTIONS
392 | Integration:
393 | Type: MOCK
394 | IntegrationResponses:
395 | - ResponseParameters:
396 | method.response.header.Access-Control-Allow-Headers: >-
397 | 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
398 | method.response.header.Access-Control-Allow-Methods: '''GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH'''
399 | method.response.header.Access-Control-Allow-Origin: '''*'''
400 | ResponseTemplates:
401 | application/json: ''
402 | StatusCode: '200'
403 | PassthroughBehavior: WHEN_NO_MATCH
404 | RequestTemplates:
405 | application/json: '{"statusCode": 200}'
406 | MethodResponses:
407 | - ResponseModels:
408 | application/json: Empty
409 | ResponseParameters:
410 | method.response.header.Access-Control-Allow-Headers: true
411 | method.response.header.Access-Control-Allow-Methods: true
412 | method.response.header.Access-Control-Allow-Origin: true
413 | StatusCode: '200'
414 |
415 | # ---------- NEW API GATEWAY [OPTIONAL] ---------
416 | NewAPIGateway:
417 | Type: 'AWS::ApiGateway::RestApi'
418 | Condition: CreateNewAPIGateway
419 | Properties:
420 | Name: !Sub '${DynamoDBTable}-Search'
421 | Description: Search API
422 | FailOnWarnings: true
423 |
424 | APIDeployment:
425 | DependsOn:
426 | - SearchApiRequestGET
427 | - SearchApiRequestOPTIONS
428 | Type: 'AWS::ApiGateway::Deployment'
429 | Properties:
430 | Description: Prod deployment for API
431 | RestApiId: !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId]
432 | StageName: prod
433 |
434 | # ---------- RETRIEVE API GATEWAY INFO ---------
435 | FunctionGetAPIGatewayInfo:
436 | Type: 'AWS::Lambda::Function'
437 | Properties:
438 | FunctionName: !Sub '${DynamoDBTable}-APIGatewayInfo'
439 | Description: Retrieve info about API Gateway resource
440 | Handler: index.handler
441 | Runtime: nodejs12.x
442 | Role: !GetAtt
443 | - GetAPIGatewayInfoRole
444 | - Arn
445 | Timeout: 120
446 | Environment:
447 | Variables:
448 | REST_API_ID: !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId]
449 | Code:
450 | S3Bucket: !FindInMap
451 | - S3Buckets
452 | - !Ref 'AWS::Region'
453 | - Bucket
454 | S3Key: !FindInMap
455 | - Constants
456 | - S3Keys
457 | - GetAPIGatewayInfoCode
458 |
459 | RunGetAPIGatewayInfo:
460 | Type: 'Custom::CustomResource'
461 | Properties:
462 | ServiceToken: !GetAtt FunctionGetAPIGatewayInfo.Arn
463 | ParameterOne: Parameter to pass into Custom Lambda Function
464 | DependsOn:
465 | - FunctionGetAPIGatewayInfo
466 | GetAPIGatewayInfoRole:
467 | Type: 'AWS::IAM::Role'
468 | Properties:
469 | RoleName: !Sub '${DynamoDBTable}-APIGatewayInfo'
470 | AssumeRolePolicyDocument:
471 | Version: 2012-10-17
472 | Statement:
473 | - Effect: Allow
474 | Principal:
475 | Service:
476 | - apigateway.amazonaws.com
477 | - lambda.amazonaws.com
478 | Action:
479 | - 'sts:AssumeRole'
480 | ManagedPolicyArns:
481 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
482 | Policies:
483 | - PolicyName: GetAPIInfo
484 | PolicyDocument:
485 | Version: 2012-10-17
486 | Statement:
487 | - Effect: Allow
488 | Action:
489 | - 'apigateway:GET'
490 | Resource: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region", "::/restapis/", !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId], "/*"]]
491 | Outputs:
492 | ElasticsearchEndpoint:
493 | Description: The Elasticsearch domain endpoint that you use to submit search requests.
494 | Value: !GetAtt ElasticsearchDomain.DomainEndpoint
495 | SearchAPI:
496 | Description: Seach API endpoint.
497 | Export:
498 | Name: !Sub '${DynamoDBTable}SearchAPI'
499 | Value: !Join [ "", [ "https://", !If [CreateNewAPIGateway, !Ref NewAPIGateway, !Ref ExistingAPIGatewayId], ".execute-api.", !Ref "AWS::Region", ".amazonaws.com/prod"]]
500 |
--------------------------------------------------------------------------------
/functions/CreateGoal.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 |
3 | const dynamoDb = new AWS.DynamoDB.DocumentClient();
4 |
5 | exports.handler = (event, context, callback) => {
6 | // Request body is passed in as a JSON encoded string in 'event.body'
7 | const data = JSON.parse(event.body);
8 |
9 | const params = {
10 | TableName: process.env.TABLE_NAME, //[ProjectName]-Goals
11 | // 'Item' contains the attributes of the item to be created
12 | // - 'userId': user identities are federated through the
13 | // Cognito Identity Pool, we will use the identity id
14 | // as the user id of the authenticated user
15 | // - 'goalId': a unique identifier for the goal (uuid)
16 | // - 'title': user generated title for the goal (parsed from request body)
17 | // - 'content': user generated content for the goal (parsed from request body)
18 | // - 'createdAt': current Unix timestamp
19 | Item: {
20 | userId: event.requestContext.identity.cognitoIdentityId,
21 | goalId: context.awsRequestId,
22 | title: data.title,
23 | content: data.content,
24 | createdAt: Date.now()
25 | }
26 | };
27 |
28 | dynamoDb.put(params, (error, data) => {
29 | // Set response headers to enable CORS (Cross-Origin Resource Sharing)
30 | const headers = {
31 | "Access-Control-Allow-Origin": "*",
32 | "Access-Control-Allow-Credentials" : true
33 | };
34 |
35 | // Return status code 500 on error
36 | if (error) {
37 | const response = {
38 | statusCode: 500,
39 | headers: headers,
40 | body: error
41 | };
42 | callback(null, response);
43 | return;
44 | }
45 |
46 | // Return status code 200 on success
47 | const response = {
48 | statusCode: 200,
49 | headers: headers
50 | };
51 | callback(null, response);
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/functions/DeleteGoal.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 |
3 | const dynamoDb = new AWS.DynamoDB.DocumentClient();
4 |
5 | exports.handler = (event, context, callback) => {
6 | const params = {
7 | TableName: process.env.TABLE_NAME, //[ProjectName]-Goals,
8 | // 'Key' defines the partition key and sort key of the item to be removed
9 | // - 'userId': user identities are federated through the Cognito Identity
10 | // Pool, we will use the identity id as the user id of the
11 | // authenticated user
12 | // - 'goalId': a unique identifier for the goal (uuid)
13 | Key: {
14 | userId: event.requestContext.identity.cognitoIdentityId,
15 | goalId: event.pathParameters.id
16 | }
17 | };
18 |
19 | dynamoDb.delete(params, (error, data) => {
20 | // Set response headers to enable CORS (Cross-Origin Resource Sharing)
21 | const headers = {
22 | "Access-Control-Allow-Origin": "*",
23 | "Access-Control-Allow-Credentials" : true
24 | };
25 |
26 | // Return status code 500 on error
27 | if (error) {
28 | const response = {
29 | statusCode: 500,
30 | headers: headers,
31 | body: error
32 | };
33 | callback(null, response);
34 | return;
35 | }
36 |
37 | // Return status code 200 on success
38 | const response = {
39 | statusCode: 200,
40 | headers: headers,
41 | };
42 | callback(null, response);
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/functions/GetGoal.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 |
3 | const dynamoDb = new AWS.DynamoDB.DocumentClient();
4 |
5 | // GetGoal - Get goal information for a given user id
6 | exports.handler = (event, context, callback) => {
7 | const params = {
8 | TableName: process.env.TABLE_NAME, //[ProjectName]-Goals,
9 | // 'Key' defines the partition key and sort key of the item to be retrieved
10 | // - 'userId': user identities are federated through the Cognito Identity
11 | // Pool, we will use the identity id as the user id of the
12 | // authenticated user
13 | // - 'goalId': a unique identifier for the goal (uuid)
14 | Key: {
15 | userId: event.requestContext.identity.cognitoIdentityId,
16 | goalId: event.pathParameters.id
17 | }
18 | };
19 |
20 | dynamoDb.get(params, (error, data) => {
21 | // Set response headers to enable CORS (Cross-Origin Resource Sharing)
22 | const headers = {
23 | "Access-Control-Allow-Origin": "*",
24 | "Access-Control-Allow-Credentials" : true
25 | };
26 |
27 | // Return status code 500 on error
28 | if (error) {
29 | const response = {
30 | statusCode: 500,
31 | headers: headers,
32 | body: error
33 | };
34 | callback(null, response);
35 | return;
36 | }
37 |
38 | // Return status code 200 and the retrieved item on success
39 | const response = {
40 | statusCode: 200,
41 | headers: headers,
42 | body: JSON.stringify(data.Item)
43 | };
44 | callback(null, response);
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/functions/ListGoals.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 |
3 | const dynamoDb = new AWS.DynamoDB.DocumentClient();
4 |
5 | exports.handler = (event, context, callback) => {
6 | const params = {
7 | TableName: process.env.TABLE_NAME, //[ProjectName]-Goals
8 | // 'KeyConditionExpression' defines the condition for the query
9 | // - 'userId = :userId': only return items with matching 'userId' partition key
10 | // 'ExpressionAttributeValues' defines the value in the condition
11 | // - ':userId': defines 'userId' to be Identity Pool identity id of the
12 | // authenticated user
13 | KeyConditionExpression: "userId = :userId",
14 | ExpressionAttributeValues: {
15 | ":userId": event.requestContext.identity.cognitoIdentityId
16 | }
17 | };
18 |
19 | dynamoDb.query(params, (error, data) => {
20 | // Set response headers to enable CORS (Cross-Origin Resource Sharing)
21 | const headers = {
22 | "Access-Control-Allow-Origin": "*",
23 | "Access-Control-Allow-Credentials" : true
24 | };
25 |
26 | // Return status code 500 on error
27 | if (error) {
28 | const response = {
29 | statusCode: 500,
30 | headers: headers,
31 | body: error
32 | };
33 | callback(null, response);
34 | return;
35 | }
36 |
37 | // Return status code 200 and the retrieved items on success
38 | const response = {
39 | statusCode: 200,
40 | headers: headers,
41 | body: JSON.stringify(data.Items)
42 | };
43 | callback(null, response);
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/functions/UpdateGoal.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 |
3 | const dynamoDb = new AWS.DynamoDB.DocumentClient();
4 |
5 | exports.handler = (event, context, callback) => {
6 | // Request body is passed in as a JSON encoded string in 'event.body'
7 | const data = JSON.parse(event.body);
8 |
9 | const params = {
10 | TableName: process.env.TABLE_NAME, //[ProjectName]-Goals,
11 | // 'Key' defines the partition key and sort key of the item to be updated
12 | // - 'userId': user identities are federated through the Cognito Identity
13 | // Pool, we will use the identity id as the user id of the
14 | // authenticated user
15 | // - 'goalId': a unique identifier for the goal (uuid)
16 | Key: {
17 | userId: event.requestContext.identity.cognitoIdentityId,
18 | goalId: event.pathParameters.id
19 | },
20 | // 'UpdateExpression' defines the attributes to be updated
21 | // 'ExpressionAttributeValues' defines the value in the update expression
22 | // - ':title': defines 'title' to be the title parsed from the request body
23 | // - ':title': defines 'content' to be the content parsed from the request body
24 | UpdateExpression: "SET title = :title, content = :content",
25 | ExpressionAttributeValues: {
26 | ":title": data.title ? data.title : null,
27 | ":content": data.content ? data.content : null
28 | },
29 | ReturnValues: "ALL_NEW"
30 | };
31 |
32 | dynamoDb.update(params, (error, data) => {
33 | // Set response headers to enable CORS (Cross-Origin Resource Sharing)
34 | const headers = {
35 | "Access-Control-Allow-Origin": "*",
36 | "Access-Control-Allow-Credentials" : true
37 | };
38 |
39 | // Return status code 500 on error
40 | if (error) {
41 | console.log(error);
42 | const response = {
43 | statusCode: 500,
44 | headers: headers,
45 | body: error
46 | };
47 | callback(null, response);
48 | return;
49 | }
50 |
51 | // Return status code 200 on success
52 | const response = {
53 | statusCode: 200,
54 | headers: headers
55 | };
56 | callback(null, response);
57 | });
58 | };
59 |
--------------------------------------------------------------------------------
/readmeImages/ArchDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-full-stack-template/ac72318d8689203b1c3fa8c71f3fed16e90248c3/readmeImages/ArchDiagram.png
--------------------------------------------------------------------------------
/readmeImages/BackendDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-full-stack-template/ac72318d8689203b1c3fa8c71f3fed16e90248c3/readmeImages/BackendDiagram.png
--------------------------------------------------------------------------------
/readmeImages/DeveloperTools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-full-stack-template/ac72318d8689203b1c3fa8c71f3fed16e90248c3/readmeImages/DeveloperTools.png
--------------------------------------------------------------------------------
/readmeImages/SummaryDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amazon-archives/aws-full-stack-template/ac72318d8689203b1c3fa8c71f3fed16e90248c3/readmeImages/SummaryDiagram.png
--------------------------------------------------------------------------------
/readmeImages/images.md:
--------------------------------------------------------------------------------
1 | ## AWS Full-Stack Template README Images
2 |
3 | Images in this directory are for the purposes of showing up in the README.md file
4 |
--------------------------------------------------------------------------------
/workshop/readme.md:
--------------------------------------------------------------------------------
1 | # Workshop readme
2 |
3 | Hello workshoppers! We are excited for you to join us to learn how to build your own stack in just a few clicks! During this workshop, you will create two full-fledged web applications ([AWS Full-Stack Template](https://github.com/awslabs/aws-full-stack-template) and [AWS Bookstore Demo App](https://github.com/aws-samples/aws-bookstore-demo-app)), explore how they work, and learn how you might customize them for your own purposes.
4 |
5 | ## License Summary
6 |
7 | This sample code is made available under a modified MIT license. See the LICENSE file.
8 |
9 |
10 |
11 | ## Outline
12 |
13 | - [Overview](#overview)
14 | - [Prerequisite: AWS account](#prerequisite-aws-account)
15 | - [Part 1: AWS Full-Stack Template](#part-1-aws-full-stack-template)
16 | - [Section 1: Get to know the app](#section-1-get-to-know-the-app)
17 | - [Section 2: Explore the backend](#section-2-explore-the-backend)
18 | - [Section 3: Add on to the application](#section-3-add-on-to-the-application)
19 | - [Part 2: Extensions](#part-2-extensions)
20 | - [Section 1: Deploy a search extension to AWS Full-Stack Template](#section-1-deploy-a-search-extension-to-aws-full-stack-template)
21 | - [Section 2: Build your own extension! (optional)](#section-2-build-your-own-extension-optional)
22 | - [Part 3: AWS Bookstore Demo App](#part-3-aws-bookstore-demo-app)
23 | - [Section 1: Get to know the app](#section-1-get-to-know-the-app-1)
24 | - [Section 2: Explore the backend](#section-2-explore-the-backend-1)
25 | - [Section 3: Change the application (optional)](#section-3-change-the-application-optional)
26 | - [Part 4: Cleanup!](#part-4-cleanup)
27 | - [Part 5: Build on!](#part-5-build-on)
28 | - [Questions and contact](#questions-and-contact)
29 |
30 |
31 |
32 | ## Overview
33 |
34 | If you complete this workshop in it's entirety, good for you! We are very impressed. This workshop is not only designed to help you learn how to leverage these application templates, but also it is intended to leave you with ideas for how you might change and extend these (or other) applications in the future. There are several advanced sections to the workshop (marked as optional) that you can take home with you after the workshop session.
35 |
36 | ### Prerequisite: AWS account
37 |
38 | In order to maximize your time at the workshop, please make sure you have an AWS account set up. If you do not have an AWS account, please see [How do I create and activate a new Amazon Web Services account?](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/)
39 | * Your account must have sufficient privileges to create, modify, and delete resources. For the purposes of this workshop, **we recommend you have admin privileges**.
40 | * Ensure that you have < 5 VPCs and < 100 S3 buckets in your account.
41 |
42 | *Be sure to shut down/remove all resources once you are finished with the workshop to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down in **Part 4: Cleanup** below.*
43 |
44 |
45 |
46 | ---
47 |
48 |
49 |
50 | ## Part 1: AWS Full-Stack Template
51 |
52 | In this part of the workshop, you will spin up a complete instance of [AWS Full-Stack Template](https://github.com/awslabs/aws-full-stack-template), understand the application architecture, manually change some components, and explore some additions.
53 |
54 |
55 | ### Section 1: Get to know the app
56 |
57 | #### Step 1: Play with the deployed goals app
58 |
59 | AWS Full-Stack Template is a full-stack sample web application that creates a simple CRUD (create, read, update, delete) app, and provides the foundational services, components, and plumbing needed to get a basic web application up and running.
60 |
61 | **[Try out the deployed application here](https://d2k5b8bzo1vefz.cloudfront.net/)**! This will open a new window with a fully-deployed version of AWS Full-Stack Template. Sign up using an email address and password (choose **Sign up to explore the demo**).
62 | *Note: Given that this is a demo application, we highly suggest that you do not use an email and password combination that you use for other purposes (such as an AWS account, email, or e-commerce site).*
63 |
64 | Once you provide your credentials, you will receive a verification code at the email address you provided. Upon entering this verification code, you will be signed into the application.
65 |
66 | Add a goal and a description, and choose save. Try editing a goal, and then deleting a goal. Well done - you can CRUD!
67 |
68 | #### Step 2: Deploy AWS Full-Stack Template in your own AWS account
69 |
70 | ***IMPORTANT NOTE:** Creating this application in your AWS account will create and consume AWS resources, which **will cost money**. We estimate that running this demo application will cost **<$0.10/hour** with light usage. Be sure to shut down/remove all resources once you are finished with the workshop to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down in **Part 4: Cleanup** below.*
71 |
72 |
73 | To get the AWS Full-Stack Template up and running in your own AWS account, follow these steps:
74 |
75 | 1. Log into the [AWS console](https://console.aws.amazon.com/) if you are not already.
76 | *Note: If you are logged in as an IAM user, ensure your account has permissions to create and manage the necessary resources and components for this application.*
77 | 2. Choose one of the **Launch Stack** buttons below for your desired AWS region to open the AWS CloudFormation console and create a new stack. AWS Full-Stack Template is supported in the following regions:
78 |
79 | Region name | Region code | Launch
80 | --- | --- | ---
81 | US East (N. Virginia) | us-east-1 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
82 | US West (Oregon) | us-west-2 | [](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
83 | EU (Ireland) | eu-west-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
84 | EU (Frankfurt) | eu-central-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=MyGoalsApp&templateURL=https://s3.amazonaws.com/aws-fullstack-template/master-fullstack.yaml)
85 |
86 |
87 | 3. Continue through the CloudFormation wizard steps
88 | 1. Name your stack, e.g. MyGoalsApp
89 | 2. Provide a project name, e.g. goalsapp (must be lowercase, letters only, and **under twelve (12) characters**). This is used when naming your resources, e.g. tables, etc.
90 | 3. Choose next, then next.
91 | 4. On the last review page, check the blue box for creating IAM resources.
92 | 4. Choose **Create stack**. This will take ~15 minutes to complete.
93 |
94 | #### Step 3: Review architecture
95 |
96 | While the application is deploying in CloudFormation (should take ~15 minutes), browse the [readme](https://github.com/awslabs/aws-full-stack-template/blob/master/README.md) for AWS Full-Stack Template. The **Overview** section provides a basic understanding of what the application consists of. The remainder of the readme file dives deeper, including the **Architecture**, **Implementation details**, and **Considerations for demo purposes** sections. This familiarization with how the app is structured will come in handy once deployment is complete and we browse the components of the application.
97 |
98 | #### Step 4: Open the endpoint from CloudFormation
99 |
100 | Once the CloudFormation deployment is complete, check the status of the build in the [CodePipeline](https://console.aws.amazon.com/codesuite/codepipeline/pipelines) console. When that has succeeded, go back to the CloudFormation console, open the stack details page for your application, go to the **Outputs** table, and find the CloudFront URL. This is the public endpoint to your deployed application. Click the link to open and explore your brand-new goals app!
101 |
102 | Since this is a completely new instance of the application, your username and password that you used before in Step 1 won't work. Sign up for an account in your goals app, and test out the app! Test to make sure the email verification works, that you can create, update, and delete goals, etc. The registration/login experience is run in your AWS account, and the supplied credentials are stored in Amazon Cognito.
103 | *Note: If you are having issues with the sign up page using Firefox, try using Chrome.*
104 |
105 |
106 | ### Section 2: Explore the backend
107 |
108 | Now that the application is up and running, let's open the hood and play around with some of the backend components.
109 |
110 | #### Step 1: Change the details for one of your goals directly in DynamoDB
111 |
112 | Let's try changing one of the goals directly in DynamoDB. Open the [DynamoDB](https://console.aws.amazon.com/dynamodb) console, find the table that corresponds to this project (the table name will match the "ProjectName" you used when creating the stack in CloudFormation), and choose one of the goal items to modify. Change either the "title" string or the "content" string (which maps to the "Description" field in the app), and save your change in DynamoDB. Return to the goals app, and refresh the page. You should see your change reflected in the list.
113 |
114 | #### Step 2: Delete a user in Cognito
115 |
116 | Open the Cognito console, and choose "Manage User Pools." Look for the user pool that matches the "ProjectName" you used when creating the stack in CloudFormation and open this user pool. Choose "Users and groups" in the left navigation menu and choose one of your users. If you only signed up yourself, you can choose to delete your own user and then sign up again, or create another user from the frontend and delete that user. Next, choose "Disable user" and then click the "Delete user" button that appears. Tada! You are an amazing administrator.
117 |
118 | #### Step 3: Change the application into a notes application *(optional)*
119 | The goals application is not that far off from a simple notes application - it contains a title and a description field. You could easily turn the goals app into a notes-taking app by simply changing the titles, column headers, and buttons on the pages. Of course, that wouldn't change the backend (APIs, Lambda functions, and Tables), so if you were really passionate about changing the entire application into a notes app, you could make the requisite changes throughout.
120 |
121 | Hint: To get started with changing just the page-level items, open the CodeCommit console, choose "Repositories" and then "Code" from the left navigation. Navigate to /src/modules/signup/Home.tsx and /src/modules/goal/AddEditGoal.tsx. Choose "Edit" in the upper-right, make your changes, and then choose "Commit changes" at the bottom.
122 |
123 | Next, go to the pipeline that was set up for you in CodePipeline (choose "Pipeline" from the left navigation, then choose "Pipelines"). Open the pipeline associated with your goals app project, and choose "Release change."
124 |
125 | Watch the CodePipeline console for the build to complete (this will take a few minutes). When that has succeeded, refresh your goals app page. If you don't see the new column show up, do a **hard refresh** (ctrl/command + shift + R) in your browser. Cool!
126 |
127 | This entire step is optional, but illustrative if you want to learn more about the different backend pieces.
128 |
129 |
130 | ### Section 3: Add on to the application
131 |
132 | By this point, you should have a pretty good feel for the different architectural components of AWS Full-Stack Template. Since this is designed to provide you with the foundational services, components, and plumbing needed to get a basic web application up and running (it's a template, after all!) the next step is to add on to the application and make it your own.
133 |
134 | #### Step 1: Add a "Last updated" or "Last modified" parameter
135 |
136 | First, you'll need to modify the "UpdateGoal" Lambda function to add the current time for the update, which will pass this information to DynamoDB. To do this, open the [AWS Lambda console](https://console.aws.amazon.com/lambda) and search for "goal" which should return a list of the functions created in your account by the CloudFormation template. Choose the one that ends in "UpdateGoal," scroll down to the "Function code" card, and make your modifications. For extra credit, try to make the changes without looking at the answers provided in the expandable section below!
137 |
138 |
139 | Expand to see code (answers!)
140 |
141 |
142 | Add a parameter at the end of the "UpdateExpression" line and a new line under "ExpressionAttributeValues" so it looks like this:
143 | ```js
144 | UpdateExpression: "SET title = :title, content = :content, lastUpdated = :lastUpdated",
145 | ExpressionAttributeValues: {
146 | ":title": data.title ? data.title : null,
147 | ":content": data.content ? data.content : null,
148 | ":lastUpdated": Date.now()
149 | },
150 | ```
151 |
152 | Note we have added the "lastUpdated" parameter in **two** places. Also, don't forget the comma after the "null".
153 |
154 |
155 |
156 | Save your function. Since the function hasn't been used yet, there won't be any "lastUpdated" record created until the function is called, so go modify one of your goals in the app. You can change the title or description (up to you), and choose "Update goal."
157 |
158 | Next, let's go to DynamoDB to verify the new "lastUpdated" information is passed through. Open the DynamoDB console and find the table that corresponds to this goals project (the table name will match the "ProjectName" you used when creating the stack in CloudFormation). Open the item corresponding to the goal you updated, and you should now see the "lastUpdated" information in the item.
159 |
160 | #### Step 2: Add a "Last updated" column to the goals list page
161 |
162 | Now that you have a snazzy new parameter to track when your goal was last updated, the next step is to add this information to the goals list page. Here's a big hint: you only need to change one file. To get started, open the CodeCommit console, choose "Repositories" and then "Code" from the left navigation. Navigate to /src/modules/signup/Home.tsx. Choose "Edit" in the upper-right and make your modifications. For extra credit, try to make the changes without looking at the answers provided in the expandable section below!
163 |
164 |
165 | Expand to see code (answers!)
166 |
167 |
168 | Add new lines under "interface Goal," "return goalsList," and "Table" so it looks like this:
169 | ```js
170 | interface Goal {
171 | content: string;
172 | goalId: string;
173 | title: string;
174 | createdAt: Date;
175 | lastUpdated: Date; //<--This line!
176 | }
177 | ```
178 | ```js
179 | return goalsList.concat(goals).map(
180 | (goal, i) =>
181 |
198 | ```
199 |
200 |
201 |
202 | Make your changes and then choose "Commit changes" at the bottom.
203 |
204 | Next, go to the pipeline that was set up for you in CodePipeline (choose "Pipeline" from the left navigation, then choose "Pipelines"). Open the pipeline associated with your goals app project, and choose "Release change."
205 |
206 | Watch the CodePipeline console for the build to complete (this will take a few minutes). When that has succeeded, refresh your goals app page. If you don't see the new column show up, do a **hard refresh** (ctrl/command + shift + R) in your browser. Cool! What else can you add?
207 |
208 |
209 | ## End of Part 1
210 |
211 | You finished Part 1 of the workshop! If you do not plan to immediately continue to **Part 2: Extensions** or **Part 3: AWS Bookstore Demo App**, please skip ahead to **Part 4: Cleanup!**
212 |
213 |
214 |
215 | ---
216 |
217 |
218 |
219 | ## Part 2: Extensions
220 |
221 | In this part of the workshop, you will spin up the [Search API Extension](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions/search-api), explore the architecture of the Extension, and understand how to take a basic application like the Goals app in AWS Full-Stack Template and turn it into something different by adding components.
222 |
223 |
224 | ### Prerequisite
225 | This part requires [Part 1: Step 2](#step-2-deploy-aws-full-stack-template-in-your-own-aws-account) and [Part 1: Step 4](#step-4-open-the-endpoint-from-cloudformation) of the workshop to be completed first. This will ensure you have a functioning instance of AWS Full-Stack Template up and running that you can add on to.
226 |
227 | ### Section 1: Deploy a search extension to AWS Full-Stack Template
228 |
229 | #### Step 1: Deploy the search extension in your AWS account
230 |
231 | The Search API Extension enables you to add search functionality on top of your data in DynamoDB powered by Elasticsearch and API Gateway. The extension can be created with a single CloudFormation template! This extension takes in a DynamoDB table and API Gateway ID as parameters. It will spin up an Elasticsearch cluster, stream changes from DynamoDB to Elasticsearch, and create a Search API. Normally you can choose between integrating with an existing API Gateway ID or having the extension create a new one, but since we are building on top of AWS Full-Stack Template, we will integrate with the existing resources.
232 |
233 | ***IMPORTANT NOTE:** Creating this application in your AWS account will create and consume AWS resources, which **will cost money**. We estimate that running this demo application will cost **<$0.10/hour** with light usage. Be sure to shut down/remove all resources once you are finished with the workshop to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down in **Part 4: Cleanup** below.*
234 |
235 |
236 | To get the Search API Extension up and running in your AWS account, follow these steps:
237 |
238 | 1. Log into the [AWS console](https://console.aws.amazon.com/) if you are not already.
239 | *Note: If you are logged in as an IAM user, ensure your account has permissions to create and manage the necessary resources and components for this application.*
240 | 2. Choose one of the **Launch Stack** buttons below for your desired AWS region to open the AWS CloudFormation console and create a new stack. The Search API extension is supported in the following regions:
241 |
242 | Region name | Region code | Launch
243 | --- | --- | ---
244 | US East (N. Virginia) | us-east-1 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
245 | US West (Oregon) | us-west-2 | [](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
246 | EU (Ireland) | eu-west-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
247 | EU (Frankfurt) | eu-central-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=SearchAPI&templateURL=https://s3.amazonaws.com/aws-dmas/ddb-es/master.yaml)
248 |
249 | 3. Continue through the CloudFormation wizard steps
250 | 1. Name your stack, e.g. SearchAPI
251 | 2. Enter the DynamoDB table to integrate with. (You can find this in the CloudFormation console in the Resources tab of your deployed AWS Full-Stack Template. In the TGoals row, use the value in the **Physical ID** column.)
252 | 3. Specify the API Gateway ID to integrate with (You can find this in the CloudFormation console in the Resources tab of your deployed AWS Full-Stack Template. In the AppApi row, use the value in the **Physical ID** column.)
253 | 4. Choose next, then next.
254 | 5. On the last review page, check the blue box for creating IAM resources.
255 | 4. Choose Create stack. This will take ~15 minutes to complete.
256 |
257 | #### Step 2: Play with the search capability
258 |
259 | Now that you've deployed the search extension, let's test it to make sure search results are being returned properly.
260 |
261 | Before proceeding, either create a new goal or update one of your existing goals. When the search extension is deployed, it does not currently backfill the ES cluster with existing data from DynamoDB (pull request, please!). Creating a new goal or updating an existing one will stream the new/updated goal data to the ES cluster.
262 |
263 | Open the DynamoDB console and find the table associated with your goals app. Go to the items tab and choose the new/updated goal you just modified. Copy the userID and the title of the goal and make a note of them for use in a minute.
264 |
265 | In a new tab, open the Lambda console and look for the search function that was created as part of the extension you just ran in CloudFormation. It should be titled "yourextensionprojectname-Search." Open the Lambda function, choose the "Select a test event" dropdown towards the top, and choose "Configure test events." In the "Event template" dropdown, choose "Amazon API Gateway AWS Proxy." Here we are setting up a test for our search extension.
266 |
267 | Next, enter an event name (e.g. searchtest) and change the following lines of code from:
268 |
269 | ```js
270 | "queryStringParameters": {
271 | "foo": "bar"
272 | },
273 | ```
274 | to:
275 | ```js
276 | "queryStringParameters": {
277 | "q": "title of your goal"
278 | },
279 | ```
280 |
281 | and
282 |
283 | ```js
284 | "cognitoIdentityId": null,
285 | ```
286 | to:
287 | ```js
288 | "cognitoIdentityId": "your Cognito Identity ID",
289 | ```
290 |
291 | Where "title of your goal" is the goal title you made a note of earlier (or perhaps one word of your goal title), and "your Cognito Identity ID" is the userID (don't forget to add quotations around this parameter!). Entering the goal title simulates the string that you might put into a search bar, and passing in the userID ensures that only goals created or updated by the logged-in user are shown.
292 |
293 | Choose "Create" at the bottom of the window. Next, choose "Test" at the top of the window and verify that you see your goal information returned.
294 |
295 | #### Step 3: Modify the search capability
296 |
297 | With the search capability as-is, you are currently able to search by the goal title of one of your goals. Now we're going to add the ability to search by the goal description, as well.
298 |
299 | Open the AWS Lambda console to the same Lambda function from Step 2 (the one titled "yourextensionprojectname-Search"). Scroll down to the "Function code" card and make your modifications (hint: look for the big "# TODO" code comment). For extra credit, try to make the changes without looking at the answers provided in the expandable section below!
300 |
301 |
302 | Expand to see code (answers!)
303 |
304 |
305 | Add a parameter to the "fields" attribute (Line 30) under the "query" definition so it looks like the below. You can remove the code comment if you like.
306 | ```js
307 | "fields" : [“title.S”, “content.S"]
308 | ```
309 |
310 |
311 |
312 | Save your function. If you have not already created a new goal or updated a goal (i.e. in Step 2), go do this in the app now.
313 |
314 | Next, let's verify that we can now search by a goal description. Using the same dropdown you used to create your test event in Step 2, choose "Configure test events" again, and change the query from one of your goal titles to match one of your goal descriptions. Choose "Save" at the bottom of the window. Next, choose "Test" at the top of the window and verify that you see your goal information returned.
315 |
316 | #### Step 4: Add a search bar to the goals app *(optional)*
317 |
318 | If you would like, you can go even further by integrating the search functionality into the frontend application. Feel free to explore how AWS Bookstore Demo App did this, or experiment!
319 |
320 | *Hint:* Check out [SearchBar.tsx](https://github.com/aws-samples/aws-bookstore-demo-app/blob/master/assets/src/modules/search/searchBar/SearchBar.tsx) for a sample search implementation.
321 |
322 |
323 | ### Section 2: Build your own extension! *(optional)*
324 |
325 | In this section, you will try to build your own extension on top of an existing application.
326 |
327 | #### Step 1: Tear apart the search extension in Section 1
328 |
329 | In order to figure out how you can build a generic extension on top of an existing application (ideally any application), first start by tearing apart the search extension we discussed in Part 2, Section 1. Open the CloudFormation template and understand the different elements that are being deployed. Open the Lambda functions to explore the logic for how/when conditional checks are made. You can find all the resources for the search extension along with the CloudFormation template in the [**Extensions** folder](https://github.com/awslabs/aws-full-stack-template/tree/master/extensions/search-api).
330 |
331 | #### Step 2: Decide what type of extension you want to build
332 |
333 | We decided to build a search extension to add search functionality on top of any application. What will you come up with? You could build a caching extension, a visualization extension, or something else. What feature do you wish your web app had? Let your imagination run wild.
334 |
335 | #### Step 3: Build your extension
336 |
337 | Following the generic structure of the search extension, build a CloudFormation template that will add your extension on top of an existing application. Let us know what you built!
338 |
339 |
340 | ## End of Part 2
341 |
342 | You finished Part 2 of the workshop! If you would like to save your work, please do so! If you are interested in sharing your work, please see **Part 5: Build on!** for how you might contribute additions and ideas to either AWS Full-Stack Template or AWS Bookstore Demo App.
343 |
344 | Don't forget to finish **Part 4: Cleanup!** to avoid ongoing charges to your AWS account.
345 |
346 |
347 |
348 | ---
349 |
350 |
351 |
352 | ## Part 3: AWS Bookstore Demo App
353 |
354 | In this part of the workshop, you will spin up a complete instance of [AWS Bookstore Demo App](https://github.com/aws-samples/aws-bookstore-demo-app), understand the application architecture, manually change some components, and explore some additions. If you just completed **Part 1: AWS Full-Stack Template**, some of these instructions will be repetitive.
355 |
356 |
357 | ### Section 1: Get to know the app
358 |
359 | #### Step 1: Play with the deployed Bookstore
360 |
361 | AWS Bookstore Demo App is a full-stack sample web application that creates a storefront (and backend) for customers to shop for fictitious books. You can browse and search for books, look at recommendations and best sellers, manage your cart, checkout, view your orders, and more.
362 |
363 | **[Try out the deployed application here](https://d2h3ljlsmzojxz.cloudfront.net/)**! This will open a new window with a fully-deployed version of AWS Bookstore Demo App. Sign up using an email address and password (choose **Sign up to explore the demo**).
364 | *Note: Given that this is a demo application, we highly suggest that you do not use an email and password combination that you use for other purposes (such as an AWS account, email, or e-commerce site).*
365 |
366 | Once you provide your credentials, you will receive a verification code at the email address you provided. Upon entering this verification code, you will be signed into the application.
367 |
368 | View the different product categories, add some items to your cart, and checkout. Search for a few books by title, author, or category using the search bar. View the *Best Sellers* list, and see if you can move something to the top of the list by ordering a bunch of books. Finally, take a look at the social recommendations on the home page and the *Best Sellers* page. Look at you, savvy book shopper!
369 |
370 | #### Step 2: Deploy AWS Bookstore Demo App in your own AWS account
371 |
372 | ***IMPORTANT NOTE:** Creating this application in your AWS account will create and consume AWS resources, which **will cost money**. We estimate that running this demo application will cost **<$0.45/hour** with light usage. Be sure to shut down/remove all resources once you are finished with the workshop to avoid ongoing charges to your AWS account (see instructions on cleaning up/tear down in **Part 4: Cleanup** below.*
373 |
374 |
375 | To get the AWS Bookstore Demo App up and running in your own AWS account, follow these steps:
376 |
377 | 1. Log into the [AWS console](https://console.aws.amazon.com/) if you are not already.
378 | *Note: If you are logged in as an IAM user, ensure your account has permissions to create and manage the necessary resources and components for this application.*
379 | 2. Choose one of the **Launch Stack** buttons below for your desired AWS region to open the AWS CloudFormation console and create a new stack. AWS Bookstore Demo App is supported in the following regions:
380 |
381 | Region name | Region code | Launch
382 | --- | --- | ---
383 | US East (N. Virginia) | us-east-1 | [](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=MyBookstore&templateURL=https://s3.amazonaws.com/aws-bookstore-demo/master-fullstack.yaml)
384 | US West (Oregon) | us-west-2 | [](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=MyBookstore&templateURL=https://s3.amazonaws.com/aws-bookstore-demo/master-fullstack.yaml)
385 | EU (Ireland) | eu-west-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=MyBookstore&templateURL=https://s3.amazonaws.com/aws-bookstore-demo/master-fullstack.yaml)
386 | EU (Frankfurt) | eu-central-1 | [](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=MyBookstore&templateURL=https://s3.amazonaws.com/aws-bookstore-demo/master-fullstack.yaml)
387 |
388 |
389 | 3. Continue through the CloudFormation wizard steps
390 | 1. Name your stack, e.g. MyBookstore
391 | 2. Provide a project name (must be lowercase, letters only, and **under twelve (12) characters**). This is used when naming your resources, e.g. tables, search domain, etc.
392 | 3. Choose next, then next.
393 | 4. On the last review page, check the blue box for creating IAM resources.
394 | 4. Choose **Create stack**. This will take ~20 minutes to complete.
395 |
396 | #### Step 3: Review architecture
397 |
398 | While the application is deploying in CloudFormation (should take ~20 minutes), browse the [readme](https://github.com/aws-samples/aws-bookstore-demo-app/blob/master/README.md) for AWS Bookstore Demo App. The **Overview** section provides a basic understanding of what the application consists of. The remainder of the readme file dives deeper, including the **Architecture**, **Implementation details**, and **Considerations for demo purposes** sections. This familiarization with how the app is structured will come in handy once deployment is complete and we browse the components of the application.
399 |
400 | If you are coming from **Part 1** of this workshop, you should notice many similarities in the architecture. That is because AWS Bookstore Demo App was built on top of AWS Full-Stack Template. They contain the same developer infrastructure and frontend architectures, and a similar backend interface through Amazon API Gateway and AWS Lambda.
401 |
402 | What differences can you find in the architecture? How many DynamoDB tables are there in this app compared to the other? How many additional APIs are there?
403 |
404 | #### Step 4: Open the endpoint from CloudFormation
405 |
406 | Once the CloudFormation deployment is complete, check the status of the build in the [CodePipeline](https://console.aws.amazon.com/codesuite/codepipeline/pipelines) console. When that has succeeded, go back to the CloudFormation console, open the stack details page for your application, go to the **Outputs** table, and find the CloudFront URL. This is the public endpoint to your deployed application. Click the link to open and explore your brand-new Bookstore!
407 |
408 | Since this is a completely new instance of the application, your username and password that you used before in Step 1 won't work. Sign up for an account in your Bookstore, and test out the app! Test to make sure the email verification works, and run through some of the use cases from before like search, cart, ordering, and best sellers.
409 | *Note: If you are having issues with the sign up page using Firefox, try using Chrome.*
410 |
411 |
412 | ### Section 2: Explore the backend
413 |
414 | Now that the application is up and running, let's open the hood and play around with some of the backend componentry.
415 |
416 | #### Step 1: Change the author of a book in DynamoDB
417 |
418 | Since you've deployed this application, you deserve a reward! Let's make you the instant author of your favorite book in the bookstore. Open the DynamoDB console, find the BooksTable, find the name of the book you want to be the author of, and change the Author name. Refresh the bookstore page. Congratulations, you are an author in your own personal bookstore!
419 |
420 | #### Step 2: Update search to reflect your new authorship
421 |
422 | This might be the easiest step. After you updated the Author name, in the backend, DynamoDB Streams automatically pushed this information to the Elasticsearch cluster for your application. In the bookstore page, try searching for your name. The book you authored should be returned.
423 |
424 | #### Step 3: Manually edit the leaderboard/Best Sellers list
425 |
426 | First, make sure you have at least two books in your *Past orders* list with different order quantities. Unless you've already created multiple accounts for the boosktore, the books in your *Past orders* list will match the books in the *Best Sellers* list (since the bookstore started with no orders).
427 |
428 | If you're like us, you ordered over 1000 copies of one of the books to bump it to the top of the list. That was clearly an ordering error, so let's go correct the *Best Sellers* list. In order to do this, open the DynamoDB console and find the orders table. Next, find the entry for your big order and open the item to edit it. Open the "books" list, and then edit the "quantity" to be less than one of your other ordered books. Click save. Wait a few moments, and then go to the bookstore endpoint and refresh the page. You should notice the book moved in the list.
429 |
430 | Just like with updating the search experience, in the backend, DynamoDB Streams automatically pushed this information to ElastiCache for Redis, and the *Best Sellers* list was updated.
431 |
432 |
433 | ### Section 3: Change the application *(optional)*
434 |
435 | By this point, you should have a pretty good feel for the different architectural components of AWS Bookstore Demo App, and how it is just one example of what you might create with AWS Full-Stack Template. You can choose to start with the basic template and add on, or start with something more full-featured (like the bookstore) and change it. Either way, you have the foundational services, components, and plumbing you need to start building any web application.
436 |
437 | Here are a few ideas for how you might change AWS Bookstore Demo App. Note: many of these are advanced! You are also welcome to move on to **Part 3: Extensions** if you like.
438 |
439 | #### Option 1: Use it for your own storefront *(medium)*
440 |
441 | Perhaps you are interested in spinning up your own storefront. You will likely still want to have a product catalog, categories, search, best sellers, etc., so this app is a very good place to start.
442 |
443 | Let's imagine that you are going to sell furniture. Go into DynamoDB and change the structure (and content) of all of the tables to reflect that you are selling furniture. You'll also want to go to API Gateway and make the requisite changes. Finally, you can make changes to the frontend assets in CodeCommit to reflect your new look, furniture categories (instead of books), etc., and then push these changes through CodePipeline to update your storefront.
444 |
445 | #### Option 2: Add individual product pages *(hard)*
446 |
447 | One thing missing from the bookstore (or your furniture store) is individual pages for your products. Build out an additional table(s) or rows in DynamoDB to contain the structured information for your product pages.
448 |
449 | #### Option 3: Turn the bookstore into a blog *(hard)*
450 |
451 | Now that you've added individual product pages, you are not that far off from a robust blog application. The search experience will help viewers find your content, the *Best Sellers* list can effectively return your most popular posts, and the product catalog and product pages can be your blog entries! What else can you do?
452 |
453 | #### Option 4: Use a different database *(hardest)*
454 |
455 | Perhaps you want to use a different database for your product catalog (i.e. Amazon Aurora). Change the backend structure, API calls, and other lookups to use a different database structure and call the other service.
456 |
457 | #### Option 5: Build out the “Friends” functionality in the Bookstore *(hardest)*
458 |
459 | One of the main items that is missing from the current AWS Bookstore Demo App implementation is that there is no way to manage (view, add, remove) your friends. Build a friends management system and add it into the bookstore.
460 |
461 |
462 | ## End of Part 3
463 |
464 | You finished Part 3 of the workshop! If you would like to save your work, please do so! If you are interested in sharing your work, please see **Part 5: Build on!** for how you might contribute additions and ideas to either AWS Full-Stack Template or AWS Bookstore Demo App.
465 |
466 | Don't forget to finish **Part 4: Cleanup!** to avoid ongoing charges to your AWS account.
467 |
468 |
469 |
470 | ---
471 |
472 |
473 |
474 | ## Part 4: Cleanup!
475 |
476 | To make sure you don't continue to incur charges on your AWS account, make sure to tear down your applications and remove all resources associated with both AWS Full-Stack Template and AWS Bookstore Demo App.
477 |
478 | 1. Log into the [Amazon S3 Console](https://console.aws.amazon.com/s3) and delete the buckets created for this workshop.
479 | - There should be two buckets created for AWS Full-Stack Template and two buckets created for AWS Bookstore Demo App. The buckets will be titled "X" and "X-pipeline", where "X" is the name you specified in the CloudFormation wizard under the AssetsBucketName parameter.
480 | - *Note: Please be **very careful** to only delete the buckets associated with this workshop that you are absolutely sure you want to delete.*
481 | 2. Log into the AWS CloudFormation Console and find the stack(s) you created during this workshop
482 | 3. Delete the stack(s)
483 |
484 | *Remember to shut down/remove all related resources once you are finished to avoid ongoing charges to your AWS account.*
485 |
486 |
487 |
488 | ---
489 |
490 |
491 |
492 | ## Part 5: Build on!
493 |
494 | Now that you are an expert in creating full-stack applications in just a few clicks, go build something awesome! Let us know what you come up with. We encourage developer participation via contributions and suggested additions to both AWS Full-Stack Template and AWS Bookstore Demo App. Of course you are welcome to create your own version!
495 |
496 | Please see the [contributing guidelines](https://github.com/awslabs/aws-full-stack-template/blob/master/CONTRIBUTING.md) for more information.
497 |
498 |
499 |
500 | ---
501 |
502 |
503 |
504 | ## Questions and contact
505 |
506 | For questions on the AWS Full-Stack Template, or to contact the team, please leave a comment on GitHub.
507 |
--------------------------------------------------------------------------------