File Uploading In NextJS - The Best way

File Uploading In NextJS - The Best way

File Uploading is one of the crucial parts of any app, In this Blog I will tell you How you can upload images on the NextJS application.

For the Past Few Days, I have been working on a project where I need to apply this functionality so I tried many things But UploadThing works as expected for the NextJS project.

What you will learn after this Blog -

  1. How to Initialize UploadThing on a Project?

  2. How to store this IMG URL to the MongoDB?

  3. How to fetch this IMG from the Database(MongoDB)

Firstly Let's Create a NextJS application, you can create this via this -

npx create-next-app@latest

Now, go to UploadThing and SignUp, create a new app, and store these to .env.local

UPLOADTHING_SECRET=Your Secret key 
UPLOADTHING_APP_ID=Your Id

after that install uplaodthing through this command

npm install uploadthing @uploadthing/react

Now inside the app directory create a folder api and inside that create another folder named uploadthing and then create a file named cors.ts
and put these things into it-

import { createUploadthing, type FileRouter } from "uploadthing/next";

const f = createUploadthing();

export const ourFileRouter = {
  imageUploader: f({ image: { maxFileSize: "4MB" } }).onUploadComplete(
    async ({ file }) => {
      console.log("file url", file.url);
    }
  ),
} satisfies FileRouter;

export type OurFileRouter = typeof ourFileRouter;

inside uploadthing folder create route.ts and put these things into this -

import { createNextRouteHandler } from "uploadthing/next";

import { ourFileRouter } from "../uploadthing/cors";

export const { GET, POST } = createNextRouteHandler({
  router: ourFileRouter,
});

You can add UploadThing styling, for that you should need to add this to your app/layout.tsx

import "@uploadthing/react/styles.css";

Now Create the UploadThing Components, it will be good if you create a utils folder and create a file name uploadthing.ts

import { generateComponents } from "@uploadthing/react";

import type { OurFileRouter } from "~/app/api/uploadthing/core";

export const { UploadButton, UploadDropzone, Uploader } =
  generateComponents<OurFileRouter>();

Now Before using this button Let's set MongoDB so that our Link will go to the database

Firstly create the ConnectToDB function inside the app and create one folder named db and inside that create db.js file with this -

import mongoose from "mongoose";

const connectToDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URL!);
    console.log("MongoDB connected");
  } catch (err: any) {
    console.log(err.message);
    process.exit(1);
  }
};
export default connectToDB;

Now Let's create a model for the database -

const mongoose = require("mongoose");

const notesSchema = new mongoose.Schema({
  notesTitle: {
    type: String,
    required: true,
  },
  notesDescription: {
    type: String,
    required: true,
  },
  fileLink: {
    type: String,
    required: true,
  },
  creator: {
    type: String,
  },
  creatorImgUrl: {
    type: String,
  },
});

const Notes = mongoose.models.Notes || mongoose.model("Notes", notesSchema);

export default Notes;

Now let's create a api route for handling this inside api folder create a folder name upload

import connectToDB from "@/db/db";
import Notes from "@/models/Notes";
import { NextRequest, NextResponse } from "next/server";

connectToDB();

export async function POST(req: NextRequest, res: NextResponse) {
  try {
    const reqBody = await req.json();
    const { notesTitle, notesDescription, fileLink, creator, creatorImgUrl } =
      reqBody;
    const Note = new Notes({
      notesTitle,
      notesDescription,
      fileLink,
      creator,
      creatorImgUrl,
    });

    const NewNotes = await Note.save();
    console.log(NewNotes);

    const response = NextResponse.json({
      message: "Notes created successfully",
      success: true,
      NewNotes,
    });

    return response;
  } catch (error: any) {
    const response = NextResponse.json(
      { error: error.message },
      { status: 500 }
    );
    return response;
  }
}

