Sådan opbygges en Full Stack-app med AWS Amplify og React

AWS Amplify er et værktøj udviklet af Amazon Web Services, der hjælper med at gøre appudvikling lettere.

Det inkluderer masser af funktioner, som giver dig mulighed for hurtigt og nemt at arbejde med andre AWS-tjenester. Det betyder, at du kan bruge mere tid på at opbygge de funktioner, der gør din app unik.

Denne tutorial er opdelt i fire dele:

  • Sådan oprettes appen med login
  • Sådan tilføjes en database og arbejder med dataene
  • Sådan tilføjes fillagring og bruger disse filer
  • Sådan tillades brugere at uploade deres egne filer og data

Hvis du vil læse denne artikel offline, kan du downloade den her.

Sådan oprettes appen med tilmelding, login og logout

I dette første afsnit opretter vi en ny React-app med AWS Amplify for at tilføje Tilmelding, login og logout på den nemmeste måde.

Vi skal starte med at oprette en ny React-app ved hjælp af create-react-app. Åbn en terminal, og kør disse kommandoer. Hvis du ikke har oprettet-reager-app installeret, kan du køre npm i -g create-react-appførst.

npx create-react-app amplify-react-app cd amplify-react-app

Med denne opsætning kan vi nu installere Amplify og derefter konfigurere den.

npm install -g @aws-amplify/cli amplify configure

Dette åbner en AWS-konsolfane i din browser. Sørg for, at du er logget ind på den korrekte konto hos en bruger, der har administratortilladelser.

Gå tilbage til terminalen og følg trinene, tilføj en region og et navn til brugeren. Dette vil derefter føre dig tilbage browseren, hvor du kan følge trinene for at oprette den nye bruger. Sørg for at blive på den side, hvor du ser nøglen og hemmeligheden!

Tilbage i terminalen igen kan du følge trinene, kopiere adgangsnøglen og hemmeligheden ind i terminalen, når du bliver spurgt. Når du bliver spurgt, om du vil føje dette til en profil, skal du sige Yes. Opret en profil, der ligner noget serverless-amplify.

Nu kan vi initialisere forstærke opsætningen ved at køre amplify init. Du kan give projektet et navn og besvare alle spørgsmålene. De fleste af dem skal allerede være korrekte. Dette tager derefter et stykke tid at foretage ændringerne på din konto.

Når det er gjort, skal vi tilføje godkendelse til appen. Vi gør dette med amplify add auth. Vælg metoden som defaultlogin på emailog derefter no, I am done. Vi kan nu implementere dette ved at køre amplify push. Dette tager et stykke tid, men i slutningen er vores src/aws-exports.jsfil oprettet.

Sådan opbygges React-appen

Nu kan vi komme videre til at oprette reageringsappen. Start med at installere de Amplify npm-pakker, vi har brug for.

npm install --save aws-amplify @aws-amplify/ui-react

Nu kan vi begynde at redigere koden i vores app. I vores src/App.jsfil kan vi fjerne alt i overskrifterne og erstatte det med dette:

My App Content

Dette er en meget grundlæggende opsætning, men du kan placere hovedindholdet på dit websted her og lægge AmplifySignOutknappen, hvor du vil have det.

Vi skal også tilføje ekstra import til toppen af ​​filen:

import Amplify from 'aws-amplify'; import awsconfig from './aws-exports'; import { AmplifySignOut, withAuthenticator } from '@aws-amplify/ui-react'; Amplify.configure(awsconfig); 

Nu er den sidste ting, vi skal gøre, at ændre den måde, vi eksporterer appen på. Skift den sidste linje, der skal export default withAuthenticator(App);tilføjes Amplify til denne app.

Nu når vi kører, skal npm startvi få en loginskærm. Vi har ikke oprettet dette, så det er kommet fra Amplify selv.

Hvis vi prøver at logge ind, vil det mislykkes, da vi først skal tilmelde os. Vi kan klikke create accountog derefter indtaste vores e-mail og en adgangskode for at tilmelde os.

Når vi har bekræftet vores e-mail ved at indsende den kode, vi fik tilsendt, kommer vi ind på hjemmesiden for vores app. Hvis vi logger ud, kan vi nu logge ind igen som forventet.

