How to Upload Image to MongoDB using Only NextJS 13.4?
Next.js is a React framework that enables functionality such as server-side rendering and generating static websites. MongoDB is a NoSQL database that stores data in flexible, JSON-like documents. Combining Next.js with MongoDB allows you to build full-stack applications where the backend interacts directly with the database and serves dynamic content to the frontend.
Uploading images to MongoDB using only Next.js 13.4 involves setting up a server-side route to handle the image upload and storing the image in MongoDB. In this article, we will learn how to upload an image to MongoDB using NextJS 13.
Approach to upload Image to MongoDB using only NextJS 13.4
- The project connects to MongoDB using Mongoose and implements GridFS for file storage. It defines a schema for posts with
name
andimageUrl
fields. - The API includes a
POST
endpoint to handle form data, upload images to GridFS, and save posts to MongoDB, as well as aGET
endpoint to fetch all posts. - Another route is provided for fetching and deleting specific images from GridFS and MongoDB. On the frontend,
react-hook-form
is used for form submissions, with FileReader handling image previews. - The
NewPost
component includes a form for inputting post details and uploading images, displaying a preview upon file selection. The Home page renders theNewPost
component.
Step to Create a NextJS App and Installing Module
Step 1: Installation
npx create-next-app@13.4.4 my-next-app
cd my-next-app
Step 2: Install the required dependencies:
npm install tailwindcss react-hook-form mongoose mongodb
Project Structure
Step 3: Setting Up MongoDB Connection inside .env file add:
MONGOB_URI = mongodb+srv://yourMongodbnab:yourpassword@cluster0.ldnz1.mongodb.net/DatabaseName
The Updated dependencies of your package.json file will look like this:
"dependencies": {
"autoprefixer": "10.4.19",
"eslint": "9.3.0",
"eslint-config-next": "14.2.3",
"mongodb": "^6.6.2",
"mongoose": "^8.4.0",
"next": "13.4.4",
"postcss": "8.4.38",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "^7.51.5",
"tailwindcss": "^3.4.3"
}
Example: Below is an example of uploading an Image to MongoDB using only NextJS 13.4.
// utils/connectToDb.js
import mongoose from "mongoose";
let client = null;
let bucket = null;
const MONGOB_URI = "mongodb://localhost:27017/myapp";
async function connectToDb() {
if (client) {
return { client, bucket };
}
await mongoose.connect(MONGOB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
client = mongoose.connection;
// use mongoose connection
const db = mongoose.connection;
bucket = new mongoose.mongo.GridFSBucket(db, {
bucketName: "images",
});
console.log("Connected to the Database");
return { client, bucket };
}
export default connectToDb;
// utils/posts.js
import mongoose, { Schema } from "mongoose";
const postsSchema = new Schema({
name: { type: String },
imageUrl: { type: String },
});
module.exports = mongoose.models.Posts || mongoose.model("Posts", postsSchema);
// api/route.jsx
import { NextResponse } from "next/server";
import { Readable } from "stream";
import Posts from "@/utils/posts";
import connectToDb from "@/utils/connectToDb";
export const revalidate = 0;
export const POST = async (req) => {
const { client, bucket } = await connectToDb();
// const newItem = new Posts({ name: "hloe", imageUrl: "image" });
// await newItem.save();
let name;
let image;
const formData = await req.formData();
for (const entries of Array.from(formData.entries())) {
const [key, value] = entries;
if (key == "name") {
name = value;
}
if (typeof value == "object") {
image = Date.now() + value.name;
console.log("done");
const buffer = Buffer.from(await value.arrayBuffer());
const stream = Readable.from(buffer);
const uploadStream = bucket.openUploadStream(image, {});
await stream.pipe(uploadStream);
}
}
const newItem = new Posts({
name,
imageUrl: image,
});
await newItem.save();
return NextResponse.json({ msg: "ok" });
};
export const GET = async () => {
const { client, bucket } = await connectToDb();
const posts = await Posts.find({});
// console.log(await Posts.find({}));
return NextResponse.json(posts);
};
// api/[data]/route.jsx
import connectToDb from "@/utils/connectToDb";
import { NextResponse } from "next/server";
import Posts from "@/utils/posts";
export const revalidate = 0;
export const GET = async (req, { params }) => {
const { client, bucket } = await connectToDb();
const { data } = params;
const files = await bucket
.find({
filename: data,
})
.toArray();
// the resulat is an array and i take the first
//element that i found
const file = files[0];
//reading file using openDownloadStreamByName
const stream = bucket.openDownloadStreamByName(file.filename);
return new NextResponse(stream, {
Headers: { "Content-Type": file.contentType },
});
};
export const DELETE = async (req, { params }) => {
const { client, bucket } = await connectToDb();
try {
const { data } = params;
const deletedPost = await Posts.findByIdAndRemove(data);
const files = await bucket
.find({
filename: deletedPost.imageUrl,
})
.toArray();
// the resulat is an array and i take the first
//element that i found
const file = files[0];
bucket.delete(file._id);
return NextResponse.json({ msg: "ok" });
} catch (e) {
console.log(e);
}
};
// components/newPost.jsx
"use client";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
const NewPost = () => {
const { register, handleSubmit, reset } = useForm();
const [previewImage, setPreviewImage] = useState(null);
const router = useRouter();
const [form, setForm] = useState({});
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setPreviewImage(reader.result);
};
reader.readAsDataURL(file);
} else {
setPreviewImage(null);
}
};
const onSubmit = async (data) => {
let formData = new FormData();
formData.append("name", data.name);
for (let file of data.imageUrl) {
formData.append(file.name, file);
}
await fetch("/api", {
method: "POST",
body: formData,
});
// Clear form data and reset input fields
setForm({});
setPreviewImage(null);
reset();
router.refresh();
};
return (
<main className="flex flex-col items-center justify-between ">
<div className="max-w-md mx-auto">
<form
className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="input1"
>
Text Input 1
</label>
<input
className="shadow appearance-none border rounded w-full
py-2 px-3 text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="input1"
type="text"
placeholder="Enter text input 1"
{...register("name")}
/>
</div>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="fileInput"
>
File Input
</label>
<input
type="file"
accept="image/*"
className="file-input file-input-bordered w-full max-w-xs"
id="fileInput"
{...register("imageUrl")}
onChange={handleFileChange}
/>
{ previewImage && (
<Image
width={200}
height={200}
src={previewImage}
alt="Preview"
className="mt-2 w-full h-32 object-cover"
/>
)}
</div>
<div className="flex items-center justify-between">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold
py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit"
>
Submit
</button>
</div>
</form>
</div>
</main>
);
};
export default NewPost;
// components/newPost.jsx
"use client";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
const NewPost = () => {
const { register, handleSubmit, reset } = useForm();
const [previewImage, setPreviewImage] = useState(null);
const router = useRouter();
const [form, setForm] = useState({});
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setPreviewImage(reader.result);
};
reader.readAsDataURL(file);
} else {
setPreviewImage(null);
}
};
const onSubmit = async (data) => {
let formData = new FormData();
formData.append("name", data.name);
for (let file of data.imageUrl) {
formData.append(file.name, file);
}
await fetch("/api", {
method: "POST",
body: formData,
});
// Clear form data and reset input fields
setForm({});
setPreviewImage(null);
reset();
router.refresh();
};
return (
<main className="flex flex-col items-center justify-between ">
<div className="max-w-md mx-auto">
<form
className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="input1"
>
Text Input 1
</label>
<input
className="shadow appearance-none border rounded w-full
py-2 px-3 text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="input1"
type="text"
placeholder="Enter text input 1"
{...register("name")}
/>
</div>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="fileInput"
>
File Input
</label>
<input
type="file"
accept="image/*"
className="file-input file-input-bordered w-full max-w-xs"
id="fileInput"
{...register("imageUrl")}
onChange={handleFileChange}
/>
{previewImage && (
<Image
width={200}
height={200}
src={previewImage}
alt="Preview"
className="mt-2 w-full h-32 object-cover"
/>
)}
</div>
<div className="flex items-center justify-between">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold
py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit"
>
Submit
</button>
</div>
</form>
</div>
</main>
);
};
export default NewPost;
// page.jsx
import NewPost from "./components/newPost";
export default async function Home() {
return (
<div className="">
<div className=" my-20">
<NewPost />
</div>
</div>
);
}
Start your Next.js development server:
npm run dev
Visit http://localhost:3000 in your browser to see the application in action.
Output:
When you enter the file name, upload a file, and then click on the submit button, your file is successfully uploaded to MongoDB.
Conclusion
In conclusion, integrating Next.js with MongoDB allows for the development of full-stack web applications with dynamic content management and storage capabilities. By following the outlined steps, including setting up the MongoDB connection, defining schemas, creating API routes, building UI components, and integrating them into pages, you can efficiently handle tasks like image uploads to MongoDB within a Next.js 13.4 project. This combination enables the creation of robust, scalable, and responsive web applications with server-side rendering and static site generation capabilities, suitable for a wide range of modern web development needs.