├── .github └── PULL_REQUEST_TEMPLATE.md ├── AddDestination.jsx ├── AllDestinations.jsx ├── AllDestinationsQuery.js ├── App.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Destinations.json ├── LICENSE ├── NOTICE ├── NewDestinationMutation.js ├── NewDestinationsSubscription.js ├── README.md ├── bucketpolicy.json ├── getWeatherData.js └── images ├── Step0.png ├── Step2.1.png ├── Step2.2.png ├── Step2.3.png ├── Step2.4.png ├── Step3.1.png ├── Step3.2.png ├── Step3.3.png ├── Step3.4.png ├── Step3.5.png ├── Step3.6.png ├── Step6.1.png ├── Step6.2.png ├── arch.png ├── region-selection.png └── twitch.png /.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 | -------------------------------------------------------------------------------- /AddDestination.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export default class AddDestination extends Component { 4 | 5 | constructor(props) { 6 | super(props); 7 | this.state = this.getInitialState(); 8 | } 9 | 10 | static defaultProps = { 11 | onAdd: () => null 12 | } 13 | 14 | getInitialState = () => ({ 15 | id: '', 16 | description: '', 17 | city: '', 18 | state: '', 19 | zip: '' 20 | }); 21 | 22 | handleChange = (field, event) => { 23 | const { target: { value } } = event; 24 | 25 | this.setState({ 26 | [field]: value 27 | }); 28 | } 29 | 30 | handleAdd = () => { 31 | const { id, description, city, state, zip } = this.state; 32 | 33 | this.setState(this.getInitialState(), () => { 34 | this.props.onAdd({ id, description, city, state, zip }); 35 | }); 36 | 37 | alert('Destination Added!'); 38 | } 39 | 40 | handleCancel = () => { 41 | this.setState(this.getInitialState()); 42 | } 43 | 44 | 45 | 46 | 47 | render() { 48 | 49 | return ( 50 | 51 | 52 |
53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 |
61 | 62 | 63 |
64 |
65 | 66 | 67 |
68 |
69 | 70 | 71 |
72 |
73 | 76 |
77 |
78 | ); 79 | } 80 | } -------------------------------------------------------------------------------- /AllDestinations.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | export default class AllDestinations extends Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = { 10 | editing: {} 11 | } 12 | } 13 | 14 | static defaultProps = { 15 | destinations: [], 16 | onDelete: () => null, 17 | onEdit: () => null, 18 | } 19 | 20 | renderOrEditdestination = (destination) => { 21 | const { editing } = this.state; 22 | 23 | const editData = editing[destination.id]; 24 | const isEditing = !!editData; 25 | 26 | const redStyle = { 27 | color: 'red' 28 | }; 29 | const blueStyle = { 30 | color: 'blue' 31 | }; 32 | 33 | var tempStyle ={ 34 | color: 'green' 35 | }; 36 | 37 | if(destination.conditions.current > 80){ 38 | tempStyle = redStyle; 39 | } 40 | else if(destination.conditions.current < 65){ 41 | tempStyle = blueStyle; 42 | } 43 | 44 | return ( 45 | 46 | !isEditing ? 47 | ( 48 | 49 |
50 |
{destination.description}
51 |
52 |

{(destination.id)}

53 |

{(destination.conditions.description)}

54 |

{destination.conditions.current} F

55 |
56 |
57 | 58 | 59 | ) : ( 60 | 61 | 62 | {destination.id} 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ) 74 | ); 75 | } 76 | 77 | componentWillMount(){ 78 | this.props.subscribeToDestinations(); 79 | } 80 | 81 | render() { 82 | const { destinations } = this.props; 83 | 84 | return ( 85 |
86 |
87 | {[].concat(destinations).sort((a, b) => b.id - a.id).map(this.renderOrEditdestination)} 88 |
89 |
90 | ); 91 | } 92 | } -------------------------------------------------------------------------------- /AllDestinationsQuery.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export default gql` 4 | query getAllDestinations{ 5 | getAllDestinations { 6 | id 7 | description 8 | state 9 | conditions { 10 | description 11 | current 12 | maxTemp 13 | minTemp 14 | } 15 | } 16 | }`; -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import { BrowserRouter as Router, Route } from 'react-router-dom'; 4 | import "semantic-ui-css/semantic.min.css"; 5 | 6 | import AllDestinations from "./Components/AllDestinations"; 7 | import AddDestination from "./Components/AddDestination"; 8 | import NewDestinationsSubscription from './Queries/NewDestinationsSubscription'; 9 | 10 | import AWSAppSyncClient from "aws-appsync"; 11 | import { Rehydrated } from 'aws-appsync-react'; 12 | import { AUTH_TYPE } from "aws-appsync/lib/link/auth-link"; 13 | import { graphql, ApolloProvider, compose } from 'react-apollo'; 14 | import * as AWS from 'aws-sdk'; 15 | import AppSync from './aws-exports.js'; 16 | import AllDestinationsQuery from './Queries/AllDestinationsQuery'; 17 | import NewDestinationMutation from './Queries/NewDestinationMutation'; 18 | 19 | const client = new AWSAppSyncClient({ 20 | url: AppSync.aws_appsync_graphqlEndpoint, 21 | region: AppSync.aws_appsync_region, 22 | auth: { 23 | type: AUTH_TYPE.API_KEY, 24 | apiKey: AppSync.aws_appsync_apiKey, 25 | 26 | // type: AUTH_TYPE.AWS_IAM, 27 | // Note - Testing purposes only 28 | /*credentials: new AWS.Credentials({ 29 | accessKeyId: AWS_ACCESS_KEY_ID, 30 | secretAccessKey: AWS_SECRET_ACCESS_KEY 31 | })*/ 32 | 33 | // Amazon Cognito Federated Identities using AWS Amplify 34 | //credentials: () => Auth.currentCredentials(), 35 | 36 | // Amazon Cognito user pools using AWS Amplify 37 | // type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS, 38 | // jwtToken: async () => (await Auth.currentSession()).getIdToken().getJwtToken(), 39 | }, 40 | options: { 41 | fetchPolicy: 'cache-and-network' 42 | }, 43 | disableOffline:false 44 | }); 45 | 46 | 47 | const appHeaderStyle = { 48 | marginBottom: '20px' 49 | }; 50 | 51 | const headerStyle = { 52 | color: 'white', 53 | padding: '10px' 54 | } 55 | 56 | const Home = () => ( 57 | 58 |
59 |
60 |

61 | logo 62 | Welcome to Serverless Bytes #3: Serverless Web App powered by AWS AppSync 63 |

64 |
65 | 66 |
67 | 68 |

69 | 70 | Travel Destinations and Current Conditions 71 |

72 | 73 |

74 | 75 | Create a destination 76 |

77 | 78 |
79 |
80 | 81 | ); 82 | 83 | const App = () => ( 84 | 85 |
86 | 87 |
88 |
89 | ); 90 | 91 | const AllDestinationsWithData = compose( 92 | graphql(AllDestinationsQuery, { 93 | options: { 94 | fetchPolicy: 'cache-and-network' 95 | }, 96 | props: (props) => ({ 97 | destinations: props.data.getAllDestinations, 98 | 99 | // START - NEW PROP : 100 | subscribeToDestinations: params => { 101 | props.data.subscribeToMore({ 102 | document: NewDestinationsSubscription, 103 | updateQuery: (previousResult, { subscriptionData, variables }) => { 104 | // Perform updates on previousResult with subscriptionData 105 | console.log(previousResult); 106 | 107 | var newDestination = subscriptionData.data.newDestination; 108 | console.log (newDestination); 109 | 110 | const newObj = {}; 111 | newObj.getAllDestinations = [newDestination, ...previousResult.getAllDestinations]; 112 | console.log(newObj); 113 | 114 | return newObj; 115 | } 116 | 117 | /*(prev, { subscriptionData: {data: {newDestination}} }) => ({ 118 | ...prev, 119 | getAllDestinations: { getAllDestinations: [newDestination, prev.getAllDestinations.filter(d => d.id != newDestination.id)] , __typename: 'Destinations'} 120 | })*/ 121 | }); 122 | } 123 | // END - NEW PROP 124 | 125 | }) 126 | }) 127 | )(AllDestinations); 128 | 129 | const NewDestinationWithData = graphql(NewDestinationMutation, { 130 | props: (props) => ({ 131 | onAdd: destination => props.mutate({ 132 | variables: destination, 133 | optimisticResponse: () => ({ addDestination: { ...destination, __typename: 'Destination', version: 1 } }), 134 | }) 135 | }), 136 | options: { 137 | //refetchQueries: [{ query: AllDestinationsQuery }], 138 | update: (dataProxy, { data: { addDestination } }) => { 139 | const query = AllDestinationsQuery; 140 | const data = dataProxy.readQuery({ query }); 141 | 142 | data.getAllDestinations.destinations.push(addDestination); 143 | 144 | dataProxy.writeQuery({ query, data }); 145 | }, 146 | fetchPolicy: 'cache-and-network', 147 | disableOffline:false 148 | } 149 | })(AddDestination); 150 | 151 | const WithProvider = () => ( 152 | 153 | 154 | 155 | 156 | 157 | ); 158 | 159 | export default WithProvider; -------------------------------------------------------------------------------- /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/aws-samples/aws-serverless-appsync-app/issues), or [recently closed](https://github.com/aws-samples/aws-serverless-appsync-app/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/aws-samples/aws-serverless-appsync-app/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/aws-samples/aws-serverless-appsync-app/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 | -------------------------------------------------------------------------------- /Destinations.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSync-Destinations": [ 3 | { 4 | "PutRequest": { 5 | "Item": { 6 | "id": {"S":"a5210bf8-3642-40ac-b68a-05fe0ac0xxxx"}, 7 | "city": {"S":"New York"}, 8 | "state": {"S":"New York"}, 9 | "zip": {"S":"10118"}, 10 | "description": {"S":"Empire State Building"} 11 | } 12 | } 13 | }, 14 | { 15 | "PutRequest": { 16 | "Item": { 17 | "id": {"S":"b7214nf8-4242-40nc-b68a-15f212xxxxx"}, 18 | "city": {"S":"Orlando"}, 19 | "state": {"S":"Florida"}, 20 | "zip": {"S":"32830"}, 21 | "description": {"S":"Disney World"} 22 | } 23 | } 24 | }, 25 | { 26 | "PutRequest": { 27 | "Item": { 28 | "id": {"S":"c3114nf8-5212-30nc-3d8a-yhf21cxxxxx"}, 29 | "city": {"S":"Paradise"}, 30 | "state": {"S":"Nevada"}, 31 | "zip": {"S":"89109"}, 32 | "description": {"S":"Las Vegas Strip"} 33 | } 34 | } 35 | }, 36 | { 37 | "PutRequest": { 38 | "Item": { 39 | "id": {"S":"d5113ma8-5512-60yc-3a5a-whf21zxxxxx"}, 40 | "city": {"S":"Philadelphia"}, 41 | "state": {"S":"Pennsylvania"}, 42 | "zip": {"S":"19106"}, 43 | "description": {"S":"Liberty Bell"} 44 | } 45 | } 46 | }, 47 | { 48 | "PutRequest": { 49 | "Item": { 50 | "id": {"S":"e7813kl8-8512-61cc-2q5a-poc33cxxxxx"}, 51 | "city": {"S":"Yellowstone"}, 52 | "state": {"S":"Wyoming"}, 53 | "zip": {"S":"82190"}, 54 | "description": {"S":"Yellowstone National Park"} 55 | } 56 | } 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | DestinationsAppSyncWorkshop 2 | Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -------------------------------------------------------------------------------- /NewDestinationMutation.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export default gql` 4 | mutation addDestination($id: ID!, $description: String!, $city: String!, $state: String!, $zip: String!){ 5 | addDestination( 6 | id: $id 7 | description: $description 8 | state: $state 9 | city: $city 10 | zip: $zip 11 | ){ 12 | __typename 13 | id 14 | description 15 | state 16 | conditions{ 17 | description 18 | __typename 19 | maxTemp 20 | minTemp 21 | current 22 | } 23 | } 24 | }`; -------------------------------------------------------------------------------- /NewDestinationsSubscription.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export default gql` 4 | subscription NewDestinationSub { 5 | newDestination { 6 | __typename 7 | id 8 | description 9 | state 10 | conditions { 11 | __typename 12 | description 13 | current 14 | maxTemp 15 | minTemp 16 | } 17 | } 18 | }`; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serverless Web Application with AppSync Workshop 2 | 3 | Serverless Bytes | Building a Serverless GraphQL App 4 |
5 | Follow along this workshop on YouTube. 6 | 7 | This workshop shows you how easy it is to build a data driven web applications all with no servers. You will build a serverless web application that lets users search for popular tourist destinations. The application will provide real-time weather analysis of the indexed destinations. 8 | 9 | You will host your web application's static assets on Amazon S3 and use S3 to deliver the web application to your users. The application will integrate with AWS AppSync to provide real-time data from multiple data sources via GraphQL technology. Destination data will be stored in Amazon DynamoDB and AWS Lambda will query for real time weather information. AppSync will make it easy to access this data and provide the exact information our application needs. 10 | 11 | The application architecture uses [Amazon S3](https://aws.amazon.com/s3/) to host the static web resources including our ReactJS based frontend (HTML, CSS, JavaScript, and image files). The application is loaded by the user's browser and interacts with our API layer built using, [AWS AppSync](https://aws.amazon.com/appsync/). AppSync provides realtime query capability to search for the list of travel destinations stored in [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). AppSync makes it easy to combine data from [AWS Lambda](https://aws.amazon.com/lambda/) to provide realtime weather information for each destination. 12 | 13 | See the diagram below for a depiction of the complete architecture: 14 | 15 | ![App Architecture](images/arch.png) 16 | 17 | ## Prerequisites 18 | 19 | 1. Signup for a free OpenWeatherApp account here: https://openweathermap.org/appid. Make sure you note the assigned api key. 20 | 2. [Provision](https://docs.aws.amazon.com/cloud9/latest/user-guide/tutorial.html) an AWS Cloud9 instance. 21 | 22 | ### AWS Account 23 | 24 | In order to complete this workshop you'll need an AWS Account with access to create AWS IAM, S3, DynamoDB, Lambda, and AppSync resources. The code and instructions in this workshop assume only one developer is using a given AWS account at a time. If you try sharing an account with another developer, you'll run into naming conflicts for certain resources. You can work around these by appending a unique suffix to the resources that fail to create due to conflicts, but the instructions do not provide details on the changes required to make this work. 25 | 26 | All of the resources you will launch as part of this workshop are eligible for the AWS free tier if your account is less than 12 months old. See the [AWS Free Tier page](https://aws.amazon.com/free/) for more details. 27 | 28 | ### Browser 29 | 30 | We recommend you use the latest version of Chrome to complete this workshop. 31 | 32 | ### AWS Cloud9 or a Text Editor 33 | 34 | The instructions below assume you are using Cloud9 to build this application. You can optionally choose to use any Text editor. 35 | 36 | ## Implementation Instructions 37 | 38 | ### Region Selection 39 | 40 | This workshop can be deployed in any AWS region that supports the following services: 41 | 42 | - AWS Lambda 43 | - Amazon AppSync 44 | - Amazon S3 45 | - Amazon DynamoDB 46 | 47 | You can refer to the [region table](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/) in the AWS documentation to see which regions have the supported services. Among the currently supported regions you can choose are: **N. Virginia, Ohio, Oregon, Ireland, Frankfurt, Singapore, Tokyo, Sydney, and Mumbai**. 48 | 49 | Once you've chosen a region, you should deploy all of the resources for this workshop there. Make sure you select your region from the dropdown in the upper right corner of the AWS Console before getting started. 50 | 51 | ![Region selection screenshot](images/region-selection.png) 52 | 53 | ### Step 0: Prepare Cloud9 Workspace 54 | Download all files from the Github repo. 55 | Upload them to your Cloud9 workspace (File -> Upload Local files ...) 56 | 57 | ![Upload screenshot](images/Step0.png) 58 | 59 | ### Step 1: Create your DynamoDB Table 60 | Execute following CLI command to create a table called: **AppSync-Destinations** 61 | 62 | aws dynamodb create-table --table-name AppSync-Destinations --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=10 63 | 64 | Execute the following CLI command to load the table with a few travel destinations: 65 | 66 | aws dynamodb batch-write-item --request-items file://Destinations.json 67 | 68 | ### Step 2: Create your Lambda Function 69 | 70 | This function will get the latest weather data from the OpenWeather API. The function will be written in NodeJS. 71 | 72 | - Switch back to your AWS Cloud9 IDE and create a new Lambda function. 73 | - Select the AWS Resources tab on the right. 74 | - Click "Create a new Lambda Function" 75 | - For Function name, enter: **getWeatherData** 76 | 77 | ![Upload screenshot](images/Step2.1.png) 78 | 79 | Click *Next* and select **empty-nodejs** 80 | 81 | ![Upload screenshot](images/Step2.2.png) 82 | 83 | - Click **Next**, accept all defaults and **Finish**. 84 | - On the left, expand your **getWeatherData** folder and open **index.js**. 85 | - Copy the provided source code from **getWeatherData.js** and paste it into **index.js**. 86 | - Open the **template.yaml** file and add the following environment variable to the bottom of the template. This is your OpenWeather API key. Replace with your OpenWeather API key: 87 | - Make sure you Save your changes! 88 | 89 | ```yaml 90 | Environment: 91 | Variables: 92 | APPID: YOUR_API_KEY_HERE 93 | ``` 94 | 95 | ![Upload screenshot](images/Step2.3.png) 96 | 97 | Right click your function and select **Deploy** 98 | 99 | ![Upload screenshot](images/Step2.4.png) 100 | 101 | ### Step 3: Create your AppSync API backend 102 | 103 | Open the AWS AppSync Console and click **Create API**. Choose **Build from Scratch** and click **Start**. 104 | Enter a name for your API and click **Create** 105 | 106 | On the next screen, scroll down to the "Integrate your app" section and download your *aws-exports.js* config file. Choose the **Javascript** tab and click **Download Config**. You will save this file into your ./src directory later. 107 | 108 | ![Upload screenshot](images/Step3.1.png) 109 | 110 | ### Setup your data sources 111 | 112 | We will be using DynamoDB and Lambda as our data sources 113 | 114 | - On the left pane, Select **Data Sources**. For the name enter **Destinations**. 115 | - Select the table you created in the previous step and click **Create** 116 | 117 | ![Upload screenshot](images/Step3.2.png) 118 | 119 | 120 | - On the left pane, Select **Data Sources**. For the name enter **WeatherConditions**. 121 | - For data source type select AWS Lambda function. Then Select the getWeatherData function you created in the previous step and click **Create** 122 | 123 | ![Upload screenshot](images/Step3.3.png) 124 | 125 | ### Setup your AppSync Schema 126 | 127 | - On the left pane, select **Schema**. 128 | - Copy and paste the following into the Schema editor and click **Save**: 129 | 130 | ```js 131 | type Destination { 132 | id: ID! 133 | description: String! 134 | state: String! 135 | city: String! 136 | zip: String! 137 | conditions: Weather! 138 | } 139 | 140 | type Mutation { 141 | addDestination( 142 | id: ID, 143 | description: String!, 144 | state: String!, 145 | city: String!, 146 | zip: String! 147 | ): Destination! 148 | } 149 | 150 | type Query { 151 | # Get a single value of type 'Post' by primary key. 152 | getDestination(id: ID!, zip: String): Destination 153 | getAllDestinations: [Destination] 154 | getDestinationsByState(state: String!): [Destination] 155 | } 156 | 157 | type Subscription { 158 | newDestination: Destination 159 | @aws_subscribe(mutations: ["addDestination"]) 160 | } 161 | 162 | type Weather { 163 | description: String 164 | current: String 165 | maxTemp: String 166 | minTemp: String 167 | } 168 | 169 | schema { 170 | query: Query 171 | mutation: Mutation 172 | subscription: Subscription 173 | } 174 | ``` 175 | 176 | ### Configure the Query resolvers 177 | 178 | - Scroll to the Query section, locate *getAllDestinations: [Destination]* and click **Attach**. 179 | - For the *Data source name* choose **Destinations**. 180 | - In the *Configure request mapping template* box, choose **List items** 181 | - In the *Configure response mapping template* box, choose **Return a list of results** 182 | - Click Save 183 | 184 | #### Optional: Connect the getDestinationsByState Query Resolver 185 | 186 | ![Upload screenshot](images/Step3.4.png) 187 | 188 | ### Configure the Weather resolver 189 | - Scroll to the *Destination* section and click **Attach** for *conditions: Weather!*. 190 | - For the *Data source name* choose **WeatherConditions**. 191 | - In the *Configure request mapping template* box, enter the following JSON in the box: 192 | 193 | ```js 194 | { 195 | "version" : "2017-02-28", 196 | "operation": "Invoke", 197 | "payload": { 198 | "city":$util.toJson($context.source.city) 199 | } 200 | 201 | } 202 | ``` 203 | 204 | This will grab the results from the parent DynamoDB query and pass the *city* attribute into the Lambda function as the payload. The *$context.source* attribute contains the result of the parent query. 205 | 206 | In the *Configure response mapping template* box, choose **Return Lambda Result**. 207 | Your configuration should look similar to this: 208 | 209 | ![Upload screenshot](images/Step3.5.png) 210 | 211 | ### Configure the Mutuation resolver 212 | - For the *Data source name* choose **Destinations**. 213 | - In the *Configure request mapping template* box, choose **Put Item** 214 | - In the *Configure response mapping template* box, choose **Return a single item** 215 | - Accept all defaults. Pay attention to autoId. 216 | 217 | ![Upload screenshot](images/Step3.6.png) 218 | 219 | ### Step 4: Implement your ReactJS web front end 220 | Switch back to the AWS Cloud9 IDE. 221 | Upload the *aws-exports.js* file you downloaded in the previous step to your Cloud9 workspace 222 | 223 | Create your ReactJS project (this step will take a few minutes): 224 | 225 | ```bash 226 | npm install -g npx 227 | npx create-react-app my-destination-app 228 | cd my-destination-app 229 | ``` 230 | 231 | Test your base ReactJS site by starting the local server: 232 | ```bash 233 | npm start 234 | ``` 235 | - Preview the ReactJS site: Select **Preview** -> **Preview Running Application** 236 | 237 | Enter **Control + C ** to shutdown the web server. 238 | 239 | Install AppSync dependencies: 240 | ```bash 241 | npm install --save react-apollo graphql-tag aws-sdk 242 | npm install --save aws-appsync 243 | npm install --save aws-appsync-react 244 | npm install --save react-router-dom 245 | npm install --save semantic-ui-css 246 | ``` 247 | 248 | Under the */my-destination-app/src* folder create a **Components** folder and a **Queries** folder 249 | Drag and drop files you downloaded into *src* as follows: 250 | 251 | App.js 252 | aws-exports.js 253 | Components 254 | AddDestination.jsx 255 | AllDestinations.jsx 256 | Queries 257 | AllDestinationsQuery.js 258 | NewDestinationMutation.js 259 | NewDestinationsSubscription.js 260 | 261 | 262 | ### Step 5: Deploy your ReactJS front end 263 | 264 | Create S3 bucket for static website hosting: 265 | ```bash 266 | aws s3 mb s3://[YOUR-BUCKET-NAME] 267 | aws s3 website s3://[YOUR-BUCKET-NAME]/ --index-document index.html 268 | ``` 269 | 270 | - Edit *bucketpolicy.json* and enter your bucket name on line 9 271 | - Set the bucket policy as follows: 272 | 273 | ```bash 274 | aws s3api put-bucket-policy --bucket [YOUR-BUCKET-NAME] --policy file://bucketpolicy.json 275 | ``` 276 | 277 | Compile and package the app for deployment to S3: 278 | ```bash 279 | cd my-destination-app 280 | npm run build 281 | ``` 282 | 283 | The distributables will be in the /build folder. Change directory into the build folder 284 | ``` 285 | cd build 286 | ``` 287 | 288 | Sync the contents to s3: 289 | ```bash 290 | aws s3 sync . s3://[YOUR-BUCKET-NAME]/ 291 | ``` 292 | 293 | ### Step 6: Test! 294 | Bring up the S3 console. Goto Static website Hosting and click the link or type this in your browser: 295 | http://[yourbucketname].s3-website.[region].amazonaws.com 296 | 297 | ![Upload screenshot](images/Step6.1.png) 298 | 299 | #### Test AppSync realtime capabilities 300 | 301 | AppSync uses WebSockets to provide realtime data sync. 302 | Try adding a new destination through a Mutation and watch the results populate in realtime: 303 | 304 | Goto the AppSync Console, click the Queries section and run the following mutation: 305 | 306 | ```javascript 307 | mutation addDestination{ 308 | addDestination( 309 | description:"Space Needle" 310 | city: "Seattle" 311 | state: "Washington" 312 | zip: "98109" 313 | ){ 314 | __typename 315 | id 316 | description 317 | city 318 | state 319 | zip 320 | conditions{ 321 | __typename 322 | maxTemp 323 | minTemp 324 | current 325 | description 326 | } 327 | } 328 | } 329 | ``` 330 | 331 | You should see the new *Space Needle* destination populate in your browser: 332 | 333 | ![Upload screenshot](images/Step6.2.png) 334 | 335 | ## License Summary 336 | 337 | This sample code is made available under a modified MIT license. See the LICENSE file. 338 | -------------------------------------------------------------------------------- /bucketpolicy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version":"2012-10-17", 3 | "Statement":[ 4 | { 5 | "Sid":"AddPerm", 6 | "Effect":"Allow", 7 | "Principal": "*", 8 | "Action":["s3:GetObject"], 9 | "Resource":["arn:aws:s3:::USE_YOUR_BUCKET_NAME/*"] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /getWeatherData.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | const querystring = require('querystring'); 3 | 4 | exports.handler = (event, context, callback) => { 5 | 6 | console.log(event); 7 | 8 | var city = querystring.escape(event.city); 9 | 10 | http.get({ 11 | hostname: 'api.openweathermap.org', 12 | port: 80, 13 | path: '/data/2.5/forecast?q='+ city +'&appid=' + process.env.APPID + '&cnt=3&units=imperial', 14 | agent: false // create a new agent just for this one request 15 | }, (res) => { 16 | 17 | var body = ''; 18 | res.on('data', function(d) { 19 | body += d; 20 | }); 21 | 22 | res.on('end', function() { 23 | 24 | // Data reception is done, do whatever with it! 25 | var parsed = JSON.parse(body); 26 | console.log(body); 27 | 28 | var current = parsed.list[0].main.temp; 29 | var maxTemp = parsed.list[0].main.temp_max; 30 | var minTemp = parsed.list[0].main.temp_min; 31 | var description = parsed.list[0].weather[0].main ; 32 | var descriptionText = parsed.list[0].weather[0].description ; 33 | 34 | callback(null, { 35 | description: description + ":" + descriptionText, 36 | current: current, 37 | maxTemp: maxTemp, 38 | minTemp: minTemp 39 | }); 40 | 41 | }); 42 | }); 43 | }; -------------------------------------------------------------------------------- /images/Step0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step0.png -------------------------------------------------------------------------------- /images/Step2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step2.1.png -------------------------------------------------------------------------------- /images/Step2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step2.2.png -------------------------------------------------------------------------------- /images/Step2.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step2.3.png -------------------------------------------------------------------------------- /images/Step2.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step2.4.png -------------------------------------------------------------------------------- /images/Step3.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step3.1.png -------------------------------------------------------------------------------- /images/Step3.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step3.2.png -------------------------------------------------------------------------------- /images/Step3.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step3.3.png -------------------------------------------------------------------------------- /images/Step3.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step3.4.png -------------------------------------------------------------------------------- /images/Step3.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step3.5.png -------------------------------------------------------------------------------- /images/Step3.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step3.6.png -------------------------------------------------------------------------------- /images/Step6.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step6.1.png -------------------------------------------------------------------------------- /images/Step6.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/Step6.2.png -------------------------------------------------------------------------------- /images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/arch.png -------------------------------------------------------------------------------- /images/region-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/region-selection.png -------------------------------------------------------------------------------- /images/twitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-serverless-appsync-app/568b89dff9e927bfa67daa263ea485ca86e9f9d3/images/twitch.png --------------------------------------------------------------------------------