4

I am trying to send a file to api in a next js app. The image will be uploaded to cloudinary:

Function calling api is:

  async uploadButtonClicked(imageUpload) {
      const formData = new FormData();
      //formData.append('test', "testing");
      formData.append('postPic', imageUpload.files[0]);
      const res = await fetch('/api/hello', {
        method: 'POST',
        headers: {
          'Content-Type': 'multipart/form-data',
          'Accept': 'application/json'
        },
        body: formData,
      })

console.log(imageUpload.files[0]) in front end gives me the values below and looks good. enter image description here

In the api,

export default (upload.single('postPic'), async (req, res) => {
  console.log(req.body)

The above is undefined when I use

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

When I remove the bodyParser setting (bodyParser is true), the data is as a stream which is not useful for upload. I get a upload error as shown below: enter image description here

Cloudinary will upload if the body reaches the api in this format below: enter image description here

What should be changed to get the body (which is basically an image file) to reach the api in the proper format as shown above?

I think the question can also be asked as: Why is the req.body undefined when using bodyParser: false ?

Kal
  • 1,656
  • 4
  • 26
  • 41

3 Answers3

5

I ran into the same error but Formidable worked for me. Try putting the image into Form Data first.

From Client:

export const uploadImage = async (image) => {

    const formData = new FormData();
    formData.append('image', image);
    const response = await axios.post('/api/v1/image', formData);
    return response.data;
}

And below is on server API : /api/v1/image

import formidable from 'formidable';


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

export default async (req, res) => {
  const form = new formidable.IncomingForm();
  form.uploadDir = "./";
  form.keepExtensions = true;
  form.parse(req, (err, fields, files) => {
    console.log(err, fields, files);
  });
};
Neil Graham
  • 593
  • 1
  • 5
  • 17
dthanny
  • 81
  • 1
  • 3
  • 1
    Thanks. Im putting image in form data already - `formData.append('postPic', imageUpload.files[0]);`. Can you please explain what formidable does in this case and why use it? – Kal Oct 24 '20 at 15:43
  • 1
    In this instance, it is used to read the form data and provide the file. I am getting something similar to what you are trying to achieve (Client -> NextJS -> Express Server -> Cloudinary – dthanny Oct 24 '20 at 16:12
  • Let me know how you get on with it. – dthanny Oct 24 '20 at 16:13
0

If you're uploading to Cloudinary, why are you trying to send the file from the client to your nextjs api? You can upload directly to Cloudinary.

With a library like react-uploady. It's as simple as this:

import React from "react";
import Uploady from "@rpldy/uploady";
import UploadButton from "@rpldy/upload-button";

const CLOUD_NAME = "<your-cloud-name>";
const UPLOAD_PRESET = "<your-upload-preset>";

const App = () => (<Uploady
    destination={{ 
        url: `https://api.cloudinary.com/v1_1/${CLOUD_NAME}/upload`,
        params: {
            upload_preset: UPLOAD_PRESET,
        }
    }}>
    <UploadButton/>
</Uploady>);
poeticGeek
  • 1,001
  • 10
  • 14
  • 4
    Maybe he wants to hide secrets for using 3rd services – nghiaht Oct 22 '20 at 07:10
  • 1
    In general yes. For Cloudinary specifically, there is no need to hide these parameters. What should be done in Production is use [signed uploads](https://cloudinary.com/documentation/upload_images#generating_authentication_signatures) - meaning, signing the upload data on the server side – poeticGeek Oct 22 '20 at 07:32
  • 1
    @nghiaht - I thought its better to do signed uploads hiding api values. In the end, I did end up directly using the cloudinary api url with a POST but it needed an upload preset and unsigned upload. – Kal Oct 23 '20 at 01:47
  • This is a very interesting question. I'm trying to handle the multipart form data in Next.js's API routes, they said it is the core Node.js http request, I tried using `formidable` to parse but failed. I'll post an answer if I could solve it – nghiaht Oct 23 '20 at 07:36
  • @Kal plz dont do unsigned uploads in Production code. it will allow anyone to use the details to upload to your cloud. signed uploads are the best way in production env. Let me know if you need assistance with that – poeticGeek Oct 23 '20 at 07:40
  • What I imagine is: Client browse and upload image - Next.js API Routes: sign data then upload to Cloudinary. Am I correct? – nghiaht Oct 23 '20 at 07:47
  • yup thats correct. see this [sandbox](https://codesandbox.io/s/react-uploady-cloudinary-signed-sample-8tw8d) for example – poeticGeek Oct 23 '20 at 12:01
  • @poeticGeek Thanks for the valuable suggestion. I guess you are asking me to do is try the Next api which was not working for me. Is there a way to do this without installing uploady? I already have cloudinaryjs installed and I am very reluctant to install libraries for one off things. I will also figure out to use signed uploads. – Kal Oct 23 '20 at 22:52
  • @Kal you can do signed uploads without Uploady, it will just be more code and more chance for bugs. You can see in my sandbox, the call to sign the params isnt Uploady specific. you can do direct upload to Cloudinary + a next.js API for the signing. It would just a lot easier to do with Uploady. If you're going to build upload functionality into your application, I seriously recommend using a library like Uploady. There all sorts of issues that youd need to handle (multiple files, chunked uploads, etc) that doing alone is just a waste of your time, in my opinion. – poeticGeek Oct 24 '20 at 07:35
0

This works for me

import cloudinary from 'cloudinary';
import formidable from 'formidable';

cloudinary.config({ 
  cloud_name: '', 
  api_key: '', 
  api_secret: '' 
});

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

export default async (req, res) => {
  const form = new formidable.IncomingForm();
  form.uploadDir = "./";
  form.keepExtensions = true;
  await form.parse(req, (err, fields, files) => {
    cloudinary.v2.uploader.upload(files.image.path, function(error, result) {
      res.status(200).json({
        success: true,
        data: result.secure_url
      })
    });
  });
}
troydo42
  • 129
  • 2
  • 8
  • 2
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – blazej Aug 13 '21 at 18:34