DEV Community

Cover image for AWS Project: Build a Basic Web Application
Asif Khan
Asif Khan

Posted on

AWS Project: Build a Basic Web Application

Introduction

This project involves building a full-stack web application using AWS Amplify. It features a simple React frontend with user authentication, a serverless function to handle user sign-ups, and a DynamoDB database for storing user emails. The application leverages AWS’s robust and scalable cloud services to deliver a seamless user experience, allowing users to sign up, log in, and store information securely.

Why Use AWS Amplify for This Project?

AWS Amplify simplifies the process of deploying full-stack web applications by providing ready-to-use backend services, including hosting, authentication, API management, and data storage. By using Amplify, you can focus on building the application’s functionality rather than managing infrastructure.

What You Will Learn

  • Host: Build and deploy a React application on the AWS global content delivery network (CDN).
  • Authenticate: Add authentication to your app to enable sign-in and sign-out functionality.
  • Database: Integrate a real-time API, database, and a serverless function.
  • Function: Implement a lambda function that is triggered when a user signs up to the App.

Tech Stack

This project uses the following services and technologies:

  • AWS Amplify: Provides end-to-end services for hosting and managing the web application.
  • AWS AppSync: Facilitates real-time API creation and management.
  • AWS Lambda: Runs serverless functions for handling user data.
  • Amazon DynamoDB: A NoSQL database for storing user emails.
  • React: The frontend framework used to build the web application.
  • Node.js & npm: For managing dependencies and running the React app.
  • Git & GitHub: Version control and repository hosting.

Prerequisites

Before starting the project, ensure you have the following:

  • Basic AWS Knowledge: Familiarity with services such as Amplify, Lambda, and DynamoDB.
  • AWS CLI configured: With appropriate IAM permissions for managing AWS Amplify, Lambda, and DynamoDB.
  • Node.js and npm: Installed locally for running and managing the React application.
  • Git: For version control and integration with AWS Amplify.

Problem Statement or Use Case

Web applications today require features like user authentication, data storage, and API integration. Setting up and managing these services can be challenging, especially for scalability and real-time capabilities. Using AWS Amplify simplifies this by offering a fully managed backend that integrates seamlessly with the frontend.

Solution: This project provides a user sign-up and sign-in system with a backend that captures and stores user data. This setup is applicable to various real-world scenarios, from building social media platforms to e-commerce websites, where user management and data storage are essential.

Architecture Diagram

Image description

Step-by-Step Implementation

Tasks

This tutorial is divided into six tasks. You must complete each task in order before moving on to the next one:

  1. Create Web App: Deploy static resources for your web application using the AWS Amplify Console.
  2. Build Serverless Function: Build a serverless function using AWS Lambda.
  3. Create Data Table: Persist data in an Amazon DynamoDB table.
  4. Link Serverless Function to Web App: Deploy your serverless function with API Gateway.
  5. Add Interactivity to Web App: Modify your web app to invoke your API.
  6. Clean Up Resources: Clean up resources used in this tutorial.

You will be building this web application using the AWS Management Console accessible directly from your browser.

Task 1: Create a Web App

Overview

AWS Amplify offers a Git-based CI/CD workflow for building, deploying, and hosting single-page web applications or static sites with backends. When connected to a Git repository, Amplify determines the build settings for both the frontend framework and any configured backend resources and automatically deploys updates with every code commit.

In this task, you will start by creating a new React application and pushing it to a GitHub repository. You will then connect the repository to AWS Amplify web hosting and deploy it to a globally available content delivery network (CDN) hosted on an amplifyapp.com domain.

Key Concepts

  • React Application: React is a JavaScript library that enables developers to quickly build performant single-page applications.
  • Git: Git is a version control system that allows developers to store files, maintain and update relationships between files and directories, and track versions and changes to the files.

Implementation

Step 1: Create a new React application

In a new terminal or command line window, run the following command to use Vite to create a React application:

npm create vite@latest profilesapp -- --template react
cd profilesapp
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

Image description

  1. In the terminal window, select and open the Local link to view the Vite + React application.

Image description

Step 2: Initialize a GitHub repository

In this step, you will create a GitHub repository and commit your code to the repository. You will need a GitHub account to complete this step; if you do not have an account, sign up here.

Image description

Note: If you have never used GitHub on your computer, follow these steps before continuing to allow connection to your account.

  1. Sign in to GitHub at https://github.com/.

  2. In the "Start a new repository" section, make the following selections:

    • For Repository name, enter profilesapp, and choose the Public radio button.
    • Then select Create a new repository.

Image description

  1. Open a new terminal window, navigate to your project's root folder (profilesapp), and run the following commands to initialize Git and push the application to the new GitHub repository:
