Building a serverless Web app on AWS with Amplify, CDK(python) and Nuxt(Part 3)

Building a serverless Web app on AWS with Amplify, CDK(python) and Nuxt(Part 3)

Hi, Welcome to Part 3 of this post series. In Part 1, we covered the steps to get started in deploying an Amplify Application using AWS CDK.
In Part 2, we added a backend to the application with Amplify tools and Amplify CLI. We added categories such as authentication, API, and Storage.


In this part, we will start building the application from the login page.

Login Page

We will be using Amplify UI Components to quickly build a Sign Up/Login page/ reset password etc.
Amplify UI Components is an open-source UI toolkit that encapsulates cloud-connected workflows inside of cross-framework UI components.


Install these 2 modules before proceeding

npm install @aws-amplify/ui-components

@aws-amplify/ui-vue

Create a file called loginpage.vue inside the pages directory and type in this code

<template>
    <div class="container">
      <div class="content">
           <div class="left">
           <h2 class="article-title article-title--large">
                    <a href="#" class="article-link">The Newspaper for <mark class="mark mark--primary">Those</mark> who can <mark class="mark mark--secondary">Read</mark></a>
                </h2>
          </div>
        <div class="right">
         <amplify-authenticator v-if="authState !== 'signedin'" class="signIn" />
          <div class="authenticated" v-if="authState === 'signedin' && user">
          <update-profile :email="user.attributes.email"
          :userId="user.attributes.sub" :username="user.username" />
     <button @click="signOut">Sign Out</button>
    </div>
      </div>
    </div>
    </div>
</template>

<script>
import { onAuthUIStateChange } from '@aws-amplify/ui-components'
import { Auth }  from 'aws-amplify';
import UpdateProfile from '../components/UpdateProfile.vue';
    export default {
components:{UpdateProfile},
       created() {
    onAuthUIStateChange((authState, authData) => {
      this.authState = authState;
      this.user = authData;
      console.log(this.user);
    })
  },
  data() {
    return { user: undefined, authState: undefined }
  },
  methods: {
    signOut(){
      console.log("clicked sign out");
       Auth.signOut();
       this.authState = undefined;
    } 
  },
   beforeDestroy() {
    return onAuthUIStateChange;
  }

    }
</script>

amplify-authenticator provides ready-made UI components for building the authentication pages of our application faster.
We first verify if the user is signed in, by checking the authState.
If the user is signed in, grab the user details and send them as props to another page called updateprofile.vue.

If not, display the login screen. Here's how the login page looks like.

Screen Shot 2021-05-24 at 12.31.56.png

Update Page

In the components folder, create a file called UpdateProfile.vue and typing this code

<template>
    <div class="update-profile">


 <input type="file" @change="onFileChange" />

      <div id="preview">
    <img class= "profilepic" v-if="url" :src="url" />
  </div>

      <div class="sub-load">

      <Loading v-if="loading"/>


<input v-else type="submit" value="Upload Image" class="btn" @click="upload">


      </div>



   </div>
</template>

<script>
import { API } from 'aws-amplify';
import { Storage }  from 'aws-amplify';
import { v4 as uuidv4 } from 'uuid';
import profile_icon from '@/assets/profile.png';
import {createUser} from '../src/graphql/mutations';

    export default {

        mounted(){
            this.first_name = this.username;
            this.email = this.email;
            this.userId = this.userId;
            console.log(this.email);


        },

       props:{
           username:String,
           email:String,
           userId:String,
       },
       data(){
           return{
           first_name:String,
           url:profile_icon,
           filename:String,
           loading:false,
           filePath:String

           }
       },
       methods:{


 async addUserToDb(){
                const uuid = uuidv4();
               const {username,email,filename,userId} = this;
               const signedURL = await Storage.get(filename); 
               const user = {id:userId,username,email,profilePicUrl:signedURL,userType:"ADMIN"};

               console.log("signed url"+signedURL);
               await API.graphql({
                   query:createUser,
                   variables:{input:user},
               }).then((result) =>{
                   this.loading = false;
                   console.log("result is"+result);
               })
               this.loading = false;
               console.log("successfully uploaded");
               this.$router.push({name:'index',params:{id: uuid}});
           },

           onFileChange(e) {
      const file = e.target.files[0];
      console.log(file);
      this.filename =file.name;
      this.filePath = file; 

      this.url = URL.createObjectURL(file);
    },
    upload(e) {
        console.log(this.filename);
        this.loading = true;
       const result = Storage.put(this.filename, this.filePath, {

           contentType:this.filePath.type,
    progressCallback(progress) {
        if(`${progress.loaded}` === `${progress.total}` ){



        }
        console.log(`Uploaded: ${progress.loaded}/${progress.total}`);
  },
}).then((result) =>{
    this.addUserToDb();
})
    }

       }

    }
