├── .gitignore ├── README.md └── dayzero.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | amplify-web-workshop 2 | 3 | amplify-demos/.gitignore 4 | amplify-demos/amplify 5 | amplify-demos/.amplifyrc 6 | amplify-demos/src/aws-exports.js 7 | amplify-demos/node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building full stack web and mobile applications on AWS 2 | 3 | In this workshop we'll learn how to build cloud-enabled applications with GraphQL, React, & [AWS Amplify](https://aws-amplify.github.io/). 4 | 5 | ![](dayzero.jpg) 6 | 7 | ### Topics we'll be covering: 8 | 9 | - [Authentication](https://github.com/dabit3/day-zero-workshop#adding-authentication) 10 | - [GraphQL API with AWS AppSync](https://github.com/dabit3/day-zero-workshop#adding-a-graphql-api) 11 | - [Adding Authorization to the GraphQL API](https://github.com/dabit3/day-zero-workshop#adding-authorization-to-the-graphql-api) 12 | - [Deploying via the Amplify Console](https://github.com/dabit3/day-zero-workshop#deploying-via-the-amplify-console) 13 | - [React Native](https://github.com/dabit3/day-zero-workshop#react-native) 14 | - [Removing / Deleting Services](https://github.com/dabit3/day-zero-workshop#removing-services) 15 | 16 | ## Getting Started - Creating the React Application 17 | 18 | To get started, we first need to create a new React project using the [Create React App CLI](https://github.com/facebook/create-react-app) & change into the new directory. 19 | 20 | Create a new React app using npx (npm 5.2 & later): 21 | 22 | ```bash 23 | npx create-react-app my-amplify-app 24 | ``` 25 | 26 | Or install the CLI & create the React app: 27 | 28 | ```bash 29 | npm install -g create-react-app 30 | create-react-app my-amplify-app 31 | ``` 32 | 33 | Now change into the new app directory & install the AWS Amplify & AWS Amplify React libraries: 34 | 35 | ```bash 36 | cd my-amplify-app 37 | npm install --save aws-amplify aws-amplify-react uuid 38 | # or 39 | yarn add aws-amplify aws-amplify-react uuid 40 | ``` 41 | 42 | ## Installing the CLI & Initializing a new AWS Amplify Project 43 | 44 | ### Installing the CLI 45 | 46 | Next, we'll install the AWS Amplify CLI: 47 | 48 | ```bash 49 | npm install -g @aws-amplify/cli 50 | ``` 51 | 52 | Now we need to configure the CLI with our credentials: 53 | 54 | ```js 55 | amplify configure 56 | ``` 57 | 58 | > If you'd like to see a video walkthrough of this configuration process, click [here](https://www.youtube.com/watch?v=fWbM5DLh25U). 59 | 60 | Here we'll walk through the `amplify configure` setup. Once you've signed in to the AWS console, continue: 61 | - Specify the AWS Region: __us-west-2__ 62 | - Specify the username of the new IAM user: __amplify-workshop-user__ 63 | > In the AWS Console, click __Next: Permissions__, __Next: Tags__, __Next: Review__, & __Create User__ to create the new IAM user. Then, return to the command line & press Enter. 64 | - Enter the access key of the newly created user: 65 | accessKeyId: __()__ 66 | secretAccessKey: __()__ 67 | - Profile Name: __amplify-workshop-user__ 68 | 69 | ### Initializing A New Project 70 | 71 | ```bash 72 | amplify init 73 | ``` 74 | 75 | - Enter a name for the project: __amplifyreactapp__ 76 | - Enter a name for the environment: __dev__ 77 | - Choose your default editor: __Visual Studio Code (or your default editor)__ 78 | - Please choose the type of app that you're building __javascript__ 79 | - What javascript framework are you using __react__ 80 | - Source Directory Path: __src__ 81 | - Distribution Directory Path: __build__ 82 | - Build Command: __npm run-script build__ 83 | - Start Command: __npm run-script start__ 84 | - Do you want to use an AWS profile? __Y__ 85 | - Please choose the profile you want to use: __amplify-workshop-user__ 86 | 87 | Now, the AWS Amplify CLI has iniatilized a new project & you will see a new folder: __amplify__ & a new file called `aws-export.js` in the __src__ directory. These files hold your project configuration. 88 | 89 | ## Adding Authentication 90 | 91 | To add authentication, we can use the following command: 92 | 93 | ```sh 94 | amplify add auth 95 | ``` 96 | - Do you want to use default authentication and security configuration? __Default configuration__ 97 | - How do you want users to be able to sign in when using your Cognito User Pool? __Username__ 98 | - What attributes are required for signing up? __Email__ (keep default) 99 | 100 | Now, we'll run the push command and the cloud resources will be created in our AWS account. 101 | 102 | ```bash 103 | amplify push 104 | ``` 105 | 106 | To view the service you can run the `console` command the feature you'd like to view: 107 | 108 | ```sh 109 | amplify console auth 110 | ``` 111 | 112 | ### Configuring the React applicaion 113 | 114 | Now, our resources are created & we can start using them! 115 | 116 | The first thing we need to do is to configure our React application to be aware of our new AWS Amplify project. We can do this by referencing the auto-generated `aws-exports.js` file that is now in our src folder. 117 | 118 | To configure the app, open __src/index.js__ and add the following code below the last import: 119 | 120 | ```js 121 | import Amplify from 'aws-amplify' 122 | import config from './aws-exports' 123 | Amplify.configure(config) 124 | ``` 125 | 126 | Now, our app is ready to start using our AWS services. 127 | 128 | ### Using the withAuthenticator component 129 | 130 | To add authentication, we'll go into __src/App.js__ and first import the `withAuthenticator` HOC (Higher Order Component) from `aws-amplify-react`: 131 | 132 | ### src/App.js 133 | 134 | ```js 135 | import { withAuthenticator } from 'aws-amplify-react' 136 | ``` 137 | 138 | Next, we'll wrap our default export (the App component) with the `withAuthenticator` HOC: 139 | 140 | ```js 141 | export default withAuthenticator(App, { includeGreetings: true }) 142 | ``` 143 | 144 | ```sh 145 | # run the app 146 | 147 | npm start 148 | ``` 149 | 150 | Now, we can run the app and see that an Authentication flow has been added in front of our App component. This flow gives users the ability to sign up & sign in. 151 | 152 | > To view the new user that was created in Cognito, go back to the dashboard at [https://console.aws.amazon.com/cognito/](https://console.aws.amazon.com/cognito/). Also be sure that your region is set correctly. 153 | 154 | ### Accessing User Data 155 | 156 | We can access the user's info now that they are signed in by calling `Auth.currentAuthenticatedUser()`. 157 | 158 | ### src/App.js 159 | 160 | ```js 161 | import React, { useEffect } from 'react' 162 | import { Auth } from 'aws-amplify' 163 | 164 | function App() { 165 | useEffect(() => { 166 | Auth.currentAuthenticatedUser() 167 | .then(user => console.log({ user })) 168 | .catch(error => console.log({ error })) 169 | }) 170 | return ( 171 |
172 |

173 | Edit src/App.js and save to reload. 174 |

175 |
176 | ) 177 | } 178 | 179 | export default App 180 | ``` 181 | 182 | ### Custom authentication strategies 183 | 184 | > For a full example of a custom authentication flow as well as OAuth with Facebook & Google Signin check out [this repo](https://github.com/dabit3/amplify-auth-demo) and a live example [here](https://www.amplifyauth.dev/). 185 | 186 | The `withAuthenticator` component is a really easy way to get up and running with authentication, but in a real-world application we probably want more control over how our form looks & functions. 187 | 188 | Let's look at how we might create our own authentication flow. 189 | 190 | To get started, we would probably want to create input fields that would hold user input data in the state. For instance when signing up a new user, we would probably need 4 user inputs to capture the user's username, email, password, & phone number. 191 | 192 | To do this, we could create some initial state for these values & create an event handler that we could attach to the form inputs: 193 | 194 | ```js 195 | // initial state 196 | import React, { useReducer } from 'react' 197 | 198 | // define initial state 199 | const initialState = { 200 | username: '', password: '', email: '' 201 | } 202 | 203 | // create reducer 204 | function reducer(state, action) { 205 | switch(action.type) { 206 | case 'SET_INPUT': 207 | return { ...state, [action.inputName]: action.inputValue } 208 | default: 209 | return state 210 | } 211 | } 212 | 213 | // useReducer hook creates local state 214 | const [state, dispatch] = useReducer(reducer, initialState) 215 | 216 | // event handler 217 | function onChange(e) { 218 | dispatch({ 219 | type: 'SET_INPUT', 220 | inputName: e.target.name, 221 | inputValue: e.target.value 222 | }) 223 | } 224 | 225 | // example of usage with input 226 | 232 | ``` 233 | 234 | We'd also need to have a method that signed up & signed in users. We can us the Auth class to do thi. The Auth class has over 30 methods including things like `signUp`, `signIn`, `confirmSignUp`, `confirmSignIn`, & `forgotPassword`. Thes functions return a promise so they need to be handled asynchronously. 235 | 236 | ```js 237 | // import the Auth component 238 | import { Auth } from 'aws-amplify' 239 | 240 | // Class method to sign up a user 241 | async function signUp() { 242 | const { username, password, email } = state 243 | try { 244 | await Auth.signUp({ username, password, attributes: { email }}) 245 | console.log('user successfully signed up!') 246 | } catch (err) { 247 | console.log('error signing up user...', err) 248 | } 249 | } 250 | 251 | 252 | ``` 253 | 254 | ## Adding a GraphQL API 255 | 256 | To add a GraphQL API, we can use the following command: 257 | 258 | ```sh 259 | amplify add api 260 | ``` 261 | 262 | Answer the following questions 263 | 264 | - Please select from one of the above mentioned services __GraphQL__ 265 | - Provide API name: __CryptoGraphQL__ 266 | - Choose an authorization type for the API __API key__ 267 | - Do you have an annotated GraphQL schema? __N__ 268 | - Do you want a guided schema creation? __Y__ 269 | - What best describes your project: __Single object with fields (e.g. “Todo” with ID, name, description)__ 270 | - Do you want to edit the schema now? (Y/n) __Y__ 271 | 272 | > When prompted, update the schema to the following: 273 | 274 | ```graphql 275 | type Coin @model { 276 | id: ID! 277 | clientId: ID 278 | name: String! 279 | symbol: String! 280 | price: Float! 281 | } 282 | ``` 283 | 284 | > Next, let's push the configuration to our account: 285 | 286 | ```bash 287 | amplify push 288 | ``` 289 | 290 | - Do you want to generate code for your newly created GraphQL API __Y__ 291 | - Choose the code generation language target: __javascript__ 292 | - Enter the file name pattern of graphql queries, mutations and subscriptions: __(src/graphql/**/*.js)__ 293 | - Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions? __Y__ 294 | - Enter maximum statement depth [increase from default if your schema is deeply nested] __2__ 295 | 296 | To view the service you can run the `console` command the feature you'd like to view: 297 | 298 | ```sh 299 | amplify console api 300 | ``` 301 | 302 | ### Adding mutations from within the AWS AppSync Console 303 | 304 | In the AWS AppSync console, open your API & then click on Queries. 305 | 306 | Execute the following mutation to create a new coin in the API: 307 | 308 | ```graphql 309 | mutation createCoin { 310 | createCoin(input: { 311 | name: "Bitcoin" 312 | symbol: "BTC" 313 | price: 9000 314 | }) { 315 | id name symbol price 316 | } 317 | } 318 | ``` 319 | 320 | Now, let's query for the coin: 321 | 322 | ```graphql 323 | query listCoins { 324 | listCoins { 325 | items { 326 | id 327 | name 328 | symbol 329 | price 330 | } 331 | } 332 | } 333 | ``` 334 | 335 | We can even add search / filter capabilities when querying: 336 | 337 | ```graphql 338 | query listCoins { 339 | listCoins(filter: { 340 | price: { 341 | gt: 2000 342 | } 343 | }) { 344 | items { 345 | id 346 | name 347 | symbol 348 | price 349 | } 350 | } 351 | } 352 | ``` 353 | 354 | ### Interacting with the GraphQL API from our client application - Querying for data 355 | 356 | Now that the GraphQL API is created we can begin interacting with it! 357 | 358 | The first thing we'll do is perform a query to fetch data from our API. 359 | 360 | To do so, we need to define the query, execute the query, store the data in our state, then list the items in our UI. 361 | 362 | ### src/App.js 363 | 364 | ```js 365 | // src/App.js 366 | import React, { useEffect, useState } from 'react' 367 | 368 | // imports from Amplify library 369 | import { API, graphqlOperation } from 'aws-amplify' 370 | import { withAuthenticator } from 'aws-amplify-react' 371 | 372 | // import query 373 | import { listCoins } from './graphql/queries' 374 | 375 | function App() { 376 | const [coins, updateCoins] = useState([]) 377 | 378 | useEffect(() => { 379 | getData() 380 | }, []) 381 | 382 | async function getData() { 383 | try { 384 | const coinData = await API.graphql(graphqlOperation(listCoins)) 385 | console.log('data from API: ', coinData) 386 | updateCoins(coinData.data.listCoins.items) 387 | } catch (err) { 388 | console.log('error fetching data..', err) 389 | } 390 | } 391 | 392 | return ( 393 |
394 | { 395 | coins.map((c, i) => ( 396 |
397 |

{c.name}

398 |

{c.symbol}

399 |

{c.price}

400 |
401 | )) 402 | } 403 |
404 | ) 405 | } 406 | 407 | export default withAuthenticator(App, { includeGreetings: true }) 408 | ``` 409 | 410 | ## Performing mutations 411 | 412 | Now, let's look at how we can create mutations. Let's change the component to use a `useReducer` hook. 413 | 414 | ```js 415 | // src/App.js 416 | import React, { useEffect, useReducer } from 'react' 417 | import { API, graphqlOperation } from 'aws-amplify' 418 | import { withAuthenticator } from 'aws-amplify-react' 419 | import { listCoins } from './graphql/queries' 420 | import { createCoin as CreateCoin } from './graphql/mutations' 421 | 422 | // import uuid to create a unique client ID 423 | import uuid from 'uuid/v4' 424 | 425 | const CLIENT_ID = uuid() 426 | 427 | // create initial state 428 | const initialState = { 429 | name: '', price: '', symbol: '', coins: [] 430 | } 431 | 432 | // create reducer to update state 433 | function reducer(state, action) { 434 | switch(action.type) { 435 | case 'SETCOINS': 436 | return { ...state, coins: action.coins } 437 | case 'SETINPUT': 438 | return { ...state, [action.key]: action.value } 439 | default: 440 | return state 441 | } 442 | } 443 | 444 | function App() { 445 | const [state, dispatch] = useReducer(reducer, initialState) 446 | 447 | useEffect(() => { 448 | getData() 449 | }, []) 450 | 451 | async function getData() { 452 | try { 453 | const coinData = await API.graphql(graphqlOperation(listCoins)) 454 | console.log('data from API: ', coinData) 455 | dispatch({ type: 'SETCOINS', coins: coinData.data.listCoins.items}) 456 | } catch (err) { 457 | console.log('error fetching data..', err) 458 | } 459 | } 460 | 461 | async function createCoin() { 462 | const { name, price, symbol } = state 463 | if (name === '' || price === '' || symbol === '') return 464 | const coin = { 465 | name, price: parseFloat(price), symbol, clientId: CLIENT_ID 466 | } 467 | const coins = [...state.coins, coin] 468 | dispatch({ type: 'SETCOINS', coins }) 469 | console.log('coin:', coin) 470 | 471 | try { 472 | await API.graphql(graphqlOperation(CreateCoin, { input: coin })) 473 | console.log('item created!') 474 | } catch (err) { 475 | console.log('error creating coin...', err) 476 | } 477 | } 478 | 479 | // change state then user types into input 480 | function onChange(e) { 481 | dispatch({ type: 'SETINPUT', key: e.target.name, value: e.target.value }) 482 | } 483 | 484 | // add UI with event handlers to manage user input 485 | return ( 486 |
487 | 493 | 499 | 505 | 506 | { 507 | state.coins.map((c, i) => ( 508 |
509 |

{c.name}

510 |

{c.symbol}

511 |

{c.price}

512 |
513 | )) 514 | } 515 |
516 | ) 517 | } 518 | 519 | export default withAuthenticator(App, { includeGreetings: true }) 520 | ``` 521 | 522 | ### GraphQL Subscriptions 523 | 524 | Next, let's see how we can create a subscription to subscribe to changes of data in our API. 525 | 526 | To do so, we need to define the subscription, listen for the subscription, & update the state whenever a new piece of data comes in through the subscription. 527 | 528 | ```js 529 | // import the subscription 530 | import { onCreateCoin } from './graphql/subscriptions' 531 | 532 | // update reducer 533 | function reducer(state, action) { 534 | switch(action.type) { 535 | case 'SETCOINS': 536 | return { ...state, coins: action.coins } 537 | case 'SETINPUT': 538 | return { ...state, [action.key]: action.value } 539 | // new 👇 540 | case 'ADDCOIN': 541 | return { ...state, coins: [...state.coins, action.coin] } 542 | default: 543 | return state 544 | } 545 | } 546 | 547 | // subscribe in useEffect 548 | useEffect(() => { 549 | const subscription = API.graphql(graphqlOperation(onCreateCoin)).subscribe({ 550 | next: (eventData) => { 551 | const coin = eventData.value.data.onCreateCoin 552 | if (coin.clientId === CLIENT_ID) return 553 | dispatch({ type: 'ADDCOIN', coin }) 554 | } 555 | }) 556 | return () => subscription.unsubscribe() 557 | }, []) 558 | ``` 559 | 560 | ## Adding Authorization to the GraphQL API 561 | 562 | To add authorization to the API, we can re-configure the API to use our cognito identity pool. To do so, we can run `amplify configure api`: 563 | 564 | ```sh 565 | amplify configure api 566 | ``` 567 | 568 | - Please select from one of the below mentioned services: __GraphQL__ 569 | - Choose an authorization type for the API: __Amazon Cognito User Pool__ 570 | 571 | ### Adding fine-grained access controls to the GraphQL API 572 | 573 | Next, let's add a field that can only be accessed by the current user. 574 | 575 | To do so, we'll update the schema to add the following new type below the existing Coin type: 576 | 577 | ```graphql 578 | type Note @model @auth(rules: [{allow: owner}]) { 579 | id: ID! 580 | title: String! 581 | description: String 582 | } 583 | ``` 584 | 585 | Next, we'll deploy the updates to our API: 586 | 587 | ```sh 588 | amplify push 589 | ``` 590 | 591 | - Do you want to update code for your updated GraphQL API: __Y__ 592 | - Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types? __Y__ 593 | 594 | Now, the operations associated with this field will only be accessible by the creator of the item. 595 | 596 | To test it out, try creating a new user & accessing a note from another user. 597 | 598 | To test the API out in the AWS AppSync console, it will ask for you to __Login with User Pools__. The form will ask you for a __ClientId__. This __ClientId__ is located in __src/aws-exports.js__ in the `aws_user_pools_web_client_id` field. 599 | 600 | ## Deploying via the Amplify Console 601 | 602 | For hosting, we can use the [Amplify Console](https://aws.amazon.com/amplify/console/) to deploy the application. 603 | 604 | The first thing we need to do is [create a new GitHub repo](https://github.com/new) for this project. Once we've created the repo, we'll copy the URL for the project to the clipboard & initialize git in our local project: 605 | 606 | ```sh 607 | git init 608 | 609 | git remote add origin git@github.com:username/project-name.git 610 | 611 | git add . 612 | 613 | git commit -m 'initial commit' 614 | 615 | git push origin master 616 | ``` 617 | 618 | Next we'll visit the Amplify Console in our AWS account at [https://eu-west-1.console.aws.amazon.com/amplify/home](https://eu-west-1.console.aws.amazon.com/amplify/home). 619 | 620 | Here, we'll click __Get Started__ to create a new deployment. Next, authorize Github as the repository service. 621 | 622 | Next, we'll choose the new repository & branch for the project we just created & click __Next__. 623 | 624 | In the next screen, we'll create a new role & use this role to allow the Amplify Console to deploy these resources & click __Next__. 625 | 626 | Finally, we can click __Save and Deploy__ to deploy our application! 627 | 628 | Now, we can push updates to Master to update our application. 629 | 630 | ## React Native 631 | 632 | AWS Amplify also has framework support for [React Native](https://aws-amplify.github.io/docs/js/start?platform=react-native). 633 | 634 | To get started with using AWS Amplify with React Native, we'll need to install the __AWS Amplify React Native__ package & then link the dependencies. 635 | 636 | ```sh 637 | npm install aws-amplify-react-native 638 | 639 | # If using Expo, you do not need to link these two libraries as they are both part of the Expo SDK. 640 | react-native link amazon-cognito-identity-js 641 | react-native link react-native-vector-icons 642 | ``` 643 | 644 | Implementing features with AWS Amplify in React Native is the same as the features implemented in the other steps of this workshop. The only difference is that you will be working with React Native primitives vs HTML elements. 645 | 646 | ## Removing Services 647 | 648 | If at any time, or at the end of this workshop, you would like to delete a service from your project & your account, you can do this by running the `amplify remove` command: 649 | 650 | ```sh 651 | amplify remove auth 652 | 653 | amplify push 654 | ``` 655 | 656 | If you are unsure of what services you have enabled at any time, you can run the `amplify status` command: 657 | 658 | ```sh 659 | amplify status 660 | ``` 661 | 662 | `amplify status` will give you the list of resources that are currently enabled in your app. 663 | 664 | ## Deleting entire project 665 | 666 | ```sh 667 | amplify delete 668 | ``` 669 | -------------------------------------------------------------------------------- /dayzero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabit3/day-zero-workshop/deaab512ddde710284c5e427f6390896c32e8bb9/dayzero.jpg --------------------------------------------------------------------------------