git init
git add .
git commit -m "first commit"
git remote add origin git@github.com:<your-username>/profilesapp.git
git branch -M main
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Image description

Step 3: Install the Amplify packages

Open a new terminal window, navigate to your app's root folder (profilesapp), and run the following command:

npm create amplify@latest -y
Enter fullscreen mode Exit fullscreen mode

Image description

  1. Running the previous command will scaffold a lightweight Amplify project in the app’s directory.

Image description

  1. In your terminal window, run the following command to push the changes to GitHub:
git add .
git commit -m 'installing amplify'
git push origin main
Enter fullscreen mode Exit fullscreen mode

Image description

Step 4: Deploy your app with AWS Amplify

  1. Sign in to the AWS Management console in a new browser window and open the AWS Amplify console at https://console.aws.amazon.com/amplify/apps.

  2. Choose Create new app.

Image description

  1. On the Start building with Amplify page, for Deploy your app, select GitHub, and select Next.

Image description

  1. When prompted, authenticate with GitHub. You will be automatically redirected back to the Amplify console. Choose the repository and main branch you created earlier. Then select Next.

Image description

  1. Leave the default build settings, and select Next.

Image description

  1. Review the inputs selected, and choose Save and deploy.

AWS Amplify will now build your source code and deploy your app at https://...amplifyapp.com, and on every git push, your deployment instance will update. It may take up to 5 minutes to deploy your app.

Image description

  1. Once the build completes, select the Visit deployed URL button to see your web app up and running live.

Image description

Conclusion

You have deployed a React application in the AWS Cloud by integrating with GitHub and using AWS Amplify. With AWS Amplify, you can continuously deploy your application in the Cloud and host it on a globally available CDN.

Task 2: Build a Serverless Function

Overview

Now that you have a React web app, you will use AWS Amplify and AWS Lambda to configure a serverless function. This function is invoked after a signed-up user confirms their user account. AWS Lambda is a compute service that runs your code in response to events and automatically manages the compute resources, making it the fastest way to turn an idea into modern, production, serverless applications.

Key Concepts

  • Serverless function: A piece of code that will be executed by a compute service, on demand.

Implementation

Step 1: Setup an Amplify Function

  1. On your local machine, navigate to the profilesapp/amplify/auth folder and create a new folder inside the amplify/auth folder, naming it post-confirmation, and then create the files named resource.ts and handler.ts inside the folder.

Image description

  1. Update the amplify/auth/post-confirmation/resource.ts file with the following code to define the postConfirmation function:
import { defineFunction } from '@aws-amplify/backend';

export const postConfirmation = defineFunction({
  name: 'post-confirmation',
});
Enter fullscreen mode Exit fullscreen mode

Image description

  1. Update the amplify/auth/post-confirmation/handler.ts file with the following code to define the function’s handler:
import type { PostConfirmationTriggerHandler } from "aws-lambda";

export const handler: PostConfirmationTriggerHandler = async (event) => {
  return event;
};
Enter fullscreen mode Exit fullscreen mode

Image description

Conclusion

You have defined a Lambda function using Amplify.

Task 3: Create a Data Table

Overview

In this task, you will create a data model to persist data using a GraphQL API and Amazon DynamoDB. Additionally, you will use AWS Identity and Access Management (IAM) authorization to securely give our services the required permissions to interact with each other. You will also allow the Lambda function you created in the previous task to use the GraphQL API to write to your newly created DynamoDB table using an IAM policy.

Key Concepts

  • Amplify backend: Amplify Gen 2 uses a full-stack TypeScript developer experience (DX) for defining backends. Simply author app requirements like data models, business logic, and auth rules in TypeScript. Amplify automatically configures the correct cloud resources and deploys them to per-developer cloud sandbox environments for fast, local iteration.

Implementation

Step 1: Setup Amplify Data

  1. On your local machine, navigate to the amplify/data/resource.ts file and update it with the following code:
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
import { postConfirmation } from "../auth/post-confirmation/resource";