</script>

Here's what's happening. Firstly, we get the passed-in user data(username, email and userId) as props. Then we give the user the possibility to select an image as a profile picture. When the upload button is clicked, the image is uploaded to an s3 bucket, the pre-signed URL is gotten and saved to the user table through the createUser mutation of our GraphQl API.


Screen Shot 2021-05-24 at 12.32.21.png

Create post

In the pages folder, create e file called createArticle.vue and type in this code

<template>
    <div class="container">
      <div class="content">
           <div class="left">
           <h2 class="article-title article-title--large">
                    <a href="#" class="article-link">Talk <mark class="mark mark--tertiary">That</mark> Talk</a>
                </h2>
          </div>
        <div class="right">


        <div v-if="image" class="img-container">
            <input class="pickImage" type="file" @change="onFileChange" />

      <div id="preview-pic">
    <img class= "postpic" v-if="url" :src="url" />
  </div>
        </div>
  <div class="checkbox">

  <input type="checkbox" name="noImage" id="noImage" class="noImageCheck" @click="showImagePicker">
   <label for="noImage" class="noImage"> No Image</label>
  </div>
  <input type="text" class="postTitle" placeholder= "Post title" v-model="postTitle" name="postTitle" id="postTitle">
        <textarea placeholder="What's new ?" v-model="postText" class="article__post" rows="4" cols="10"/><br>

       <Loading v-if="loading" />
        <input type="submit" v-else-if="image" value="Create Article" class="article__btn" @click="addPostWithImage">
        <input type="submit" v-else value="Create Article" class="article__btn" @click="addPostWithoutImage">


      </div>
    </div>

    </div>

</template>

<script>
import { onAuthUIStateChange } from '@aws-amplify/ui-components'
import { Auth,API,Storage }  from 'aws-amplify';
import add_image from '@/assets/add_pic.svg';

import { v4 as uuidv4 } from 'uuid';
import {createPost} from '../src/graphql/mutations';
    export default {
mounted() {
    console.log('Params: ', this.$route.params);
    this.userId = this.$route.params.userId;
    console.log("user id is"+this.userId);


},


        data(){
            return {
                postTitle:"",
               postText:'',
               loading:false,
               image:true,
              url:add_image,
               filename:String,
           filePath:String,
            }
        },
         methods:{
             showImagePicker(){
               this.image = !this.image;
             },
           async addPostToDb(){
                const uuid = uuidv4();
               const {userId,postText,filename,postTitle,image} = this;
               const signedURL = await Storage.get(filename); 
               const post = {id:uuid,userId:userId,postText,title:postTitle,postType:"BUSINESS",postStatus:"CREATED",postImageUrl:signedURL,image};

               console.log("signed url"+signedURL);
               await API.graphql({
                   query:createPost,
                   variables:{input:post},
               }).then((result) =>{
                   console.log("result is"+result);
                   this.loading = false;
               })
               console.log("successfully uploaded");
               this.postText = "";
               this.loading = false;
             //this.$emit('close');
           },

           onFileChange(e) {
      const file = e.target.files[0];
      console.log(file);
      this.filename =file.name;
      this.filePath = file; 

      this.url = URL.createObjectURL(file);
    },
     addPostWithImage(e) {
        console.log("clicked");
        this.loading = true;

       const result = Storage.put(this.filename, this.filePath, {

           contentType:this.filePath.type,
    progressCallback(progress) {
        if(`${progress.loaded}` === `${progress.total}` ){



        }
        console.log(`Uploaded: ${progress.loaded}/${progress.total}`);
  },
}).then((result) =>{
    this.addPostToDb();
})
    },
   async addPostWithoutImage(e){
         const uuid = uuidv4();
               const {userId,postText,postTitle,image} = this;

               const post = {id:uuid,userId:userId,postText,title:postTitle,postType:"BUSINESS",postStatus:"CREATED",image};

               await API.graphql({
                   query:createPost,
                   variables:{input:post},
               }).then((result) =>{
                   console.log("result is"+result);
                   this.loading = false;
               })
               console.log("successfully uploaded");
               this.postText = "";
               this.loading = false;
    }

         }

    }
