Next.js Contact Form Tutorial: MongoDB Integration and Docker Setup

thumbnail

Hello Guys, In this post, we’ll explore the process of creating a contact form using the Next.js, MongoDB and Docker. Let’s dive into the step-by-step guide on setting up a contact form that stores user messages in a Mongodb database. We will use docker to create a Mongodb container.

Prerequisites:

Before we start, ensure you have the docker installed in your machine

Install Docker Engine

You can also create a account in Mongodb atlas and generate the connection string.

MongoDB Atlas Database | Multi-Cloud Database Service

Docker Setup

To start the mongodb container

docker run -d -p 27017:27017 — name mongodb mongo

  • -d: Run the container in the background (detached mode).
  • -p 27017:27017: Map the container's port 27017 to the host machine's port 27017. MongoDB typically runs on port 27017.
  • --name mongodb: Assign a name to the container (you can choose any name).
  • mongo : pulls the mongodb image

To open a terminal for your MongoDB container, you can use the docker exec command. Now you can use mongosh commonad to connect and then create a new user and database.

docker exec -it mongodb bash

Now run mongosh command to connect to the database after that we can create a new user.

db.createUser({
    user: "test",
    pwd: "pass",
    roles: [{role: "readWrite", db: "contact" }]
})

This will create a new user “test” with roles readWrite and database for which we need. use command to create database. Below is the docs reference:

db.createUser()

For mongosh command reference

Run Commands

Setup NEXT.JS Project:

Create a new Next JS project using below

npx create-next-app@latest

Setting Up MongoDB Connection:

First, we will install the mongoose dependency

npm install mongoose

Next, In our .env file we will add the connection URI.

// .env

MONGODB_URI=mongodb://test:pass@127.0.0.1:27017/contact

Next we will create libs folder, create a file mongo.lib.ts where we have logic for mongodb connection.

// libs/mongo.lib.ts
import mongoose from "mongoose";
const MONGODB_URI = process.env.MONGODB_URI;
export const connectToMongo = async () => {
  if (MONGODB_URI) {
    try {
      await mongoose.connect(MONGODB_URI);
      console.log("Connected");
    } catch (err) {
      console.error("Unable to connect to the database ", err);
    }
  } else {
    console.error("Mongo URL not found");
  }
};

First import the mongoose and retrieve the MongoDB connection URI from the environment variables. This URI typically contains information about the MongoDB server, port, and authentication credentials.If the URI is available, it tries to connect to MongoDB using mongoose.connect(MONGODB_URI).Also we are catching and logging the errors.

Creating a Contact Model

Create a model for the database inside the models folder

import { Schema, model, models } from "mongoose";
const contactSchema = new Schema({
    name: String,
    email: String,
    comment: String,
},
    {
        timestamps: true
    }
)
const ContactModel = models.contact || model("contact", contactSchema)
export default ContactModel
  • The Schema class from Mongoose is used to define the structure of documents in the MongoDB collection.
  • The contactSchema is created with three fields: name, email, and comment, each of type String.
  • The second parameter to the Schema constructor includes an options object with timestamps: true.
  • This option automatically adds createdAt and updatedAt fields to each document, tracking when it was created and last updated.
  • The model function from Mongoose is used to create a model for the "contact" collection.
  • The ContactModel variable is assigned the result of models.contact or a new model created with the name "contact" and the defined contactSchema.
  • The models.contact part is used to check if a model with the name "contact" already exists. If it does, it is reused; otherwise, a new model is created.

Creating the API

We need to define the api inside the api folder and create a folder with the name of the endpoint you want and finally create route.ts for methods.

// app/api/contact/route.ts
import { connectToMongo } from "@/libs/mongo.lib";
import ContactModel from "@/models/contact.model";
import mongoose from "mongoose";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
    try {
        const { name, email, description } = await request.json()
        await connectToMongo()
        await ContactModel.create({ name, email, description })
        await mongoose.connection.close()
        return NextResponse.json({ message: "Message sent successfully" }, { status: 201 })
    } catch (err) {
        console.error(err)
        await mongoose.connection.close()
        return NextResponse.json({ message: "Failed to send message " }, { status: 400 })
    }
}

