3

How do I get uploaded image in next.js API route and save it on public folder? I have front end ready. I'm uploading images to an endpoint using plain JavaScript. here is the onSubmit function for uploading images. Suggest me if I'm doing it wrong here. The main question is how do I retrieve it?

  const onSubmit=async(e)=>{ 
        e.preventDefault();
        const fd=new FormData()
        fd.append('myfile',image.name)
        let res=await fetch(`http://localhost:3000/api/upload`,{
            method: 'POST',
            headers: {
              "Content-Type": "image/jpeg",
            },
            body: fd,
          })
           let response=await res.json(); 

one more bonus question, it's surely not a good idea to save the uploaded images on public folder. I have save it somewhere on the cloud.

Vraj Solanki
  • 51
  • 1
  • 1
  • 5

4 Answers4

8

This is the endpoint code I used for uploading image in nextjs, it requires some additional packages I will list them bellow also.

  1. next-connect
  2. multer
  3. uuid
import nextConnect from "next-connect";
import multer from "multer";
import { v4 as uuidv4 } from "uuid";

let filename = uuidv4() + "-" + new Date().getTime();
const upload = multer({
    storage: multer.diskStorage({
        destination: "./public/uploads/profiles", // destination folder
        filename: (req, file, cb) => cb(null, getFileName(file)),
    }),
});

const getFileName = (file) => {
    filename +=
        "." +
        file.originalname.substring(
            file.originalname.lastIndexOf(".") + 1,
            file.originalname.length
        );
    return filename;
};

const apiRoute = nextConnect({
    onError(error, req, res) {
        res
            .status(501)
            .json({ error: `Sorry something Happened! ${error.message}` });
    },
    onNoMatch(req, res) {
        res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
    },
});

apiRoute.use(upload.array("file")); // attribute name you are sending the file by 

apiRoute.post((req, res) => {
    res.status(200).json({ data: `/uploads/profiles/${filename}` }); // response
});

export default apiRoute;

export const config = {
    api: {
        bodyParser: false, // Disallow body parsing, consume as stream
    },
};
Abdullah Qasemi
  • 449
  • 1
  • 12
  • Hey I have copied pasted your code in on my endpoints, no error whatsoever but the files are not uploading. The folder upload/profile has also been created but images, files aren't there. – Vraj Solanki Jun 17 '22 at 19:25
  • Have you changed `apiRoute.use(upload.array("file //--> this"))`; to your attribute name? In your case I think it is "myfile" as used above. – Abdullah Qasemi Jun 17 '22 at 19:30
  • I noticed something in your code, you have appended the filename to your FormData object `fd.append('myfile',image.name)` you have to append the actual file to the object as `fd.append('myfile', image)` , and also change the `"Content-Type": "image/jpeg"` to `"Content-Type": "multipart/form-data"`. – Abdullah Qasemi Jun 17 '22 at 19:32
  • updated it and it's now saying ```{error: 'Sorry something Happened! Multipart: Boundary not found'}``` – Vraj Solanki Jun 17 '22 at 19:41
  • hey i found a solution on here https://stackoverflow.com/questions/49692745/express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos ... and it's now working, thank you so much for your time – Vraj Solanki Jun 17 '22 at 19:43
  • 1
    Remove the content-type header and try again, I think It is a problem with fetch api. It should work. – Abdullah Qasemi Jun 17 '22 at 19:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245715/discussion-between-vraj-solanki-and-abdullah-qasemi). – Vraj Solanki Jun 18 '22 at 09:35
  • You just saved my day!! – Mohit Sep 26 '22 at 14:04
  • @AbdullahQasemi can you share the code, how use this apiRoute in file uploaded side – Majeed Jan 13 '23 at 12:36
1

Nextjs 13+ is perfectly capable of handling form data and image upload by its own. You don't need formidable, multer etc... You can easily save images to your local directory with the blow code.

import { NextResponse } from "next/server";
import path from "path";
import { writeFile } from "fs/promises";

export const POST = async (req, res) => {
  const formData = await req.formData();

  const file = formData.get("file");
  if (!file) {
    return NextResponse.json({ error: "No files received." }, { status: 400 });
  }

  const buffer = Buffer.from(await file.arrayBuffer());
  const filename = Date.now() + file.name.replaceAll(" ", "_");
  console.log(filename);
  try {
    await writeFile(
      path.join(process.cwd(), "public/uploads/" + filename),
      buffer
    );
    return NextResponse.json({ Message: "Success", status: 201 });
  } catch (error) {
    console.log("Error occured ", error);
    return NextResponse.json({ Message: "Failed", status: 500 });
  }
};
0

I suggest the popular and lightweight formidable library:

# install
yarn add formidable@v3 @types/formidable
// pages/api/file-upload.ts
import fs from "fs";
import path from "path";
import { File } from "formidable";

// Important for NextJS!
export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<string>
) {
  try {
    // Parse request with formidable
    const { fields, files } = await parseFormAsync(req);

    // Files are always arrays (formidable v3+)
    const myfile = (files["myfile"] as any as File[])[0];

    // Save file in the public folder
    saveFile(myfile, "./public/uploads");

    // Return success
    res.status(200).json("success!");
  } catch (e) {
    return res.status(500).json(e);
  }
}

