---
# System prepended metadata

title: Implementing a feedback form on a Next.js site
tags: [Completed]

---

# Implementing a feedback form on a Next.js site

## Introduction

In this article, we will provide a step-by-step guide to creating a form with the ability to collect feedback from users of your applications and also execute the API functions for us to receive them in a NextJs project. Consider the following scenarios as to why this is important:

1.  You are a developer building your portfolio. You want to add a Contact Me section where visitors could enter some messages and submit them. And you would receive these messages in real-time in your email.
2. You have just built an amazing product and need some users to test it out and give you feedback on how to improve your application.
3. Lastly, You already have an established website and you need to add a feature where users can lay complaints about bugs and irregularities. These messages are then received by you (the owner) or an admin of the site and acted on accordingly.

These are only a few of the many scenarios where this is necessary to have. It is also worth noting that you don’t need to have some back-end knowledge of form collection or database management to be able to have this feature.
We would make use of an email client which collects the form data and deliver them as messages in your emails.


## Pre-requisite

To be able to follow this tutorial, it is advised to know/have the following:

- HTML
- CSS
- JavaScript
- Basic knowledge of React
- npm and Nodejs installed on your hardware


## Installation And Overview

### Installation
First, we have to initialize a Next.js project. Enter the following commands in your terminal

```bash
npx create-next-app@latest feedback-form
```
You would be prompt with an interactive CLI. Since this is a simple project, toggle `No` on all the options and press `Enter`.

