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.
Before we start, ensure you have the docker installed in your machine
You can also create a account in Mongodb atlas and generate the connection string.
MongoDB Atlas Database | Multi-Cloud Database Service
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).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:
For mongosh command reference
Create a new Next JS project using below
npx create-next-app@latest
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.
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
Schema
class from Mongoose is used to define the structure of documents in the MongoDB collection.contactSchema
is created with three fields: name
, email
, and comment
, each of type String
.Schema
constructor includes an options object with timestamps: true
.createdAt
and updatedAt
fields to each document, tracking when it was created and last updated.model
function from Mongoose is used to create a model for the "contact" collection.ContactModel
variable is assigned the result of models.contact
or a new model created with the name "contact" and the defined contactSchema
.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.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.
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;
Initializes state variables for form fields (formState
), notification activation (active
), and notification message (message
) using the useState
hook.
2. Handling Form Submission:
fetch
API to send a POST request to the "/api/contact" endpoint with the form data in JSON format.3. Handling Input Changes:
handleChange
function is called when any input field changes.formState
by spreading the previous state and updating the specific field by using e.target.id.4. JSX Structure:
active
and message
state.onSubmit
event that triggers the handleSubmit
function.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!