const schema = a
  .schema({
    UserProfile: a
      .model({
        email: a.string(),
        profileOwner: a.string(),
      })
      .authorization((allow) => [
        allow.ownerDefinedIn("profileOwner"),
      ]),
  })
  .authorization((allow) => [allow.resource(postConfirmation)]);

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: "apiKey",
    apiKeyAuthorizationMode: {
      expiresInDays: 30,
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Image description

  1. Open a new terminal window, navigate to your app's root folder (profilesapp), and run the following command to deploy cloud resources into an isolated development space:
npx ampx sandbox
Enter fullscreen mode Exit fullscreen mode

Image description

  1. Once the cloud sandbox has been fully deployed, your terminal will display a confirmation message, and the amplify_outputs.json file will be generated and added to your project.

Image description

Image description

  1. Open a new terminal window, navigate to your app's root folder (profilesapp), and run the following command to generate the GraphQL client code to call your data backend:
npx ampx generate graphql-client-code --out <path-to-post-confirmation-handler-dir>/graphql
Enter fullscreen mode Exit fullscreen mode

Amplify will create the folder amplify/auth/post-confirmation/graphql, where you will find the client code to connect to the GraphQL API.

Image description

Step 2: Modify Lambda Function to Connect to the API

  1. On your local machine, navigate to the amplify/auth/post-confirmation/handler.ts file and replace the code with the following:
import type { PostConfirmationTriggerHandler } from "aws-lambda";
import { type Schema } from "../../data/resource";
import { Amplify } from "aws-amplify";
import { generateClient } from "aws-amplify/data";
import { env } from "$amplify/env/post-confirmation";
import { createUserProfile } from "./graphql/mutations";

Amplify.configure(
  {
    API: {
      GraphQL: {
        endpoint: env.AMPLIFY_DATA_GRAPHQL_ENDPOINT,
        region: env.AWS_REGION,
        defaultAuthMode: "iam",
      },
    },
  },
  {
    Auth: {
      credentialsProvider: {
        getCredentialsAndIdentityId: async () => ({
          credentials: {
            accessKeyId: env.AWS_ACCESS_KEY_ID,
            secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
            sessionToken: env.AWS_SESSION_TOKEN,
          },
        }),
        clearCredentialsAndIdentityId: () => {
          /* noop */
        },
      },
    },
  }
);

const client = generateClient<Schema>({
  authMode: "iam",
});

export const handler: PostConfirmationTriggerHandler = async (event) => {
  await client.graphql({
    query: createUserProfile,
    variables: {
      input: {
        email: event.request.userAttributes.email,
        profileOwner: `${event.request.userAttributes.sub}::${event.userName}`,
      },
    },
  });

  return event;
};
Enter fullscreen mode Exit fullscreen mode

Image description

Conclusion

You have created a data table and configured a GraphQL API to persist data in an Amazon DynamoDB database. Then, you updated the Lambda function to use the API.

Task 4: Link a Serverless Function to a Web App

Overview

In this task, you will update the Amplify Auth resources to use the Lambda function created in the previous module as an Amazon Cognito post-confirmation invocation. When the user completes the sign-up, the function will use the GraphQL API and capture the user’s email into the DynamoDB table.

Key Concepts

  • Lambda invocation: The type of event that will make a Lambda (serverless) function run. This can be another AWS service or an external input.

Implementation

Step 1: Setup Amplify Auth

  1. On your local machine, navigate to the amplify/auth/resource.ts file and update it with the following code:
import { defineAuth } from '@aws-amplify/backend';
import { postConfirmation } from './post-confirmation/resource';

export const auth = defineAuth({
  loginWith: {
    email: true,
  },
  triggers: {
    postConfirmation
  }
});
Enter fullscreen mode Exit fullscreen mode

Image description

  1. The sandbox will automatically get updated and redeployed once the file is updated. If the sandbox is not running, you can run the following command in a new terminal window:
npx ampx sandbox
Enter fullscreen mode Exit fullscreen mode
  1. Once the cloud sandbox has been fully deployed, your terminal will display a confirmation message, and the amplify_outputs.json file will be generated/updated and added to your profilesapp project.

Image description

Image description

Conclusion

You used Amplify to configure authentication and set up the Lambda function to be invoked when the user signs in to the app.

Task 5: Add Interactivity to Your Web App

Overview

In this task, you will create a frontend for your app and connect it to the cloud backend you have already built. You will update the website to use the Amplify UI component library to scaffold out an entire user authentication flow, allowing users to sign up, sign in, reset their password, and invoke the GraphQL API.

Key Concepts

  • Amplify libraries: The Amplify libraries allow you to interact with AWS services from a web or mobile application.

Implementation

Step 1: Install the Amplify libraries

In a new terminal window, in your project's root folder (profilesapp), run the following command:

npm install aws-amplify @aws-amplify/ui-react
Enter fullscreen mode Exit fullscreen mode

Image description

Step 2: Style the app UI

On your local machine, navigate to the profilesapp/src/index.css file and update it with the following code:

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.87);
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
}

.card {
  padding: 2em;
}

.read-the-docs {
  color: #888;
}

.box:nth-child(3n + 1) {
  grid-column: 1;
}
.box:nth-child(3n + 2) {
  grid-column: 2;
}
.box:nth-child(3n + 3) {
  grid-column: 3;
}
Enter fullscreen mode Exit fullscreen mode

Image description

Step 3: Implement the UI

On your local machine, navigate to the profilesapp/src/main.jsx file and update it with the following code:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { Authenticator } from "@aws-amplify/ui-react";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Authenticator>
      <App />
    </Authenticator>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