![](https://i.imgur.com/HjUioSV.png)



On completion, You should have a new folder feedback-form with all Next.js starter files and folders created.
Next, enter the command

```bash
cd feedback-form
```

### Overview of the Folder Structure
Open the `feedback-form` folder in your code editor. It should have a similar folder structure as the one below.
    
![](https://paper-attachments.dropboxusercontent.com/s_C9AA4DB899BA19ADED657DDC78905D1FAAE0B1C6D28D8E3A419651CF65AA895A_1674193841903_image.png)

a. **node_modules**: Contains all dependency packages.
b. **Pages**: This is where all the React components and API functions live.
c. **Pages/api**: Nextjs being a `FullStack` framework is made possible by this folder. Files created in this folder represent back-end APIs and functions. Files in this folder by default have a server configured with `Nodejs` which makes it possible to write backend code here.
d. **Public**: As the name implies, files in this folder are accessible in the root folder of your deployed application. For example, `<your-site-name.com/favicon.ico`  displays the favicon in the browser.
e. **Styles**: This folder contains all CSS styles. `.css` as well as `CSS Modules` files.
The other files at the bottom represent general configurations.

### Creating The Form Component

```javascript
// Replace the code in Pages/index.js with this code

import Head from "next/head";

export default function Home() {
  return (
    <>
      <Head>
        <title>Create Feedback Form</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="form-wrapper">
        <form action="/">
          <div className="form-inner">
            <h1>Send Me A Message</h1>
            <input type="text" placeholder="First Name" />
            <input type="text" placeholder="Last Name"  />
            <input type="email" placeholder="Email" />
            <textarea placeholder="Message..." rows="10" ></textarea>
            <button type="submit" href="/">
              Submit
            </button>
          </div>
        </form>
      </div>
    </>
  );
}
```
**Code Breakdown**
First, we import the `HEAD` component which we use to add custom content to the `<head/>` of the generated HTML.
Then we return a simple HTML form with classes to style it.


```css
/* Replace the code in styles/globals.css with this code below */

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html,
body {
  font-size: 14px;
  color: rgb(70, 69, 69);
}
input,
textarea {
  outline: none;
}
input:is(:focus, :focus-within),
textarea:is(:focus, :focus-within) {
  outline: 1px solid #22d3ee;
}
.form-wrapper {
  height: 100vh;
  padding-inline: 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: #22d3ee;
}
h1 {
  font-weight: 500;
  margin-bottom: 10px;
  text-align: center;
}
form {
  width: 100%;
  max-width: 600px;
  border-radius: 30px;
  background: #fff;
  margin: 0 auto;
}
.form-inner {
  padding-inline: 40px;
  padding-block: 30px 40px;
}
.form-inner input,
.form-inner textarea {
  display: block;
  width: 100%;
  padding: 15px;
  margin-bottom: 10px;
  border: none;
  border-radius: 20px;
  background: #d0dfe8;
}
.form-inner textarea {
  resize: none;
}
button {
  width: 100%;
  padding: 10px;
  margin-top: 20px;
  border-radius: 20px;
  border: none;
  border-bottom: 4px solid #0e7490;
  background: #0891b2;
  font-size: 16px;
  font-weight: 400;
  color: #fff;
}
button:hover {
  background: #0e7490;
  cursor:pointer;
}
```
    

We added the following styles to make our form look a bit better.
To see what the form currently looks like, run the command below in your terminal

```bash
npm run dev
```

Open the link in a browser (usually it is http://localhost:3000/) and you should see a simple form looking similar to the one below.

![](https://paper-attachments.dropboxusercontent.com/s_C9AA4DB899BA19ADED657DDC78905D1FAAE0B1C6D28D8E3A419651CF65AA895A_1674297662820_image.png)


For this article, I have the fields `Name`, `Email`, `Subject` and `Message` which suggest a contact form but depending on the type of information you wish to collect, the form could have different types of inputs. 

## Values Collection and Submission


### Collecting the form values

```javascript
/* Replace the code in Pages/index.js with this code */

import Head from "next/head";
import { useRef } from "react";

export default function Home() {
  const usernameRef = useRef("");
  const subjectRef = useRef("");
  const emailRef = useRef("");
  const messageRef = useRef("");

  async function submitForm(e) {
    e.preventDefault();
    // Getting the values from their useRef hooks
    let username, subject, email, message;
    username = usernameRef.current.value;
    subject = subjectRef.current.value;
    email = emailRef.current.value;
    message = messageRef.current.value;
    //Some form Validation
    if (!username || !subject || !email || !message) {
      alert("Failed: Ensure to fill all form inputs");
      return;
    }
    // Clear the form inputs after submit
    usernameRef.current.value =
      subjectRef.current.value =
      emailRef.current.value =
      messageRef.current.value =
        "";

    // TODO: Send the form  values to an api route

  }
  return (
    <>
      <Head>
        <title>Create Feedback Form</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="form-wrapper">
        <form action="/" onSubmit={submitForm}>
          <div className="form-inner">
            <h1>Send Me A Message</h1>
            <input type="text" placeholder="Name" required ref={usernameRef} />
            <input type="email" placeholder="Email" required ref={emailRef} />
            <input
              type="text"
              placeholder="Subject"
              required
              ref={subjectRef}
            />
            <textarea
              placeholder="Message..."
              rows="10"
              required
              ref={messageRef}
            ></textarea>
            <button type="submit" href="/">
              Submit
            </button>
          </div>
        </form>
      </div>
    </>
  );
}
```

**Code Breakdown**
First, we use the [useRef()](https://beta.reactjs.org/reference/react/useRef) react hook to save the input values. Unlike the common [useState()](https://beta.reactjs.org/reference/react/useState) hook, this does not trigger the re-rendering of the component and hence tends to be faster for our use case. 
To learn more about it, see this [useRef article](https://blog.openreplay.com/understanding-the-useref-hook/).
We also added some validation to check that all the values are entered before sending the form to the server route. 
A more specific validation could be done in your application depending on the values you aim to collect from the user. Also, all the form input fields have also been given the required attribute but that could be different in your use case

### Sending the form values to an API route
```javascript
// Update pages/index.js
/*
The previous code remains unchanged
*/

// TODO: Send form values to an api route
const formValues = { username, subject, email, message };
let result;
try {
  let data = await fetch("/api/contact", {
    method: "POST",
    body: JSON.stringify(formValues),
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
  });
  result = await data.json();
} catch (error) {
  result = { message: `Failed: ${error.message}` };
}
alert(result.message); // Gives the user some sort of feedback after the form has been processed

/* 
The rest of the code remains unchanged
*/
```
**Note**: Refer to the previous step to see where to add the code

**Code Breakdown**
First, we put the values in an object and use the fetch browser API to send the data to the `/api/contact` route. 
This is the route we would create in the next step.
Also, we check for errors and alert the user a message based on the result of their submission (success or failure).

## Implementing Functions to Receive The Form Values As Email


### Creating the API route
Create a new file `contact.js` in the  `/pages/api`  folder and insert the code below
```javascript
// pages/api/contact.js

export default function handler(req, res) {
  if (req.method === "POST") {
    const formValues = req.body;
    try {
      // Function to validate email and send form values goes here
      return res.status(200).json({ message: "Success: Message Sent" });
    } catch (error) {
      return res
        .status(400)
        .json({
          message: "Failed: Something went wrong. Please try again later",
        });
    }
  }
}
```
    

**Code Breakdown**
First, we check that the correct method is used to access the route.
Then, we get the `formValues` object from the request body.
Then, we use the `try/catch` block which returns success or failure depending on result. 
In the `try`  block, we would execute the transport functions.


### Validating and Transporting the message to your Email

To carry out this step, we need to involve an email delivery service in our application. 
There are a few of them out there. [Postmark](https://postmarkapp.com/), [Sendgrid](https://sendgrid.com/), [Nodemailer](https://nodemailer.com/about/) to name a few.
For this tutorial, we would make use of Nodemailer. 
**Nodemailer** is a package that helps for easy sending and receiving of mails in Nodejs applications. To learn more about this module see the [official documentation](https://nodemailer.com/usage/).
Nodemailer works well with Gmail so **ensure to create a Gmail account** to follow this part fully. 

First, we install the `nodemailer` package. Run the command below in the root folder of your project
        
```bash
npm install nodemailer
```

Next, we need to update the API route 
```javascript  
// Replace the code in pages/api/contact.js

import nodemailer from "nodemailer";

// Replace the email and password values with a string containing your email and password
const email = '<your-gmail-goes-here>'
const password = '<your-gmail-password-goes-here>'

const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: email,
    pass: password,
  },
});
export default async function handler(req, res) {
  if (req.method === "POST") {
    const formValues = req.body;
    
    // Server-side form validation
    if (
      !formValues.username ||
      !formValues.email ||
      !formValues.subject ||
      !formValues.message
    )
      return res
        .status(400)
        .json({ message: "Failed: Missing Required Values" });
    try {
      // Validate email and send form values to the validated email
      await transporter.sendMail({
        from: email,
        to: email,
        text: `
        Name: ${formValues.username}
        Email: ${formValues.email}
        Message: ${formValues.message}`,
        subject: formValues.subject,
      });
      return res
        .status(200)
        .json({ message: "Success: Message sent" });
    } catch (error) {
      return res.status(400).json({
        message: `Failed: ${error.message}`,
      });
    }
  }
}
```

**Note**: Ensure to replace the `email` and `password` values with your own

**Code Breakdown**
First, we import the nodemailer package, and initialize the `email` and `password`. 
Then, we create a `transport` (as per the nodemailer documentation) to authenticate our email.
Then, in the try block, we call a `sendMail` method for the `transport` and pass in the fields needed for it to work.

Now enter fill the input fields and click `Submit`

![](https://i.imgur.com/7o03TN6.png)

If you tried running the code (with your Gmail account and password), you will notice it doesn’t work yet instead it shows the error message in the image above. 
This is because Gmail has strict security protocol and there are still a few more steps to make it work
1. **Add 2-step verification to your account**: Gmail accounts recently have this active but If yours does not, Follow the [2-step verification](https://support.google.com/accounts/answer/185839?hl=en&co=GENIE.Platform%3DAndroid) documentation to activate it.
2. **Create application password**: This is a security procedure where google generates a password for you and can be used for only a specific application. This is to keep your account safe if the password gets compromised.
Go to the[ Application Specific](https://security.google.com/settings/security/apppasswords) page to create a new password with the name `Nodemailer`.


    See the screencast below for an example

![](https://i.imgur.com/oIZQnk0.png)

![](https://i.imgur.com/yZpF208.png)


Congratulations!!!. Submitting the form should now show up as a message in your email. 

![](https://i.imgur.com/udX6fSa.png)
*After submitting the values now*

It is **IMPORTANT** to note that putting sensitive data like passwords, private keys, etc in code is highly discouraged for security reasons. We use environment variables to save such values since they would only be evaluated at runtime.
Follow the steps below to save your email and password as environment variables:
1. Create a file called `.env.local` in the root of your `feedback-form` folder
    
![.env.local file added](https://paper-attachments.dropboxusercontent.com/s_C9AA4DB899BA19ADED657DDC78905D1FAAE0B1C6D28D8E3A419651CF65AA895A_1674254938209_image.png)

2. Move your email and password to `.env.local`

```javascript
// .env.local   
EMAIL=<your-gmail-goes-here>
EMAIL_PASSWORD=<your-nodemailer-generated-password-goes-here>
```
![](https://i.imgur.com/22DQiYN.png)
            

3. Update the API route 
```javascript=            
// Update the code in pages/api/contact.js
import nodemailer from "nodemailer";

/* Replace the raw email and password with the process environment values*/
const email = process.env.EMAIL 
const password = process.env.EMAIL_PASSWORD
/* 
Rest of the code remains unchanged
*/
```
The code should still work the same but as a security procedure, **never put sensitive information in code**.
The complete code could be found on [GitHub](https://github.com/Complexlity/feedback-form).
###  Additional Information
The `sendMail` function takes an `html` field in its argument. This could be used to format the message received in a better way than how it is currently. 
It also takes an `attachments` field to send and receive files. 
See the [nodemailer documentation](https://nodemailer.com/message/) for the complete list of fields.

## Deploying The Application

This application could be deployed on both Netlify and Vercel.
Follow the article on [deploying Next.js to production](https://blog.openreplay.com/how-to-deploy-a-nextjs-app-to-production/) **step-by-step** to host your application on either of the platforms.
The article shows deploying from the CLI but you could also deploy on the [Netlify GUI](https://www.netlify.com/) and  [Vercel GUI](https://vercel.com/)

**IMPORTANT**: Ensure to add the `EMAIL` and `EMAIL_PASSWORD` as environment variables because the one created before only works in local development.


## Conclusion

In this article, we learnt how to receive feedback from users of our NextJs site. And how straightforward it is using the nodemailer module.