</script>

This piece of code is similar to the updateprofile code.
You give the user the possibility to upload a post with or without an image. You save the post in the post table using createPost mutation .

Screen Shot 2021-05-24 at 12.34.42.png

Index page In the pages folder, open up the index.vue file and type in the following code

<template>
    <div>
<header class="header responsive-wrapper">
    <div class="header-left">
        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="233.445" height="52.805" viewBox="0 0 233.445 52.805">
            <defs>
                <clipPath id="clip-fox-news-logo">
                    <rect width="233.445" height="52.805" />
                </clipPath>
            </defs>
            <g id="fox-news-logo" clip-path="url(#clip-fox-news-logo)">
                <g id="Group_1" data-name="Group 1" transform="translate(-2179.585 601.093)">
                    <path id="Path_5" data-name="Path 5" d="M4.185-29.205H22.59v4.59H9.5v7.7h11.97v4.545H9.5V0H4.185Zm27.675,6.75a12.117,12.117,0,0,1,4.14.7,8.656,8.656,0,0,1,3.33,2.138q3.105,3.15,3.105,8.73,0,5.49-3.105,8.64A9.921,9.921,0,0,1,31.86.63,10.222,10.222,0,0,1,24.3-2.25Q21.2-5.355,21.2-10.89q0-5.625,3.105-8.73A10.217,10.217,0,0,1,31.86-22.455Zm5.265,11.52a8.372,8.372,0,0,0-1.35-5.2,4.5,4.5,0,0,0-3.757-1.733,5.013,5.013,0,0,0-3.937,1.733,7.654,7.654,0,0,0-1.53,5.2,8.27,8.27,0,0,0,1.372,5.2A4.534,4.534,0,0,0,31.68-4.005,4.989,4.989,0,0,0,35.6-5.738,7.654,7.654,0,0,0,37.125-10.935ZM64.62,0H58.14l-5.4-7.02L47.7,0H41.175l8.5-10.89-8.5-11.025H47.7l5.355,7.02,5.085-7.02h6.48L56.16-10.89ZM78.48-29.205l12.87,20.7v-20.7h5.31V0h-5.4L77.985-21.42V0h-5.31V-29.205Zm37.71,9.945a9.966,9.966,0,0,1,1.688,3.33,12.23,12.23,0,0,1,.563,3.465,20.182,20.182,0,0,1-.18,2.835H104.13a7.606,7.606,0,0,0,1.305,4.32,4.268,4.268,0,0,0,3.69,1.665q3.285,0,3.915-2.88H118.4A7.849,7.849,0,0,1,115.38-1.35,9.93,9.93,0,0,1,109.125.63a9.489,9.489,0,0,1-7.245-3.15,11.96,11.96,0,0,1-3.015-8.415,11.822,11.822,0,0,1,3.015-8.46,9.529,9.529,0,0,1,7.245-3.06A8.486,8.486,0,0,1,116.19-19.26Zm-7.02,1.035a4.385,4.385,0,0,0-3.51,1.463,6.578,6.578,0,0,0-1.485,3.758h9.315q0-3.555-2.25-4.77A4.4,4.4,0,0,0,109.17-18.225Zm13.455-3.645,4.41,15.615,4.185-14.4h4.815L140.22-6.21l4.455-15.66h5.31L142.785,0h-5.22l-4.05-13.1L129.555,0H124.29l-7.245-21.87Zm31.05,15.21q.09,3.105,4.545,3.1a4.934,4.934,0,0,0,2.813-.743,2.1,2.1,0,0,0,1.1-1.733,1.847,1.847,0,0,0-.675-1.53,6.639,6.639,0,0,0-2.115-.99l-4.77-1.71q-5.31-1.845-5.31-6.03a5.172,5.172,0,0,1,2.5-4.477,11.8,11.8,0,0,1,6.7-1.687,10.044,10.044,0,0,1,6.48,1.845,6,6,0,0,1,2.362,4.635h-4.995a2.191,2.191,0,0,0-1.035-1.71,4.856,4.856,0,0,0-2.7-.63,4.6,4.6,0,0,0-2.723.7,1.974,1.974,0,0,0-.967,1.6q0,1.575,2.655,2.34l5.175,1.935a10.264,10.264,0,0,1,3.938,2.182A4.576,4.576,0,0,1,168.03-6.12a5.8,5.8,0,0,1-2.565,4.68,11.1,11.1,0,0,1-7.02,2.07,11.753,11.753,0,0,1-7.02-1.913,6.353,6.353,0,0,1-2.7-5.377Z" transform="translate(2245 -561)" />
                    <path id="Path_1" data-name="Path 1" d="M2207.653-46.775c-.029,5.98,9.656-7.959,11.9-8.762a45.226,45.226,0,0,1,9.4-2.275,19.066,19.066,0,0,1,2.663-.116c4.366.13-3.9-18.154-3.9-18.154l-8.853.386s-6.824,3.109-7.306,9.208,1.265,4.691-.822,10.951c-1.45,4.351-2.432,3.276-3.084,5.217A25.988,25.988,0,0,0,2207.653-46.775Z" transform="translate(-0.475 -503)" />
                    <path id="Path_2" data-name="Path 2" d="M2232.822-46.775c.029,5.98-9.656-7.959-11.9-8.762a45.226,45.226,0,0,0-9.4-2.275,19.066,19.066,0,0,0-2.663-.116c-4.366.13,3.9-18.154,3.9-18.154l8.853.386s6.824,3.109,7.306,9.208-1.265,4.691.822,10.951c1.45,4.351,2.432,3.276,3.084,5.217A25.988,25.988,0,0,1,2232.822-46.775Z" transform="translate(-28 -503)" />
                    <circle id="Ellipse_1" data-name="Ellipse 1" cx="1" cy="1" r="1" transform="translate(2205 -559)" />
                    <path id="Path_3" data-name="Path 3" d="M2216.209-91.186c.2,1.456,9.045,4.389,11.518,8.66s3.761-15.174,2.68-16.427S2216.012-92.642,2216.209-91.186Z" transform="translate(0 -502)" />
                    <path id="Path_4" data-name="Path 4" d="M2230.791-91.186c-.2,1.456-9.045,4.389-11.518,8.66s-3.761-15.174-2.68-16.427S2230.988-92.642,2230.791-91.186Z" transform="translate(-34 -502)" />
                </g>
            </g>
        </svg>
    </div>
    <div class="header-middle">
        <a href="#" class="header-link">Call Us (237) 678 323 387</a>
        <span>/</span>
        <a href="#" class="header-link">rosius@ghost.com</a>
    </div>
    <div class="header-right">
    <nav class="header-nav">


        <a href="#" class="header-link" v-if="signedIn" @click="signUserOut">Logout</a>
       <NuxtLink  to="/loginpage" class="header-link" v-else> Login </NuxtLink>




        <nuxt-link :to="{name:'createArticle',params:{userId:user.id}}" class="header-link header-link--button">Write Article</nuxt-link>
    </nav>

       <img class="article-author-img" :src="user.profilePicUrl" alt="Profile Picture">


        </div>