export async function GET() {
  try {
    const Note = await Notes.find();

    const response = NextResponse.json({
      message: "Review fetched successfully",
      success: true,
      Note,
    });
    return response;
  } catch (error: any) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}

Now Let's handle this route for the front and create one folder Upload inside app

"use client";

import React, { useState } from "react";
import { UploadButton } from "../../utils/uploadthing";
import { useRouter } from "next/navigation";
import { useClerk } from "@clerk/clerk-react";
import LoadingSpinner from "@/components/LoadingSpinner";

const Page: React.FC = () => {
  const [notesTitle, setNotesTitle] = useState<string>("");
  const [notesDescription, setNotesDescription] = useState<string>("");
  const [fileLink, setFileLink] = useState<string>();
  const [loading, setLoading] = useState(false);
  const { user } = useClerk();

  const router = useRouter();

  const handleNotesTitleChange = (
    event: React.ChangeEvent<HTMLInputElement>
  ) => {
    setNotesTitle(event.target.value);
  };

  const handleNotesDescriptionChange = (
    event: React.ChangeEvent<HTMLTextAreaElement>
  ) => {
    setNotesDescription(event.target.value);
  };

  const handleSubmit = async () => {
    try {
      if (!notesTitle || !notesDescription || !fileLink) {
        alert("Please provide both Content and fileLink before submitting.");
        return;
      }

      setLoading(true);

      const response = await fetch("/api/upload", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          notesTitle,
          fileLink,
          notesDescription,
          creator: user?.fullName,
          creatorImgUrl: user?.imageUrl,
        }),
      });

      if (response.ok) {
        router.push("/dashboard");
      } else {
        alert(`Submission failed. Status: ${response.status}`);
      }
    } catch (error: any) {
      alert(`Error during submission: ${error.message}`);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="flex justify-center flex-col items-center h-screen">
      <h1 className="text-transparent bg-gradient-to-br from-red-800 to-blue-600 bg-clip-text text-3xl sm:text-5xl font-bold mb-4">
        Upload Notes
      </h1>
      <div className="bg-[radial-gradient(ellipse_at_bottom,_var(--tw-gradient-stops))] from-gray-400 via-gray-600 to-gray-800 p-6 rounded-md shadow-md mb-6 sm:w-96">
        <form>
          <div className="mb-4">
            <label htmlFor="notesTitle" className="block text-gray-300">
              Notes Title
            </label>
            <input
              type="text"
              id="notesTitle"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-indigo-500"
              placeholder="Enter notes title"
              value={notesTitle}
              onChange={handleNotesTitleChange}
            />
          </div>
          <div className="mb-4">
            <label htmlFor="notesDescription" className="block text-gray-300">
              Notes Description
            </label>
            <textarea
              id="notesDescription"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-indigo-500"
              placeholder="Enter notes description"
              value={notesDescription}
              onChange={handleNotesDescriptionChange}
            />
          </div>
          <div>
            <UploadButton
              endpoint="imageUploader"
              onClientUploadComplete={(res: any) => {
                console.log("Url: ", res[0].url);
                setFileLink(res[0].url);
                console.log(res);
                alert("Upload Completed");
              }}
              onUploadError={(error: Error) => {
                alert(`ERROR! ${error.message}`);
              }}
            />
          </div>

          {loading ? (
            <LoadingSpinner />
          ) : (
            <button
              type="button"
              className="w-full py-2 bg-indigo-500 text-white rounded-md hover:bg-indigo-600 focus:outline-none mt-8"
              onClick={handleSubmit}
            >
              Submit
            </button>
          )}
        </form>
      </div>
    </div>
  );
};

export default Page;

Here in the image, you see you have the Choose File button which is coming through UploadThing and when you choose a file it will be saved to your uploadthing dashboard and I am saving this URL to the MongoDB database.

Here is how you can upload images on the UploadThing and later store them to mongoDB, Hope this Blog Helped you to learn something new.

Happy Coding🎉