Image description

  1. Open the profilesapp/src/App.jsx file and update it with the following code:
import { useState, useEffect } from "react";
import {
  Button,
  Heading,
  Flex,
  View,
  Grid,
  Divider,
} from "@aws-amplify/ui-react";
import { useAuthenticator } from "@aws-amplify

/ui-react";
import { Amplify } from "aws-amplify";
import "@aws-amplify/ui-react/styles.css";
import { generateClient } from "aws-amplify/data";
import outputs from "../amplify_outputs.json";

Amplify.configure(outputs);
const client = generateClient({
  authMode: "userPool",
});

export default function App() {
  const [userprofiles, setUserProfiles] = useState([]);
  const { signOut } = useAuthenticator((context) => [context.user]);

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

  async function fetchUserProfile() {
    const { data: profiles } = await client.models.UserProfile.list();
    setUserProfiles(profiles);
  }

  return (
    <Flex
      className="App"
      justifyContent="center"
      alignItems="center"
      direction="column"
      width="70%"
      margin="0 auto"
    >
      <Heading level={1}>My Profile</Heading>

      <Divider />

      <Grid
        margin="3rem 0"
        autoFlow="column"
        justifyContent="center"
        gap="2rem"
        alignContent="center"
      >
        {userprofiles.map((userprofile) => (
          <Flex
            key={userprofile.id || userprofile.email}
            direction="column"
            justifyContent="center"
            alignItems="center"
            gap="2rem"
            border="1px solid #ccc"
            padding="2rem"
            borderRadius="5%"
            className="box"
          >
            <View>
              <Heading level="3">{userprofile.email}</Heading>
            </View>
          </Flex>
        ))}
      </Grid>
      <Button onClick={signOut}>Sign Out</Button>
    </Flex>
  );
}
Enter fullscreen mode Exit fullscreen mode

Image description

Step 4: Run the Application

  1. Open a new terminal window, navigate to your project's root directory (profilesapp), and run the following command to launch the app:
npm run dev
Enter fullscreen mode Exit fullscreen mode
  1. Select the Local host link to open the Vite + React application.

Image description

  1. Choose the Create Account tab and use the authentication flow to create a new user by entering your email address and a password. Then, choose Create Account.

  2. You will receive a verification code sent to your email. Enter the verification code to log in to the app. When signed in, the app will display your email address.

Image description

  1. In the open terminal window, run the following command to push the changes to GitHub:
git add .
git commit -m 'displaying user profile'
git push origin main
Enter fullscreen mode Exit fullscreen mode

Image description

  1. Sign in to the AWS Management console in a new browser window, and open the AWS Amplify console at https://console.aws.amazon.com/amplify/apps.

  2. AWS Amplify automatically builds your source code and deploys your app at https://...amplifyapp.com. Select the Visit deployed URL button to see your web app up and running live.

Image description

Conclusion

You have successfully added interactivity to your web app, allowing users to sign up, log in, and view their profiles using AWS Amplify’s powerful UI components.

Task 6: Clean Up Resources

Overview

To finish this tutorial, you will clean up the resources created throughout. It is a best practice to delete resources you are no longer using to avoid unwanted charges.

Implementation

  1. In the Amplify console, in the left-hand navigation for the profilesapp, choose App settings and select General settings.
  2. In the General settings section, choose Delete app.

Image description

Congratulations!

You have successfully created a React web app and used AWS Amplify to configure authentication and data resources. Additionally, you created a function to update the data model with user details upon sign-up. Then, you built a frontend to display the captured data. Finally, you used AWS Amplify to host the app!

Feel free to explore further and expand on this project as needed. The sky's the limit with AWS Amplify!

Challenges Faced and Solutions

  • Challenge 1: Configuring User Authentication
    AWS Amplify simplifies authentication setup, but customization may require configuration adjustments in the Amplify Console.

  • Challenge 2: Data Consistency
    DynamoDB may not immediately reflect changes, especially with concurrent requests. Using AppSync’s real-time updates ensures data synchronization across all instances.

  • Challenge 3: Lambda Function Limits
    AWS imposes limits on Lambda’s execution time and memory. Optimize the function code and adjust settings in the Amplify Console if needed.

Conclusion

In this project, I learned how to build and deploy a simple web application with user authentication and data storage on AWS using Amplify. The setup demonstrates a full-stack serverless application, ideal for rapid development and easy scaling. AWS Amplify offers an efficient way to manage web applications and their backend services, perfect for various use cases, from prototyping to production.

Explore my GitHub repository.

Asif Khan — Aspiring Cloud Architect | Weekly Cloud Learning Chronicler

LinkedIn/Twitter/GitHub

Top comments (0)