</header>
<main class="responsive-wrapper">
    <div class="page-title">
        <h1>Latest Updates</h1>
    </div>
    <div class="magazine-layout">
        <div class="magazine-column">

            <article class="article">
                <h2 class="article-title article-title--large">
                    <a href="#" class="article-link">The First Signs of <mark class="mark mark--primary">Alcoholic Liver</mark> Damage Are Not in the Liver</a>
                </h2>
                <div class="article-excerpt">
                    <p>The combination of my father's death and my personal back ground lit a fire in me to know more</p>
                    <p>He was admitted to the hospital on June 24, 2016.</p>
                </div>
                <div class="article-author">
                    <div class="article-author-img">
                        <img src="https://assets.codepen.io/285131/author-3.png" />
                    </div>
                    <div class="article-author-info">
                        <dl>
                            <dt>David Sherof</dt>
                            <dd>Reporter</dd>
                        </dl>
                    </div>
                </div>
            </article>

        </div>
        <div class="magazine-column"  v-for="post in posts" :key="post.id">
            <article v-if="post.image" class="article">
                <figure class="article-img">
                    <img :src="post.postImageUrl" />
                </figure>
                <mark class="mark mark--secondary article-title">{{post.postType}}</mark>
                <h2 class="article-title article-title--medium">
                    <a href="#" class="article-link">{{post.title}}</a>
                </h2>
                <div class="article-excerpt">
                    <p>{{post.postText}}</p>
                </div>
                <div class="article-author">
                    <div class="article-author-img">
                        <img :src="post.user.profilePicUrl" />
                    </div>
                    <div class="article-author-info">
                        <dl>
                            <dt>{{post.user.username}}</dt>
                            <dd>{{post.user.userType}}</dd>
                        </dl>
                    </div>
                </div>
            </article>
            <article v-else class="article">
                <h2 class="article-title article-title--large">
                    <a href="#" class="article-link"> <mark class="mark mark--primary">{{post.title}}</mark> </a>
                </h2>
                <div class="article-excerpt">
                    {{post.postText}}
                </div>
                <div class="article-author">
                    <div class="article-author-img">
                        <img :src="post.user.profilePicUrl" />
                    </div>
                    <div class="article-author-info">
                        <dl>
                            <dt>{{post.user.username}}</dt>
                            <dd>{{post.user.userType}}</dd>
                        </dl>
                    </div>
                </div>
            </article>

        </div>
        <div class="magazine-column">
            <article class="article">
                <figure class="article-img">
                    <img src="https://images.unsplash.com/photo-1512521743077-a42eeaaa963c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1351&q=80" />
                </figure>
                <h2 class="article-title article-title--small">
                    <a href="#" class="article-link">To Become <mark class="mark mark--secondary">Happier</mark>, Ask Yourself These Two Questions Every Night</a>
                </h2>
                <div class="article-creditation">
                    <p>By Jonathan O'Connell</p>
                </div>
            </article>
            <article class="article">
                <figure class="article-img">
                    <img src="https://images.unsplash.com/photo-1569234817121-a2552baf4fd4?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80" />
                </figure>
                <h2 class="article-title article-title--small">
                    <a href="#" class="article-link">10 Things I Stole From People Smarter Than Me</a>
                </h2>
                <div class="article-creditation">
                    <p>By Jonathan O'Connell</p>
                </div>
            </article>
        </div>
        <div class="magazine-column">
            <article class="article">
                <h2 class="article-title article-title--medium">
                    <a href="#" class="article-link">Traveller Visiting Ice Cave With Amazing Eye-Catching Scenes</a>
                </h2>
                <div class="article-excerpt">
                    <p>Slack has become indispensible for many businesses operation remotely during the pandemic. Here's what the acquisition could mean for users...</p>
                </div>
                <div class="article-author">
                    <div class="article-author-img">
                        <img src="https://assets.codepen.io/285131/author-2.png" />
                    </div>
                    <div class="article-author-info">
                        <dl>
                            <dt>James Robert</dt>
                            <dd>Editor</dd>
                        </dl>
                    </div>
                </div>
            </article>
            <article class="article">
                <small class="article-category">
                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="28" height="22" viewBox="0 0 28 22">
  <defs>
    <clipPath id="clip-headphones">
      <rect width="28" height="22"/>
    </clipPath>
  </defs>
  <g id="headphones" clip-path="url(#clip-headphones)">
    <g id="Group_2" data-name="Group 2" transform="translate(-3680 -609)">
      <path id="Subtraction_1" data-name="Subtraction 1" d="M5,12H5A5.274,5.274,0,0,1,0,6.5,5.274,5.274,0,0,1,5,1V12Z" transform="translate(3680 619)"/>
      <rect id="Rectangle_18" data-name="Rectangle 18" width="2" height="11" transform="translate(3686 620)"/>
      <path id="Subtraction_5" data-name="Subtraction 5" d="M1.243,12H.045C.015,11.67,0,11.334,0,11A11,11,0,0,1,18.778,3.222,10.925,10.925,0,0,1,22,11c0,.331-.015.667-.045,1h-1.2a14.108,14.108,0,0,0-2.685-6.241A8.982,8.982,0,0,0,11,2,8.982,8.982,0,0,0,3.929,5.759,14.109,14.109,0,0,0,1.243,12Z" transform="translate(3683 609)"/>
      <path id="Subtraction_6" data-name="Subtraction 6" d="M5,0H5A5.274,5.274,0,0,0,0,5.5,5.274,5.274,0,0,0,5,11V0Z" transform="translate(3708 631) rotate(180)"/>
      <rect id="Rectangle_23" data-name="Rectangle 23" width="2" height="11" transform="translate(3700 620)"/>
      <path id="Path_8" data-name="Path 8" d="M.156-.031,2.125-1.687,2,2H0Z" transform="translate(3683 619)"/>
      <path id="Path_9" data-name="Path 9" d="M1.969-.031,0-1.687.125,2h2Z" transform="translate(3702.875 619)"/>
    </g>
  </g>