Sådan tilføjes en database til vores app

Hvis du vil føje data til din React-app, men ikke ønsker at skulle bygge en API, er dette afsnittet for dig. Vi vil se på, hvordan vi kan bruge AWS-forstærkning i vores React-app, så vi kan få adgang til vores database på bagsiden ved hjælp af GraphQL.

For at starte skal vi gå ind i terminalen og køre:

amplify add api

Dette vil starte os med et sæt CLI-indstillinger og stille os et par konfigurationsspørgsmål:

Hvilken slags API vi vil bruge: GraphQL

Navnet på API: songAPI

Hvordan vi ønsker at godkende API: Amazon Cognito User Pool

Avancerede indstillinger: Nej, jeg er færdig

Har du et skema: Nej

Hvilken slags skema vil du have: Enkelt objekt med felter

Efter en lille opsætning bliver vi spurgt, om vi vil redigere vores nye skema. Vi vil sige ja. Dette åbner GraphQL-skemaet, som vi opdaterer til det skema, der er angivet her.

type Song @model { id: ID! title: String! description: String! filePath: String! likes: Int! owner: String! }

Med vores skemaopsætning skal vi køre, amplify pushsom sammenligner vores nuværende forstærke opsætning med det på vores AWS-konto. Da vi har tilføjet en ny API, får vi ændringer, så vi bliver spurgt, om vi vil fortsætte med ændringerne.

Når vi har valgt Ja, sættes vi i et andet sæt valgmuligheder.

Ønsker vi at generere kode til vores GraphQL API: Ja

Hvilket sprog: JavaScript

Filmønster for de nye filer: src / graphql / ** / *. Js

Generer alle operationer: Ja

Maksimal sætningsdybde: 2

Dette vil nu implementere alle ændringer til AWS og også oprette de nye anmodningsfiler i vores React-app. Dette tager et par minutter at gøre.

Når det er afsluttet, kan vi gå ind i vores App.jsfil og omdøbe den til at være App.jsx. Dette gør det bare nemmere at skrive vores JSX-kode.

Vi skal nu skrive en funktion her for at få listen over sange fra vores nye database. Denne funktion kalder GraphQL API, der passerer i driften af listSongs. Vi skal også tilføje en ny tilstand til Appkomponenten.

const [songs, setSongs] = useState([]); const fetchSongs = async () => { try { const songData = await API.graphql(graphqlOperation(listSongs)); const songList = songData.data.listSongs.items; console.log('song list', songList); setSongs(songList); } catch (error) { console.log('error on fetching songs', error); } };

Vi er nu nødt til at tilføje eller opdatere et par importer til vores fil for at få dette til at fungere:

import React, { useState, useEffect } from 'react'; import { listSongs } from './graphql/queries'; import Amplify, { API, graphqlOperation } from 'aws-amplify';

Dette listSongser en af ​​de funktioner, der er oprettet af amplify for at hjælpe os med at få adgang til vores data. Du kan se de andre funktioner, der er tilgængelige i ./graphqlmappen.

Nu ønsker vi, at denne funktion skal kaldes en gang, når komponenten gengives, men ikke hver gang den gengives igen. For at gøre dette bruger vi, useEffectmen sørg for at tilføje en anden parameter, []så den kun udløses en gang.

useEffect(() => { fetchSongs(); }, []);

Hvis vi nu starter vores app ved hjælp af npm startog derefter går til appen, kan vi åbne konsollen og se en log på song list []. Dette betyder, at den useEffecthar kaldt den, fetchSongsder er konsol, der logger ud af resultatet, men i øjeblikket er der intet i databasen.

To correct this we need to log into our AWS account and add the DynamoDB service. We should find a new table called something like Song-5gq8g8wh64w-dev. If you can't find it make sure to check other regions as well.

This currently has no data so we need to add some. For now we're going with manually creating new data in here. Under Items click Create item and then make sure the dropdown in the top left shows text. If it shows tree then just click it and change it to text. We can then make the data go into that row.

