DEV Community

Cover image for Let's build a contact form with Next.js
Dexter for Superface

Posted on • Originally published at superface.ai

Let's build a contact form with Next.js

Most of the websites have a contact page where you can send a message to reach the owner. They look something like this:

Contact form on superface.ai includes email, first and last name, company, message, and selection of demo.

In this article, we will create a similar form with React in Next.js. First, I will create a front-end part with the form, and then I will build an API route which will send the form to your email.

Setting up the application

First, let’s create a new Next.js project. We will create it in a contact-form folder, with JavaScript and ESLint enabled:

npx create-next-app contact-form --js --eslint
Enter fullscreen mode Exit fullscreen mode

This will create the folder and installs all the dependencies.

Now enter the folder (cd contact-form) and start the development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:3000 to check the running application.

Creating the form

The main file where we are going to make changes is pages/index.js. Remove the original code inside the file and paste the following code:

import React from 'react';

export default function Home() {
  return (
    <form className="container">
      <h1>Get in touch</h1>
      <div className="email block">
        <label htmlFor="frm-email">Email</label>
        <input
          id="frm-email"
          type="email"
          name="email"
          autoComplete="email"
          required
        />
      </div>
      <div className="block phone">
        <label htmlFor="frm-phone">Phone</label>
        <input
          id="frm-phone"
          type="text"
          name="phone"
          autoComplete="tel"
          required
        />
      </div>
      <div className="name block">
        <div>
          <label htmlFor="frm-first">First Name</label>
          <input
            id="frm-first"
            type="text"
            name="first"
            autoComplete="given-name"
            required
          />
        </div>
        <div>
          <label htmlFor="frm-last">Last Name</label>
          <input
            id="frm-last"
            type="text"
            name="last"
            autoComplete="family-name"
            required
          />
        </div>
      </div>
      <div className="message block">
        <label htmlFor="frm-message">Message</label>
        <textarea id="frm-message" rows="6" name="message"></textarea>
      </div>
      <div className="button block">
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

This code creates a form with the following fields:

  • email
  • first name
  • last name
  • phone number
  • message

All fields are required except for the message.

To add style to the form, replace the contents of styles/globals.css file with the following code:

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  background: #1e1e1e;
  min-height: 100vh;
  display: flex;
  color: rgb(243, 241, 239);
  justify-content: center;
  align-items: center;
}

.block {
  display: flex;
  flex-direction: column;
}

.name {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.container {
  font-size: 1.3rem;
  border-radius: 10px;
  width: 85%;
  padding: 50px;
  box-shadow: 0 54px 55px rgb(78 78 78 / 25%), 0 -12px 30px rgb(78 78 78 / 25%),
    0 4px 6px rgb(78 78 78 / 25%), 0 12px 13px rgb(78 78 78 / 25%),
    0 -3px 5px rgb(78 78 78 / 25%);
}

.container input {
  font-size: 1.2rem;
  margin: 10px 0 10px 0px;
  border-color: rgb(31, 28, 28);
  padding: 10px;
  border-radius: 5px;
  background-color: #e8f0fe;
}

.container textarea {
  margin: 10px 0 10px 0px;
  padding: 5px;
  border-color: rgb(31, 28, 28);
  border-radius: 5px;
  background-color: #e8f0fe;
  font-size: 20px;
}

.container h1 {
  text-align: center;
  font-weight: 600;
}

.name div {
  display: flex;
  flex-direction: column;
}

.block button {
  padding: 10px;
  font-size: 20px;
  width: 30%;
  border: 3px solid black;
  border-radius: 5px;
}

.button {
  display: flex;
  align-items: center;
}

textarea {
  resize: none;
}
Enter fullscreen mode Exit fullscreen mode

Our form should look something like this:

Styled form with Email, Phone, First name, Last name, and Message

Now we need to find a way to store the input entered by the user. I will use FormData which is natively supported in all current browsers. It loads fields from the form, so they can be then submitted to the server.

Inside the pages/index.js file, paste the following code (notice the new handleSubmit function):

import React from 'react';

export default function Home() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    console.log(data);
  }

  return (
    <form className="container" onSubmit={handleSubmit}>
      <h1>Get in touch</h1>
      <div className="email block">
        <label htmlFor="frm-email">Email</label>
        <input
          id="frm-email"
          type="email"
          name="email"
          autoComplete="email"
          required
        />
      </div>
      <div className="block phone">
        <label htmlFor="frm-phone">Phone</label>
        <input
          id="frm-phone"
          type="text"
          name="phone"
          autoComplete="tel"
          required
        />
      </div>
      <div className="name block">
        <div>
          <label htmlFor="frm-first">First Name</label>
          <input
            id="frm-first"
            type="text"
            name="first"
            autoComplete="given-name"
            required
          />
        </div>
        <div>
          <label htmlFor="frm-last">Last Name</label>
          <input
            id="frm-last"
            type="text"
            name="last"
            autoComplete="family-name"
            required
          />
        </div>
      </div>
      <div className="message block">
        <label htmlFor="frm-message">Message</label>
        <textarea id="frm-message" rows="6" name="message"></textarea>
      </div>
      <div className="button block">
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now when you attempt to submit the form, you should see FormData in developer console.

Submitting the form to API

We will submit the form data to the API with [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) – no need for additional dependencies.

The form submission logic goes to the handleSubmit function. Here is the complete code in pages/index.js:

import React from 'react';

export default function Home() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    try {
      const response = await fetch('/api/contact', {
        method: 'post',
        body: new URLSearchParams(data),
      });
      if (!response.ok) {
        throw new Error(`Invalid response: ${response.status}`);
      }
      alert('Thanks for contacting us, we will get back to you soon!');
    } catch (err) {
      console.error(err);
      alert("We can't submit the form, try again later?");
    }
  }

  return (
    <form className="container" onSubmit={handleSubmit}>
      <h1>Get in touch</h1>
      <div className="email block">
        <label htmlFor="frm-email">Email</label>
        <input
          id="frm-email"
          type="email"
          name="email"
          autoComplete="email"
          required
        />
      </div>
      <div className="block phone">
        <label htmlFor="frm-phone">Phone</label>
        <input
          id="frm-phone"
          type="tel"
          name="phone"
          autoComplete="tel"
          required
        />
      </div>
      <div className="name block">
        <div>
          <label htmlFor="frm-first">First Name</label>
          <input
            id="frm-first"
            type="text"
            name="first"
            autoComplete="given-name"
            required
          />
        </div>
        <div>
          <label htmlFor="frm-last">Last Name</label>
          <input
            id="frm-last"
            type="text"
            name="last"
            autoComplete="family-name"
            required
          />
        </div>
      </div>
      <div className="message block">
        <label htmlFor="frm-message">Message</label>
        <textarea id="frm-message" rows="6" name="message"></textarea>
      </div>
      <div className="button block">
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

The handleSubmit sends data inside a POST request to the /api/contact route. We also wrap the FormData object in URLSearchParams to send data as application/x-www-form-urlencoded which is automatically decoded by Next.js API routes handler.

Handling form submission in the API route

Now we need to handle the form submission on the server. We are going to use Next.js API routes for that. API routes are located in pages/api folder. Let’s create pages/api/contact.js file which corresponds to the API route /api/contact.

First inside the pages/api/contact.js file paste the following code to test if we receive the data on the server.

export default function handler(req, res) {
  console.log(req.body);
  res.send(200);
}
Enter fullscreen mode Exit fullscreen mode

Try submitting the form now, you should see the data logged on the terminal. And now we are getting to the juicy part.

Sending emails with SendGrid and Superface

When a user submits the contact form, we want to send the submitted information to the website owner. First, we need to pick some email providers and study their API and SDK. Or we can use Superface with any provider.

Superface makes API integrations super easy. We don’t have to deal with API docs, and I can use many providers behind the same interface. Furthermore, I can use more ready-made API use cases from the Superface catalog. It’s a tool worth having in your toolbox.

Set up SendGrid

I’m going to use SendGrid as an email provider with Superface. Create your account, get your API key with Full Access and verify Single Sender Verification.

On Superface side, pick the use case, i.e.: Send Email.

Superface integrations catalog include other use cases, like Send SMS Message, or Send Templated Message.

Sending emails from the API route

Superface use cases are consumed with OneSDK, so we will have to install it.

npm i @superfaceai/one-sdk
Enter fullscreen mode Exit fullscreen mode

On Superface in Send Email use case, select sendgrid as a provider. We can use most of the code from the example in our API route handler, we just need to pass data correctly from the request.

Send Email use case with SendGrid selected as a provider

Paste the following code into your pages/api/contact.js file:

const { SuperfaceClient } = require('@superfaceai/one-sdk');

const sdk = new SuperfaceClient();

// Just check if all required fields are provided
function formValid(body) {
  return body.email && body.phone && body.first && body.last;
}

export default async function handler(req, res) {
  const body = req.body;

  if (!formValid(body)) {
    res.status(422).end();
    return;
  }

  const profile = await sdk.getProfile('communication/send-email@2.1.0');
  const message = `
    Email: ${body.email}
    Phone: ${body.phone}
    Name: ${body.first} ${body.last}
    Message: ${body.message} 
    `;
  const result = await profile.getUseCase('SendEmail').perform(
    {
      from: process.env.FROM_EMAIL,
      to: process.env.TO_EMAIL,
      subject: 'Message from contact form',
      text: message,
    },
    {
      provider: 'sendgrid',
      security: {
        bearer_token: {
          token: process.env.SENDGRID_API_KEY,
        },
      },
    }
  );

  try {
    const data = result.unwrap();
    console.log(data);
    res.status(201).end();
  } catch (error) {
    console.error(error);
    res.status(500).end();
  }
}
Enter fullscreen mode Exit fullscreen mode

You can notice that we are referring to some environment variables in the code (for example process.env.SENDGRID_TOKEN). We can store these in .env files. In the root of your project, create a .env.local file with the following contents (make sure to edit values):

# Email address verified in Single Sender Verification
FROM_EMAIL=from@example.com
# Email address where you want to send the submissions to
TO_EMAIL=to@example.com
# Sendgrid API key
SENDGRID_API_KEY=SG.abcdef...
Enter fullscreen mode Exit fullscreen mode

Our app is ready. Run (or restart) the development server with npm run dev and try to submit the form!

Form filled with all fields filled in

Received email with the form submissions

Conclusion

We have learned how to create a form in Next.js, submit the form with FormData and fetch, handle the submission in the API route and send it through an email.

Further possible improvements include adding CAPTCHA or honeypot fields to prevent spam submissions, improving form data validation with checks of phone and email address, format the submission as HTML, or providing a nicer feedback to the user upon submissions.

Besides sending an email, we can do a lot more with submissions, like sending data to CRM or Slack, or handle newsletter subscriptions. But that’s for another time – follow our profile or sign up for our monthly newsletter, so you don't miss our future tutorials.

Top comments (4)

Collapse
 
surjithctly profile image
Surjith S M

or just use Web3Forms?

Collapse
 
jnv profile image
Jan Vlnas

Except there are some limitations, like:

  • Volume: on SendGrid's free tier, you can send 100 emails per day while Web3Forms is limited to 250 submissions per month on a free plan.
  • Integrations: Want to send submissions to a Google Spreadsheet, Slack, or a CRM? You can build this with custom backend, but in case of Web3Forms you need to involve another service, like Zapier or Make, for extra costs and extra 3rd party data processors.
  • Customization: Want to send a copy of the submission to the user? You can customize the email as you want and no one will force upon their branding.
  • Protection: CAPTCHA services are available only on paid tier and their implementation of honeypot is questionable.

That being said, I think form backend services are useful for fully static websites hosted on, for example, GitHub pages, so I'd consider Web3Forms (or maybe rather FormEasy or FormSubmit) if I worked with this limitation.

In case of Next.js, we can use API routes and getting the backend logic up and running is easy and even free with Vercel.

Collapse
 
seninkokkodi profile image
Mohamed Senin

Why we use PHP instead ?

Collapse
 
jnv profile image
Jan Vlnas

Indeed, why? 😀