</svg>
                    <span>Post Reports / Podcast</span>
                </small>
                <h2 class="article-title article-title--medium">
                    <a href="#" class="article-link">Things to Do After 6 P.M Will Enrich <mark class="mark mark--tertiary">Your Life</mark></a>
                </h2>
                <div class="article-podcast-player">
                    <button class="podcast-play-button">
                        <svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" fill="#000000" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"></rect><path d="M232.3125,114.34375,88.34375,26.35937A15.99781,15.99781,0,0,0,64,40.00781V215.99219a16.00521,16.00521,0,0,0,24.34375,13.64843L232.3125,141.65625a16.00727,16.00727,0,0,0,0-27.3125Z"></path></svg>
                    </button>
                    <div class="podcast-progression">

                    </div>
                    <span class="podcast-time">23:45</span>
                </div>
                <div class="article-podcast-info">
                    Apple Podcasts, Google Podcasts, Stitcher
                </div>
                <div class="article-author">
                    <div class="article-author-img">
                        <img src="https://assets.codepen.io/285131/author-3.png" />
                    </div>
                    <div class="article-author-info">
                        <dl>
                            <dt>David Sherof</dt>
                            <dd>Reporter</dd>
                        </dl>
                    </div>
                </div>
            </article>
        </div>
    </div>