We start with the GraphQL schema, giving the row some data for each attribute. But we also need to add createdAt and updatedAt values. You can find this using the browser console.

Type new Date().toISOString() and copy the result of that. You should end up with an object like this:

{ "id": "gr4334t4tog345ht35", "title": "My First Song", "description": "A test song for our amplify app", "owner": "Sam Williams", "filePath": "", "likes": 4, "createdAt": "2020-08-13T07:01:39.176Z", "updatedAt": "2020-08-13T07:01:39.176Z" }

If we save that new object then we can go back into our app and refresh the page. We should now be able to see our data in the console.log.

We can now use this data in our app to show the list of songs that we just got. Replace the existing text of song list with this set of JSX.

 {songs.map((song, idx) => { return ( {song.title} {song.owner} {song.likes} {song.description} ); })} 

This code is mapping over each song in the list and rendering a new Paper for them with all the details we need.

We're using the MaterialUI library to help make this look nice for us so we need to make sure to run npm install --save @material-ui/core @material-ui/icons to install those packages and then add them to the imports at the top of the file too:

import { Paper, IconButton } from '@material-ui/core'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; import FavoriteIcon from '@material-ui/icons/Favorite';

With this, if we save and reload our app we now get this:

Whilst this is ok, we can update the CSS to make it look far better. Open up your App.css file and change it to this:

.App { text-align: center; } .App-logo { height: 10vmin; pointer-events: none; } .App-header { background-color: #282c34; min-height: 5vh; display: flex; align-items: center; justify-content: space-around; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } .songList { display: flex; flex-direction: column; } .songCard { display: flex; justify-content: space-around; padding: 5px; } .songTitle { font-weight: bold; }

Now we get it looking like this - much better.

Now we've got one item in the database so we only get one record. If we go back into Dynamo and create a new item or duplicate the existing one then we can see how multiple songs look. You can duplicate an item by clicking the checkbox to its left

Now that we can get the data, what about updating that info? For this we are going to add the ability to like a song. To start this we can add an onClick function to the icon button that we have for the likes.

 addLike(idx)}>

You may have realised that there is this idx property that we haven't see before. That is short for index.

Where we do the songs.map we can update it slightly to get the position of each item in the list. We can also use this idx to add a key to the top level Paper in that map to remove an errors we get from React.

{songs.map((song, idx) => { return (  ...  )} )}

With the new index and the onClick function call, we now need to make the addLike function.

This function needs to take the index of the song to find the correct song and update the number of likes. It then removes some fields that can't be passed into the updateSong operation before calling that operation.

const addLike = async idx => { try { const song = songs[idx]; song.likes = song.likes + 1; delete song.createdAt; delete song.updatedAt; const songData = await API.graphql(graphqlOperation(updateSong, { input: song })); const songList = [...songs]; songList[idx] = songData.data.updateSong; setSongs(songList); } catch (error) { console.log('error on adding Like to song', error); } };

Once the song has been updated in the database, we need to get that update back into our state. We need to clone the existing songs using const songList = [...songs].

If we just mutate the original list of songs then React wouldn't have re-rendered the page. With that new song list we call setSongs to update our state and we're done with the function.

We just need to add one more import to the top of the file which we get from the mutators that Amplify created:

import { updateSong } from './graphql/mutations';

Now when we click on the like button on a song, it is updated in state and in the database.

How to Add File Storage

Now that we have the song data stored in Dynamo, we want to store the actual MP3 data somewhere. We'll be storing the songs in S3 and accessing them using Amplify.

How to Add Play/Pause Functionality

To get started we're going to add a way to play and pause each of the songs. For now, this will just change the play button to a pause button.

Later we'll be using the Amplify Storage methods to get our MP3 file from S3 and play it directly in our app.

We're going to add a new function to the App component called toggleSong.

const toggleSong = async idx => { if (songPlaying === idx) { setSongPlaying(''); return; } setSongPlaying(idx); return }

To get this working we also need to add a new state variable to the app. This will be used to store the index of the song that is currently playing.

const [songPlaying, setSongPlaying] = useState('');

With this set up we need to make a change to the JSX code to use our new function and variables.

