Complete Guide to Amplify and Next.js
Tags: amplify, nextjs, aws, react
Published: 2020-12-30 19:11:31

Hello Everyone, this is the complete blog post for our Amplify + Next.js video series. So in this series, we will be building a fullstack web application with Amplify and Next.js.

> Click [here](https://youtu.be/oihW1pms4A8) to see the video walkthrough.

## Table of Content
1. Introduction
2. Setting Up the Project
3. Cognito Authentication
4. AppSync API
5. S3 Storage

## Introduction

### What We Will Build?

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/w653tutv8mmcd9vpziyp.jpg">
  <figcaption>App Preview</figcaption>
</figure>

We will build a simple profile app. Through this app, you can understand how to use AWS Cognito Authentication, AWS AppSync Backend API, and Amazon Simple Storage Service (S3).

### Why Next.js?

[Next.js](https://nextjs.org/) is a React Static Site Generation Web Framework built by Vercel. Next.js introduced server-side React. Next.js has some cool features such as pre-rendering data-fetching methods, more on that later.

Also, with Next.js you don’t have to create a router by yourself. You can simply create a new file. The route will be automatically generated.

### Why Amplify?

Amplify is an AWS Framework that makes developing a full-stack application quite easy. The Amplify Framework provides the following services.

> **Amplify** [**CLI**](https://docs.amplify.aws/cli) — Configure all the services needed to power your backend through a simple command-line interface.

> **Amplify** [**Libraries**](https://docs.amplify.aws/lib/q/platform/js) — Use case-centric client libraries to integrate your app code with a backend using declarative interfaces.

> **Amplify** [**UI Components**](https://docs.amplify.aws/ui/q/framework/react) — UI libraries for React, React Native, Angular, Ionic, and Vue.

Amplify makes it easy to use AWS Services in a Full Stack App.

First, let’s look at the AWS Architecture Diagram for our Application.

### AWS Architecture Diagram

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/2c41h1lxdioix84kbdp9.jpg">
  <figcaption>AWS Architecture Diagram for our Profile App</figcaption>
</figure>

You will get the gist of these services as we go on with the project. Don’t worry Amplify makes it much easier to work with these different services.

We will use an S3 Bucket to store our profile image. Amazon Cognito will handle Authentication. We will use AWS AppSync to develop our GraphQL API. Our data will be stored in Amazon DynamoDB, NoSQL Database.

Ready to build the app? Let’s get started. 🛠️

In order to avoid any disturbances in the future. Make sure you have the following prerequisites installed.

* [Node.js](https://nodejs.org/) v10.x or later
* [npm](https://www.npmjs.com/) v5.x or later
* [Amplify CLI](https://docs.amplify.aws/cli/start/install) (v4.39.0 What I'm using in the Tutorial)

## Setting Up the Project

### Installing and Configuring Amplify CLI

Through this tutorial, we will work with AWS Amplify CLI. You can install it by running,

```
npm install -g @aws-amplify/cli@4.39.0
```

Then you need to run `amplify configure`. This will set up your Amplify CLI. There you will set up a new IAM User. You will finish setting up your IAM User, by providing the accessKeyId and secretAccessKey for your IAM user.

If you are stuck at some point, you can refer to this original guideline on installing Amplify CLI, [https://docs.amplify.aws/cli/start/install](https://docs.amplify.aws/cli/start/install "https://docs.amplify.aws/cli/start/install")


### Creating a New Next.js App

Hope you have installed and configured Amplify CLI successfully.

To start the proceedings, let’s start with setting up our Next.js Project. You will need to run these two commands from your project directory.

```
npm install -g create-next-app
npx create-next-app next-profileapp
```

This will install `create-next-app` npm package globally. The second command will create a new Next.js application in the directory `next-profileapp`. Navigate into that directory, and open our newly created Next.js project in your preferred code editor. You should see a project structure similar to this.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/xvtu0cpn3a4ztup2m883.jpg">
  <figcaption>Next.js Project Structure</figcaption>
</figure>


You can test your project by running the command `npm run dev` in your project directory. Your project will run at `http://localhost:3000`.

### Initializing Amplify Backend

Now, we need to initialize Amplify for our project. Then we can add services one by one.

In the project directory, run

```
amplify init
```

Then you will be prompted for the following information regarding the project you will initialize.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/4n6tt4flgwjkryrwqg9o.jpg">
  <figcaption>Running amplify init</figcaption>
</figure>

Just accept the default. You should be good to go. For the AWS Profile, you can choose your default AWS Account, the one we have configured earlier.

When you initialize your Amplify Project,

* It creates a file called `aws-exports.js` in the src directory. This file will store all the relevant information to identify the AWS resources/services that we are going to provision.
* It creates a directory called `amplify`. This directory will be used to store the templates and configuration details of the services that we are going to use. In this directory, Amplify will hold our backend schema as well.
* You can use the command, `amplify console` to access the cloud project’s AWS Amplify Console.

To complete setting up our Amplify Project, we need to configure amplify in a higher-order component. Adding the following lines of code in your App.js or inde.js file will do the job.

```
import "../styles/globals.css";
import awsExports from "../src/aws-exports";

Amplify.configure({...awsExports, ssr: true });
```

## Adding Authentication

Now, adding Authentication to your Next.js Application gets easier with Amplify. First, you need to include AWS Cognito Authentication Service to your Amplify Backend.

Run `amplify add auth`, in your console. Submit the following information, when Configuring Authentication using the prompt.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/jgeqbs8znyzskixhwba7.jpg">
  <figcaption>Running amplify add auth</figcaption>
</figure>


Then, run `amplify push`, to deploy your backend. Amplify will take care of the rest by creating your `Cognito Userpool`.

We can use `AmplifyAuthenticator` Component to add login, signup functionalities in our Next.js Project. First, let’s install Amplify UI Components using npm.

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

Navigate to your `pages/_app.js` file, configure Amplify, and wrap your return Component with `AmplifyAuthenticator` like this.

**Directory: pages/_app.js**

```
import { Amplify } from "aws-amplify";
import { AmplifyAuthenticator } from "@aws-amplify/ui-react";
import awsExports from "../src/aws-exports";
import "../styles/globals.css";
Amplify.configure({ ...awsExports, ssr: true });
function MyApp({ Component, pageProps }) {
  return (
    <AmplifyAuthenticator>
      <Component {...pageProps} />
    </AmplifyAuthenticator>
  );
}
export default MyApp;
```

When you run your app. This login screen will show up. Try logging in as a new user. This will lead you to the home page. The user that we have created, will be saved in a Cognito User Pool.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/c1pf4nkh13722fddsn0j.jpg">
  <figcaption>Sign-in Screen by Amplify</figcaption>
</figure>


One issue though. Now we can’t log out. Let’s add a new ‘navigation component’, where we can add a ‘sign out button’.

### Adding a Navigation Bar

Before that, let’s add Bootstrap. Since I want to use easy styling, I will be using Bootstrap throughout the tutorial.

Run,

```
npm install react-bootstrap bootstrap
```

Also, add this import in your `pages/_app.js` file.

```
import 'bootstrap/dist/css/bootstrap.min.css';
```

Add a component directory. In that directory, add a new file called `Navbar.js`. Copy and paste the following code.

_**directory: components/Navbar.js**_

```
import Link from "next/link";
import { Auth } from "aws-amplify";
import React from "react";
const Navbar = () => {
  const signOutHandler = () => {};
  return (
    <nav className="navbar w-100 navbar-expand navbar-dark bg-dark mb-4">
      <div className="container">
        <a className="navbar-brand" href="#">
          Profile App
        </a>
        <div className="collapse navbar-collapse">
          <ul className="navbar-nav ml-auto">
            <li className="nav-item">
              <Link href="/">
                <a className="nav-link">Home</a>
              </Link>
            </li>
            <li className="nav-item">
              <Link href="/edit-user">
                <a className="nav-link">Edit User</a>
              </Link>
            </li>
            <button
              className="btn btn-danger"
              type="button"
              onClick={signOutHandler}
            >
              Sign Out
            </button>
          </ul>
        </div>
      </div>
    </nav>
  );
};
export default Navbar;
```

Please notice that I have used a Sign-out Button in the Navbar. That button should trigger `Auth.signOut` function provided by Amplify Library. This method will end the user session. Since we are using `AmplifyAuthenticator` wrapper component, logged-out users will get automatically redirected to Sign-in Screen.

Copy and paste the following code into `signOutHandler` method.

```
const signOutHandler = async () => {
    try {
        await Auth.signOut();
    } catch (err) {
      console.log(err);
    }
};
```

With that, our Navbar is fully completed. Let’s use that Navigation bar on our home page.

_**directory: pages/index.js**_

```
import React from "react";
import Head from "next/head";
import Navbar from "../components/Navbar";

export default function Home() {
  return (
    <div className="w-100 h-100 d-flex flex-column justify-content-start">
      <Head>
        <title>Profile App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Navbar />
    </div>
  );
}
```

I did clear the default page by `Next.js`. Try signing out and logging back in. 

Now, we have successfully added Authentication into our Next.js Application. Congratulations on completing the first part of the tutorial! 

---

Now into the next part, we will complete our project by adding our AppSync API, and an S3 Bucket.

## Adding AppSync API

Let’s store some details on the user by adding an AppSync API to our Next.js application. Users can add first name, last name, and some description. The user should be able to add a profile image as well. We will add the profile image functionality in the next part.

As I’ve said earlier, through [AppSync](https://aws.amazon.com/appsync/), we can build a [GraphQL API](https://graphql.org/). All the heavy lifting such as connecting and creating DynamoDB Tables, generating queries and mutations, will be done by AppSync.

### Executing 'amplify add api'

Using AppSync gets easier with Amplify. First, let’s add the AppSync API to our app.

Run,

```
amplify add api
```

Accept the default configurations.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/hnstlwzqw8dricxr9qg3.jpg">
  <figcaption>Adding AppSync API</figcaption>
</figure>

### Editing GraphQL Schema

Navigate into `amplify/backend/api/schema.graphql` file. This is where we will define our GraphQL schema. Let’s add a simple user schema. Copy and paste the following schema.

    type User @model {
      id: ID!
      firstName: String
      lastName: String
      description: String
      image: String
    }

Save the _**schema.graphql**_ file. Run `amplify push` to push your changes into Amplify Backend.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/6ewk8c1c8sy2926x3zvq.jpg">
  <figcaption>Running amplify push after adding AppSync API</figcaption>
</figure>

Now, our AppSync API has been created. Also, the AppSync Library automatically created queries, mutations for our GraphQL Schema. Run `amplify api console` to view your AppSync API in AWS.

You could play around with some GraphQL operations in this AWS AppSync Console.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/k7yc66nfr6xwyf0kdnkk.jpg">
  <figcaption>AWS AppSync Console</figcaption>
</figure>

### Adding Edit User Page

Let’s start interacting with our AppSync API.

First, create a new page to edit user details. Create a new file called`edit-user.js` in the pages directory. Copy and paste the following code.

**Directory: pages/edit-user.js**

```
import React, { useState } from "react";
import { Form } from "react-bootstrap";
import { createUser, updateUser } from "../src/graphql/mutations";
import { API } from "@aws-amplify/api";
import { Auth } from  "@aws-amplify/auth";
import Navbar from "../components/Navbar";

const EditUser = () => {
    const [firstName, setFirstName] = useState('');
    const [secondName, setSecondName] = useState('');
    const [description, setDescription] = useState('');
    const submitHandler = async (event) => {
        event.preventDefault();
        // Save Details
    };
    return (
        <div className="d-flex flex-column justify-content-center w-100 h-100">
            <Navbar />
            <h1 className="align-self-center">Edit User Details</h1>
            <Form className="w-50 align-self-center">
                <Form.Group className="mt-2" controlId="firstName">
                    <Form.Label>First Name</Form.Label>
                    <Form.Control
                        type="text"
                        value={firstName}
                        placeholder="Enter Your First Name"
                        onChange={(event) => {
                            setFirstName(event.target.value);
                        }}
                    />
                </Form.Group>
                <Form.Group className="mt-2" controlId="secondName">
                    <Form.Label>Second Name</Form.Label>
                    <Form.Control
                        type="text"
                        value={secondName}
                        placeholder="Enter Your Second Name"
                        onChange={(event) => {
                            setSecondName(event.target.value);
                        }}
                    />
                </Form.Group>
                <Form.Group className="mt-2" controlId="description">
                    <Form.Label>Description</Form.Label>
                    <Form.Control
                        as="textarea"
                        value={description}
                        rows={5}
                        placeholder="Enter Your Description"
                        onChange={(event) => {
                            setDescription(event.target.value);
                        }}
                    />
                </Form.Group>
                <button
                    type="submit"
                    onClick={submitHandler}
                    className="btn btn-primary"
                >
                    Submit
                </button>
            </Form>
        </div>
    );
};
export default EditUser;
```

This is a simple bootstrap form along with some state variables. Now you can navigate to ‘Edit User Page’ using the navbar. It should look something like this.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/1p8j3zxqn8is7nodwn0z.jpg">
  <figcaption>Uploaded Profile Image Preview</figcaption>
</figure>

Now at the press of this submit button we need to save entered details. In order to do that, we need to call CreateUser GraphQL mutation.

### Adding 'submitHandler' Function

Copy and paste the following code into `submitHandler` function.

```
const submitHandler = async (event) => {    
    event.preventDefault();
    const currentUser = await Auth.currentAuthenticatedUser();
    try {
        const result = await API.graphql({
            query: createUser,
            variables: {
                input: {
                    id: currentUser.attributes.sub,
                    firstName: firstName,
                    lastName: secondName,
                    description: description,
                },
            },
        });
        console.log(result);
    } catch (err) {
        console.log(err);
    }
};
```

Now, you can test this. Fill in the inputs and press enter. You could try querying from our AppSync Console.

`Auth.currentAuthenticatedUser()` will do exactly what the name suggests. It will return details about the logged-in user. Cognito gives every user an attribute called `sub`, a unique string value. We can use this value as the unique id in our User Table.

Then I have called AppSync GraphQL API in order to perform ‘createUser’ mutation. This mutation will create a new record in our User Table.

### Fetching Data in Pre-rendering Stage

Now let’s examine a user scenario here. If I’m a new user I should be able to add my details to this App. If I have already added the details, I should be able to edit my information as well. Okay, let’s build this.

I will use the `getServerSideProps` method provided by Next.js. Through this method, we can fetch data on each request. Learn more about Next.js data fetching [here](https://nextjs.org/docs/basic-features/data-fetching).

Copy and paste the following code into `getServerSideProps` method. Make sure to add the imports beforehand.

```
import { getUser } from "../src/graphql/queries";
import {withSSRContext} from "aws-amplify";

export async function getServerSideProps({ req, res }) {
  const { Auth, API } = withSSRContext({ req });
  try {
    const user = await Auth.currentAuthenticatedUser();
    const response = await API.graphql({
      query: getUser,
      variables: { id: user.attributes.sub },
    });
    
  if (response.data.getUser) {
      return {
        props: {
          mode: "EDIT",
          user: response.data.getUser,
          error: false,
        },
      };
    } else {
      return {
        props: {
          mode: "ADD",
          error: false,
        },
      };
    }
  } catch (err) {
    console.log(err);
    return {
      props: {
        error: true,
      },
    };
  }
}
```
This getStaticProps method will return an object with 3 fields.

01\. mode — _**String**_ If the user is new, the mode is ‘ADD’, otherwise it is ‘EDIT.

02\. user — _**JSON Object**_ Already added user details to preview initial values to the user, before editing.

03\. error — _**Boolean**_ If something goes wrong, ‘true’. We will use this boolean to display something readable to the user.

If you look closely, you should see that I’m querying user details in this method.

```
const user = await Auth.currentAuthenticatedUser();
const response = await API.graphql({
  query: getUser,
  variables: { id: user.attributes.sub },
});
```

Then I have called AppSync GraphQL API with ‘getUser’ query. ‘getUser’ query will search through the DynamoDB to find a record with the given id. If there’s none response will return a null object.

Now let’s use those pre-rendered data in our EditPage. We can set initial values and call ‘updateUser’ or ‘createUser’ mutation by considering our Mode.

The final version of EditUser Page is like this. I have updated the `submitHandler` function, and defined initial values for our state variables.

**Directory: pages/edit-user.js**

```
import React, {useState} from "react";
import {Form} from "react-bootstrap";
import {getUser} from "../src/graphql/queries";
import {createUser, updateUser} from "../src/graphql/mutations";
import {withSSRContext} from "aws-amplify";
import {API} from "@aws-amplify/api";
import {Auth} from "@aws-amplify/auth";
import Navbar from "../components/Navbar";


export async function getServerSideProps({req, res}) {
    const {Auth, API} = withSSRContext({req});
    try {
        const user = await Auth.currentAuthenticatedUser();
        const response = await API.graphql({
            query: getUser,
            variables: {id: user.attributes.sub},
        });
        if (response.data.getUser) {
            return {
                props: {
                    mode: "EDIT",
                    user: response.data.getUser,
                    error: false,
                },
            };
        } else {
            return {
                props: {
                    mode: "ADD",
                    error: false,
                },
            };
        }
    } catch (err) {
        console.log(err);
        return {
            props: {
                error: true,
            },
        };
    }
}

const EditUser = ({user, error, mode}) => {
    const [firstName, setFirstName] = useState(mode === 'EDIT' ? user.firstName : '');
    const [secondName, setSecondName] = useState(mode === 'EDIT' ? user.lastName : '');
    const [description, setDescription] = useState(mode === 'EDIT' ? user.description : '');
    const submitHandler = async (event) => {
        event.preventDefault();
        const currentUser = await Auth.currentAuthenticatedUser();
        try {
            const result = await API.graphql({
                query: mode === 'EDIT' ? updateUser : createUser,
                variables: {
                    input: {
                        id: currentUser.attributes.sub,
                        firstName: firstName,
                        lastName: secondName,
                        description: description,
                    },
                },
            });
            console.log(result);
            window.location.href = "/";

        } catch (err) {
            console.log(err);
        }
    };

    if (error) {
        return (
            <div>
                <Navbar />
                <h1>Something Went Wrong! Please Try Again Later.</h1>
            </div>
        );
    }
    return (
        <div className="d-flex flex-column justify-content-center w-100 h-100">
            <Navbar/>
            <h1 className="align-self-center">Edit User Details</h1>
            <Form className="w-50 align-self-center">
                <Form.Group className="mt-2" controlId="firstName">
                    <Form.Label>First Name</Form.Label>
                    <Form.Control
                        type="text"
                        value={firstName}
                        placeholder="Enter Your First Name"
                        onChange={(event) => {
                            setFirstName(event.target.value);
                        }}
                    />
                </Form.Group>
                <Form.Group className="mt-2" controlId="secondName">
                    <Form.Label>Second Name</Form.Label>
                    <Form.Control
                        type="text"
                        value={secondName}
                        placeholder="Enter Your Second Name"
                        onChange={(event) => {
                            setSecondName(event.target.value);
                        }}
                    />
                </Form.Group>
                <Form.Group className="mt-2" controlId="description">
                    <Form.Label>Description</Form.Label>
                    <Form.Control
                        as="textarea"
                        value={description}
                        rows={5}
                        placeholder="Enter Your Description"
                        onChange={(event) => {
                            setDescription(event.target.value);
                        }}
                    />
                </Form.Group>
                <button
                    type="submit"
                    onClick={submitHandler}
                    className="btn btn-primary"
                >
                    Submit
                </button>
            </Form>
        </div>
    );
};
export default EditUser;
```

It’s just some simple logic. If the user is in edit mode, we will have to trigger, GraphQL mutation ‘updateUser’. Otherwise, we will have to trigger, the GraphQL mutation ‘createUser’. Try submitting your details and come back to Edit Page again. You should see your values.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/prxn2cs4du05gwq4l5x9.jpg">
  <figcaption>EditUser Page Showing Previously Entered Details</figcaption>
</figure>

Mine works, hope yours too.😃

Now, by far we build a pretty cool application. How about letting users choose a profile image? We will need an Amazon S3 Bucket for that.

---

## Adding an S3 Storage

You are into the third and final part of this tutorial.

Through our app, the users can edit their details. Now, I want to add a profile image as well. We will need an S3 Bucket for that. Working with S3 really gets easier with Amplify. Let’s start.

### Executing 'amplify add storage'

Run,

```
amplify add storage
```

to create a new S3 Bucket. Accept the defaults in the prompt.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/0wzliljy242qdtv0ztvs.jpg">
  <figcaption>Running amplify add storage</figcaption>
</figure>

Run `amplify push` to deploy your changes.

### Updating the Form

Let’s add image uploading and previewing options into our form. I build a fancy ImageUploader component with an image preview. Make sure to add that under the components directory.

**Directory: components/ImageUploader.js**

```
import React from "react";

const ImageUploader = ({imageUploadHandler, image}) => {
    return (
        <div>
            <input className="btn-primary" type="file" onChange={imageUploadHandler}/>
            <div className="image-container">
                {image && <img className="uploaded-image" alt='Uploaded Image' src={URL.createObjectURL(image)}/>}
            </div>
        </div>
);
}

export default ImageUploader;
```

Add these CSS styles into your `styles/global.css` file.

```
amplify-s3-image {
  --height: 300px;
  --width: 300px;
}
.image-container {
  box-shadow: -7px 20px 41px 0 rgba(0,0,0,0.41);
  width: 300px;
  height: 300px;
  max-width: 400px;
  max-height: 400px;
  position: relative;
  display: inline-block;
  overflow: hidden;
  margin: 0;
}
.uploaded-image {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  height: 100%;
  width: 100%;
  transform: translate(-50%, -50%);
}
```

We will use AmplifyS3Image Component to preview already uploaded profile images. In order to use these two components, we will make these changes in our EditUser.js file.

We will add these two components inside our form.

```
<Form className="w-50 align-self-center">

  {editImage && (
    <ImageUploader
      imageUploadHandler={imageUploadHandler}
      image={userImage}
    />
  )}
  {!editImage && (
    <div>
      <button
        type="button"
        className="btn m-2 btn-outline-primary"
        onClick={() => {
          setEditImage(true);
        }}
      >
        Edit Image
      </button>
      <AmplifyS3Image imgKey={user.image} />
    </div>
  )}
```

`AmplifyS3Image` component is perfect for displaying S3 images. If you provide the relevant image key (S3 File Name), it displays your S3 Image. Declare these two state variables along with the new `imageUploadHandler` method.

```
const [editImage, setEditImage] = useState(!user.image);
const [userImage, setUserImage] = useState(null);

const imageUploadHandler = (event) => {
  setUserImage(event.target.files[0]);
};
```

Don’t forget to add these imports as well.

```
import { Storage } from "@aws-amplify/storage";
import { AmplifyS3Image } from "@aws-amplify/ui-react";
import { v4 as uuid } from "uuid";
import ImageUploader from "../components/ImageUploader";
```

You should see something like this.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/st7hb83sjjrz6vnck6eg.jpg">
  <figcaption>Form with ImageUploader</figcaption>
</figure>


### Updating the 'submitHandler' Function

Now let’s update the `submitHandler` method. Replace the current code with the following.

```
const submitHandler = async (event) => {
    event.preventDefault();
    const currentUser = await Auth.currentAuthenticatedUser();
    try {
        let key = null;
        if(userImage) {
            key = `${uuid()}${user.firstName}`;

            if(user.image) {
                await Storage.remove(user.image);
            }
            await Storage.put(key, userImage, {
                contentType: userImage.type,
            });
        }
        const result = await API.graphql({
            query: mode === 'EDIT' ? updateUser : createUser,
            variables: {
                input: {
                    id: currentUser.attributes.sub,
                    image: userImage ? key : user.image,
                    firstName: firstName,
                    lastName: secondName,
                    description: description,
                },
            },
        });
        console.log(result);
        window.location.href = "/";

    } catch (err) {
        console.log(err);
    }
};
```

We can upload an S3 Image into our bucket using `Storage.put` method, provided by AWS Amplify Library. As I’ve mentioned earlier, we need our file name (image key in S3 ) to access our file again. So we will store that in our database.

If the user is replacing the profile image. We need to remove the existing one. We can remove an S3 file by using `Storage.remove` method.

Try uploading a new image. Submit the form. Wait until the image uploads. When you navigate back to EditUser Page, you should see your profile image like this.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/tp8j8m1a96u7zbnjamii.jpg">
  <figcaption>Uploaded Profile Image Preview</figcaption>
</figure>

Congratulations on completing the tutorial! :tada:

I think now, you have a good understanding of how to work with Amplify in your Next.js Application. After all, we did work with AppSync API, Cognito Authentication, and AWS S3.

I hope you have completed all the steps without running into any issues. However, if you do, you can ask me from the comments section below.

Video Walkthrough related to this BlogPost:

**Part 1: Setting Up the Project and Adding Authentication**

{% youtube oihW1pms4A8 %}

**Part 2: Adding AppSync API and S3 Storage**

{% youtube hbx1AlbiuzE %}