The POST function is the main handler for POST requests to this API endpoint. It has a NextRequest as an argument, which has request body which we access through request.json() method. Next, we call the connectToMongo function to establish a connection to MongoDB. The ContactModel.create() method is used to create a new document in the MongoDB collection.The mongoose.connection.close() method is called to close the MongoDB connection after the operation.If the process is successful, a JSON response is sent with a success message and a status code of 201.If an error occurs, the error is logged, the MongoDB connection is closed, and a JSON response indicating failure is sent with a status code of 400.

Creating the Contact Form Component:

Now, let’s create the contact form page in Next.js, create a new folder named contact (page name) inside the app folder and create page.tsx and layout.tsx. Now in layout.tsx

//src/app/contact/layout.tsx

import { Metadata } from "next";
import React from "react";


export const metadata: Metadata = {
    title: 'Contact',
    description: 'Page to contact',
}

export default function ContactLayout({ children }: { children: React.ReactNode }) {

    return <section className="min-h-screen flex justify-center items-center">{children}</section>
}

For page component

// src/app/contact/page.tsx
import React, { useState } from 'react';
const Contact = () => {
  // State for form fields and notifications
  const [formState, setFormState] = useState({ name: "", email: "", description: "" });
  const [active, setActive] = useState(false);
  const [message, setMessage] = useState("");
  // Handling form submission
  const handleSubmit = async (e) => {
    e.preventDefault();
    // Fetching API endpoint
    let res = await fetch("/api/contact", {
      method: "POST",
      body: JSON.stringify(formState),
    });
    let data = await res.json();
    // Displaying success/failure message
    setMessage(data.message);
    setActive(true);
    setTimeout(() => {
      setActive(false);
      setMessage("");
    }, 3000);
  };
  // Handling input changes
  const handleChange = (e) => {
    setFormState((prev) => ({
      ...prev,
      [e.target.id]: e.target.value,
    }));
  };
  return (
    <div className='relative'>
      {/* Notification for successful form submission */}
      <div
        style={{
          right: active && message ? 24 : "-50%",
        }}
        className='fixed bottom-5 duration-200 px-4 py-2 font-semibold rounded bg-sky-200 text-black'>
        <p>Sent successfully</p>
      </div>
      <h1 className='text-4xl mb-8 font-bold'>How can we help today?</h1>
      <form onSubmit={handleSubmit}>
        {/* Form fields */}
        <div className='flex flex-col mb-3'>
          <label htmlFor="name">Name</label>
          <input
            placeholder='Your Name'
            onChange={handleChange}
            id='name'
            type="text"
            required
            className='border rounded py-2 px-2 mt-1 outline-none focus:border-sky-800'
          />
        </div>
        <div className='flex flex-col mb-3'>
          <label htmlFor='email'>Email</label>
          <input
            placeholder='example@gmail.com'
            onChange={handleChange}
            id="email"
            type="email"
            required
            className='border rounded py-2 px-2 mt-1 outline-none focus:border-sky-800'
          />
        </div>
        <div className='flex flex-col'>
          <label htmlFor='description'>Description</label>
          <textarea
            placeholder='Comments'
            onChange={handleChange}
            id="description"
            required
            rows={6}
            className='border resize-none rounded py-2 px-2 mt-1 outline-none focus:border-sky-800'
          />
        </div>
        {/* Submit button */}
        <button className='mt-6 w-full py-3 bg-sky-800 rounded text-white font-semibold uppercase'>
          Send
        </button>
      </form>
    </div>
  );
};
export default Contact;
  1. State Initialization:

Initializes state variables for form fields (formState), notification activation (active), and notification message (message) using the useState hook.

2. Handling Form Submission:

  • It uses the fetch API to send a POST request to the "/api/contact" endpoint with the form data in JSON format.
  • It sets the notification message based on the API response and activates the notification.
  • After 3 seconds, it resets the notification to false and message to “”.

3. Handling Input Changes:

  • The handleChange function is called when any input field changes.
  • It updates the formState by spreading the previous state and updating the specific field by using e.target.id.

4. JSX Structure:

  • The JSX structure represents a form with input fields for name, email, and description.
  • It includes a notification div that is conditionally displayed based on the active and message state.
  • The form has an onSubmit event that triggers the handleSubmit function.
  • Input changes trigger the handleChange function.

Now you can run the app using npm run dev and visit the localhost:3000/contact and see it in action.

Congratulations! You’ve successfully set up a contact form in Next.js that stores messages in a MongoDB database. Feel free to customize and expand upon this foundation to suit your project’s needs.Happy coding!