Find the first button in the songCard div. We're going to be adding an onClick which calls our new function. We're also going to use a ternary equation to say that if the song that is playing is this song, show a pause icon instead of a play icon.

 toggleSong(idx)}> {songPlaying === idx ?  : } 

We'll just need to import the PauseIcon at the top of the file and we'll be ready.

import PauseIcon from '@material-ui/icons/Pause';

How to Add the Storage to Amplify

Next, we need to use the Amplify CLI to add the storage to our app. We can start by going into our terminal and running amplify add storage. This will open a CLI where we need to select the following options.

Please select your service = Content (images, audio, video, etc.)

Friendly name for your resource = songStorage

Bucket name = song-storage

Who should have access = Auth Users Only

What access do those users have = Read and Create/Update

Do you want a Lambda trigger?  = No

With that all configured we need to deploy it. We can start that with amplify push which will take a little bit of time to work out what we've changed in our amplify application. You'll then get a little display of all of the resources we have in Amplify.

The only change is the creation of our new songStorage resource. We can select Yes to continuing and then it will create our S3 bucket for us.

How to Access the S3 File through the Amplify Storage methods

Once the deployment has finished we can start using that to access our songs. Back in our toggleSong function we are going to add some extra logic.

const toggleSong = async idx => { if (songPlaying === idx) { setSongPlaying(''); return; } const songFilePath = songs[idx].filePath; try { const fileAccessURL = await Storage.get(songFilePath, { expires: 60 }); console.log('access url', fileAccessURL); setSongPlaying(idx); setAudioURL(fileAccessURL); return; } catch (error) { console.error('error accessing the file from s3', error); setAudioURL(''); setSongPlaying(''); } };

This is getting the file path from the song data (that was stored in Dynamo) and then using Storage.get(songFilePath, { expires: 60 }); to get an access URL for the file.

The { expires: 60 } on the end is saying that the URL returned is only going to work for 60 seconds. After that you won't be able to access the file with it. This is a useful security measure so people can't share the URL to others who shouldn't have access to the files.

Once we have the file, we're also setting that in a new state variable using setAudioURL. At the top of our App we need to add this extra state.

const [audioURL, setAudioURL] = useState('');

Save this and go back into our app. If we press the play button and open the console we'll see the URL being logged out. This is what we're going to use to play the song.

How to Upload Our Songs

We're now getting to the point where we need some songs to play. If we go into our AWS account and search for S3 and then search for song , we should find our new S3 bucket.

In there we need to create a new folder called public. This is because the files will be public to everyone who has signed into the app. There are other ways of storing data which are private and only viewable by specific people.

Inside that folder we need to upload two songs. I have two copyright free songs that I uploaded called epic.mp3 and tomorrow.mp3. Once they have been uploaded we need to update our Dynamo data to point at those songs.

In Dynamo we can find our Songs-(lots of random charachters) table. Under Items we should have two records. Open up the first one and change the filePath to tomorrow.mp3 and the name to Tomorrow.

Save that and open the second song, changing that to "filePath": "epic.mp3" and "name": "Epic" , saving that file too.

If you used your own songs then just make sure the filePath matches the file name of your songs.

We can now go back into our app, refresh the page, and press play on one of the songs. If we look in our console and copy the URL we're given we can paste that into a browser and our song should start playing.

How to Add a Media Player to Our App

Now we want to be able to actually play our song from within our app. To do this we're going to use a library called react-player. We need to first install it with npm install --save react-player.

In our app we can then import it at the top of the file.

import ReactPlayer from 'react-player';

If we find our

className="songCard">, we want to add our player after that component. In the same way we showed the play/pause icons, we're going to show or hide this player based on which song is playing.

 .. .. {songPlaying === idx ? ( toggleSong(idx)} /> ) : null}

The ReactPlayer takes a few parameters. The url is just the file URL to play, which is the one we get from Amplify Storage. controls allows the user to change the volume or pause the song. playing means the song starts playing as soon as it's loaded and onPause is a listener so we can have the inbuilt pause button work the same as our pause button.

With this all done we can save everything again and open our App. In there if we press play on one of the songs, we should see a player appear and the song will be playing.

How to Enable User Uploads

