Vraj Solanki
Vraj Solanki

Reputation: 101

How do I get uploaded image in next js and save it?

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.

Upvotes: 8

Views: 47336

Answers (4)

Teljo Antony
Teljo Antony

Reputation: 261

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 below 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 });
  }
};

Upvotes: 25

Steven Kamwaza
Steven Kamwaza

Reputation: 3

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

Upvotes: 0

Daniel Loureiro
Daniel Loureiro

Reputation: 5383

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;

Upvotes: 1

Abdullah Qasemi
Abdullah Qasemi

Reputation: 459

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
    },
};

Upvotes: 8

Related Questions