function saveFile(file: File, publicFolder: string): void {
  const fileExt = path.extname(file.originalFilename || "");

  fs.renameSync(file.filepath, `${publicFolder}/${file.newFilename}${fileExt}`);
}
// ./helpers/formidable.ts
import type { NextApiRequest } from "next";
import formidable from "formidable";

export type FormidableParseReturn = {
  fields: formidable.Fields;
  files: formidable.Files;
};

export async function parseFormAsync(
  req: NextApiRequest,
  formidableOptions?: formidable.Options
): Promise<FormidableParseReturn> {
  const form = formidable(formidableOptions);

  return await new Promise<FormidableParseReturn>((resolve, reject) => {
    form.parse(req, async (err, fields, files) => {
      if (err) {
        reject(err);
      }

      resolve({ fields, files });
    });
  });
}

Bonus question

one more bonus question, it's surely not a good idea to save the uploaded images on public folder. I have save it somewhere on the cloud.

S3 and other cloud services

You can save on cloud services with Formidable.

See the official examples: https://github.com/node-formidable/formidable/blob/master/examples/store-files-on-s3.js

But you don't need to use cloud storage to protect private uploads. You can store them locally.

Working with private uploads locally

  1. Saving:
    • Store the uploads in a non-public folder;
    • Ex. /private-uploads/{logged_user_id}/;
  2. Reading:
    • Create an API page to fetch the file
      • Ex. https://.../uploads/{filename}
    • Fail if the file doesn't belong to the authenticated user;
    • Send the file as the response;
  3. Security:
    • With the above folder scheme, hackers can use .. and similar on the filename to obtain unauthorized access;
    • Sanitize the filename having this in mind (ex. only allow alphanumeric characters);
    • Alternatively, use a database table to control ownership instead of a folder scheme;
Daniel Loureiro
  • 4,595
  • 34
  • 48
0

/pages/api/createpost

Using for npm i formidable nextjs javascript and mongodb. I have used mongoose to create my models

import { IncomingForm, File } from 'formidable';
import * as fs from "fs";
import path from "path";
import { v4 as uuidv4 } from 'uuid';
import Post from '../../../models/Model';

export const config = {
    api: {
        bodyParser: false,
    },
};

export default async function handler(req, res) {
    if (req.method !== 'POST') {
        return;
    }

    // Parse incoming form data using a Promise
    try {
        const data = await new Promise((resolve, reject) => {
            const form = new IncomingForm();
            form. Parse(req, (err, fields, files) => {
                if (err) return reject(err);
                resolve({ fields, files });
            });
        });

        // Define the folder path for storing images
        const publicFolderPath = path.Join(process.cwd(), 'public', "images");
      
  let responseData;

        // Check if an image file was uploaded
        if (data.files.image) {
            const oldPath = data.files.image[0].filepath;
            const newFileName = new Date().getTime() + "-" + uuidv4() + "-" + data.files.image[0].originalFilename;
            const newPath = path.join(publicFolderPath, newFileName);

            try {
                // Copy the uploaded image to the designated path
                await fs.promises.copyFile(oldPath, newPath);
                console.log('File copied to:', newPath);
                console.log('File uploaded and renamed:', newFileName);

                // Create an object with form data
                const formData = {
                    banner: data.fields.banner[0],
                    body: data.fields.body[0],
                    image: newFileName,
                    author: data.fields.author[0],
                };

                // Create a new post entry in the mongodb database
                try {
                    const post = await Post.create(formData);
                    responseData = post;
                } catch (err) {
                    console.log(err.message);
                }

            } catch (error) {
                console. Error('Error renaming/moving file:', error);
                res.status(500).json({ error: 'Error processing uploaded file.' });
                return;
            }
        } else {
            responseData = data;
        }

        // Respond with the processed data
        res.status(200).json(responseData);

    } catch (error) {
        console. Error('Error parsing form data:', error);
        res.status(500).json({ error: 'Error processing form data.' });
    }
}