</main>
    </div>
</template>

<script>
import { onAuthUIStateChange } from '@aws-amplify/ui-components'
import { Auth,API }  from 'aws-amplify';
import {onCreatePost} from '../src/graphql/subscriptions';
import {getUser,listPosts} from '../src/graphql/queries';

    export default {
         data() {
    return { user: Object, 
    posts:[],
    authState: undefined,
     signedIn:false }
  },
 async beforeCreate() {

    try {
      const user = await Auth.currentAuthenticatedUser();
      console.log(user.attributes.sub);
      this.getUserDetails(user.attributes.sub);

      this.signedIn = true
    } catch (err) {
      this.signedIn = false
    }

 },

  created() {

  this.getPosts();
  //this.subscribe();

  },

   methods: {
        async getUserDetails(userId){
         const userData =  await API.graphql({
         query:getUser,
         variables:{id: userId}
       });
       this.user =userData.data.getUser;
console.log(this.user);
    },
    signUserOut(){
      console.log("clicked sign out");
       Auth.signOut();

       this.$router.push({name:'loginpage'});
    } ,
    async getPosts(){

       const posts = await API.graphql({
         query:listPosts


       });
       this.posts = posts.data.listPosts.items;
       console.log(posts.data.listPosts);
     },


  },




    }
</script>

<style lang="scss" scoped>
.profile-pic{
    object-fit: cover;
    width: 40px;
    height: 40px;
}
</style>

Firstly, we determine the authentication state of the application. We check to see if any user is currectly signed in .

  try {
      const user = await Auth.currentAuthenticatedUser();
      console.log(user.attributes.sub);
      this.getUserDetails(user.attributes.sub);

      this.signedIn = true
    } catch (err) {
      this.signedIn = false
    }

 },

If true, we display the logout button while getting the user details. If false, we display the login button.


We also retrieve and display the list of posts in our posts table from the dynamoDB.

 async getPosts(){

       const posts = await API.graphql({
         query:listPosts


       });
       this.posts = posts.data.listPosts.items;
       console.log(posts.data.listPosts);
     },


  },

Github

Get the complete code here

What Next

There's still a ton of things to be done with the application. I wrote the app on a weekend, and I know there are a lot of bugs in it.
Challenge yourself to add more features and resolve any bugs you find within the app.

Thanks for taking out the time to check this out. I really appreciate it.
I'll love to know what you think about this piece.
Stay Safe ❤️