Now we have an app which allows users to view all of the songs and play them from S3. To build upon that we want to allow our users to upload their own songs to the platform.

We first need to create a way to add a song and input some information. We start by creating an Add button.

{ showAddSong ?  :    } 

We then also need to add the AddIcon to our imports.

import AddIcon from '@material-ui/icons/Add'; 

Now we can move onto creating the new AddSong component. We can create this at the bottom of our App.jsx file.

const AddSong = () => { return ( ) } 

We also need to add TextField to our imports from material UI.

import { Paper, IconButton, TextField } from '@material-ui/core'; 

The next thing to do is add the ability to open our new component by controlling the showAddSong variable. We need to create a new state declaration next to the others.

const [showAddSong, setShowAddNewSong] = useState(false); 

We can now update our new AddIcon button to set showAddSong to true.

 setShowAddNewSong(true)}>

To change it back, we can add a parameter to our AddSong component called onUpload. When this gets called we will reset the showAddSong to false.

 { setShowAddNewSong(false); }} />

We then need to update our component to work with that new parameter and a button to "upload" the new song. That button calls a function in the component where we will add the ability to upload the data, but for now we will just call the onUpload function.

const AddSong = ({ onUpload }) => { const uploadSong = async () => { //Upload the song onUpload(); }; return ( ); }; 

And now we add the PublishIcon to our imports and we're ready to test this out.

import PublishIcon from '@material-ui/icons/Publish'; 

When we start up the app and log in we now get a plus icon. By clicking that we can enter some details for the song and click upload.

How to Update AddSong

Now we want to be able to store and access the data that a user enters into the fields when adding a song.

const AddSong = ({ onUpload }) => { const [songData, setSongData] = useState({}); const uploadSong = async () => { //Upload the song console.log('songData', songData); const { title, description, owner } = songData; onUpload(); }; return ( setSongData({ ...songData, title: e.target.value })} />  setSongData({ ...songData, owner: e.target.value })} />  setSongData({ ...songData, description: e.target.value })} /> ); }; 

We've also had to change all of the TextFields to be controlled, passing in a value from out state and providing an onChange too. If we save this and try entering some details before uploading we should see a console.log of the details in our chrome console.

Next we need to add the ability to actually upload the song. For this we'll be using the default html input with a type of file. Add this to the JSX just before the upload icon button.

 setMp3Data(e.target.files[0])} />

As you may have noticed we are calling setMp3Data on change. This is some more state in the AddSong component.

const [mp3Data, setMp3Data] = useState(); 

Now that we have all of the data that we need, we can start by uploading the song to S3 and then the data to our database.

To upload the song we're going to use the Amplify Storage class again. The fileName is going to be a UUID so we also need to run npm install --save uuid in our terminal and then import it at the top of our file import { v4 as uuid } from 'uuid';. We then pass in the mp3Data and a contentType and we get back an object with a key.

const { key } = await Storage.put(`${uuid()}.mp3`, mp3Data, { contentType: 'audio/mp3' }); 

Now that we have the key we can create the record for the song in the database. As there may be multiple songs with the same name, we'll use an UUID as the ID again.

const createSongInput = { id: uuid(), title, description, owner, filePath: key, like: 0, }; await API.graphql(graphqlOperation(createSong, { input: createSongInput })); 

To get this to work we need to import the createSong mutator that was created when we created the dynamo storage with Amplify.

import { updateSong, createSong } from './graphql/mutations'; 

The last thing that we need to do is make the app re-get the data from the database once we've finished uploading it. We can do this by adding a fetchSongs call as part of the onUpload function.

 { setShowAddNewSong(false); fetchSongs(); }} />

Nu når vi genindlæser siden, kan vi klikke for at tilføje en ny sang, indtaste detaljerne, vælge vores nye sang, uploade den og derefter afspille den fra appen.

Det var alt folkens!

Hvis du kan lide denne artikel (og tilhørende videoer) og vil hjælpe mig, så er det bedste, du kan gøre, at abonnere på min Youtube-kanal. Jeg opretter ugentlige videoer på AWS og Serverless for at hjælpe dig med at blive en bedre udvikler. Abonner her