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 and imageUrl fields.
  • The API includes a POST endpoint to handle form data, upload images to GridFS, and save posts to MongoDB, as well as a GET 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 the NewPost 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

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.

JavaScript
// 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;
JavaScript
// 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);
JavaScript
// 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);
};
JavaScript
// 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);
    }
};
JavaScript
// 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;
JavaScript
// 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;
JavaScript
// 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.