DEV Community

Cover image for Sending Files to Cloudinary Over REST API with Next.js
Andrew Rowley
Andrew Rowley

Posted on • Updated on • Originally published at internetdrew.com

Sending Files to Cloudinary Over REST API with Next.js

If you're struggling to take a file from user input and get it to your API so you can upload it to Cloudinary and send your secure_url back over to the front end of your application, this is for you.

When I tried figuring this out myself, there were lots of complicated videos and incomplete tutorials that made it a bit difficult to understand. But with this approach, you can easily send your files from your file input to your rest API, send that file up to Cloudinary, and return your secure_url for later usage.

Why This Matters

You've probably seen documentation and tutorials using Cloudinary directly in the client, either with an SDK or directly to the API. The problem with doing so is that every time you run the operation of sending the file over to Cloudinary, your endpoint is visible in the Network tab of your browser, making the endpoint vulnerable to malicious behavior. By handling this as a restful API, we can do all of the work on the server and hide any secure URLs, only exposing our endpoint in the browser, which will be /api/upload.

Side note: This article largely addresses getting access to the file itself on the server side. So if you are not using Cloudinary, you can still follow along and deviate where I start diving into the Cloudinary SDK.

Creating and Managing Our Form Data

I will also be a bit opinionated here because of how difficult form data can be in React. For everything I bring in, I'll explain how it saves you headaches.

First, we start with our Form component:

export default function Form() {
  return (
    <main>
      <form>
        <input type='file' />
        <button type='submit'>Send</button>
      </form>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

The first thing I will bring in to easily manage the form data is React Hook Form. Managing this one input doesn't warrant this package, but since most forms have multiple inputs of varied types, I strongly suggest you install this to make managing your data simple.

npm install react-hook-form
Enter fullscreen mode Exit fullscreen mode

Once you have this installed, you can bring a few things in we will need:

import { useForm } from 'react-hook-form';

export default function Form() {
const {
    register,
    handleSubmit,
  } = useForm();

  return (
    <main>
      <form>
        <input type='file' />
        <button type='submit'>Send</button>
      </form>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

We'll use register and handleSubmit to take a closer look at our data. We use register to register our input data. In this instance, we'll register our file input as 'file'. This will be attached to the data object we get upon submission. To see that data object, we also need to use handleSubmit, which handles a submit function, which we can make here as onSubmit.

import { useForm } from 'react-hook-form';

const onSubmit = async (data) => {
  const { file } = data;
  console.log(file);
};

export default function Form() {
const {
    register,
    handleSubmit,
  } = useForm();

  return (
    <main>
      <form onSubmit={handleSubmit(onSubmit)}>
        <input type='file' {...register('file')} />
        <button type='submit'>Send</button>
      </form>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

When we submit, we get our data object, as seen in onSubmit. From there, we can de-structure the file. If we had another input and registered it as 'name', for instance, we could also de-structure the {name} from it, as the data object would have both name and file properties because of register.

FFileList {0: File, length: 1}
 0: File
      lastModified: 1684160876984
      lastModifiedDate: Mon May 15 2023 10:27:56 GMT-0400 (Eastern Daylight Time) {}
      name: "filename.pdf"
      size: 69759
      type: "application/pdf"
      webkitRelativePath: ""
       [[Prototype]]: File
      length: 1
Enter fullscreen mode Exit fullscreen mode

What you'll see when you console.log(file) is a FileList. At index 0 will be our file.

Now that we have our file, we can push it over to our API where we want to safely post to Cloudinary.

npm i axios
Enter fullscreen mode Exit fullscreen mode

You could use the Fetch API for this, but I found there were issues with headers when using fetch. So... use Axios and save headaches on this one (or figure it out by digging around, if you truly want fetch).

const onSubmit = async (data) => {
  const { file } = data;

  const res = await axios.post('/api/upload', file);
  console.log(res)
};
Enter fullscreen mode Exit fullscreen mode

With a POST method, we push our file over to the other side, which is most likely where you started wanting to rip your hair out in confusion if you've attempted this before.

Accessing the File Object on the Server

Depending on how you started your Next app, you'll either want to go to /src/pages/api or /pages/api. Next.js API Routes allow you to build your own API using Next.js, so there's no need to bring in Express (unless you want to). You can learn more about Next.js API Routes here.

Within the API directory, you can see a hello.js file. You can take a look at that to see how things work and change the endpoint, but it's pretty straightforward, so also feel free to just rename that or create a new upload.js file. That's what enables us to send the POST request over to /api/upload.

In upload.js, we need to export the handler function.

export default async function handler(req, res) {
  console.log(req.body);
  return res.status(200).json('you made it.');
}
Enter fullscreen mode Exit fullscreen mode

If you look at the request body, you'll see the string '[object FileList]'. But you don't need a string. You need a file! To get that file, we're going to use Formidable.

npm install formidable
Enter fullscreen mode Exit fullscreen mode

To use Formidable, we'll use a Promise as an efficient way to actually get the file.

Since we want Formidable to do all of the handling of the request, we'll want to stop automatic body parsing or we won't be able to access the file. So be sure to include the config export in the server so it turns off automatic request body parsing.

import formidable from 'formidable';

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

export default async function handler(req, res) {
  const file = await new Promise((resolve, reject) => {
    const form = formidable();

    form.parse(req, (err, fields, files) => {
      if (err) return reject(err);
    });
    form.on('file', (formName, file) => {
      resolve(file);
    });
  });

  console.log(file);
  return res.status(200).json('you have the file.');
}
Enter fullscreen mode Exit fullscreen mode

Here's what's happening above. First, we make sure that our handler is async. Within there, we define the file variable for when we resolve/reject the promise. We create an instance of formidable (form).

The first thing Formidable will do is parse the request, and with a callback function that takes in error, fields, and files, we can do some management and gain visibility.

We don't actually get the file from here, but we can see if a file has made it successfully and is being detected if we console.log(files). Since we don't access files directly from there, we just return the rejection of the promise along with the error if one occurs.

With form.on, however, we can tell formidable what we want to do when it detects a file. Here, we're telling formidable that when it does detect a file, we want it to resolve the promise with that file. We now have access to the file object we sent over from the front end! You should now see it logged as a PersistentFile.

Uploading Your File to Cloudinary

While you can use the API endpoint directly, I used the SDK for a little simplicity (and easy clues on how to use it).

npm i cloudinary
Enter fullscreen mode Exit fullscreen mode

And in /api/upload:

import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  secure: true,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});
Enter fullscreen mode Exit fullscreen mode

In your Next app, you should see a file named .env.local. If you don't see it, feel free to create it.

This is a safe place to store your environment variables. You can store sensitive strings here. To keep this file from being added to your git repo on commit, be sure to add this to your .gitignore file, so your sensitive data will be safe.

To access these values on the server, you just add process.env.WHATEVER_YOU_NAMED_IT server side. You can learn more about environment variables in Next.js here.

Before you can push the files to Cloudinary, you may need to change some settings. For example, in my use case, there is a strong likelihood that files will be PDFs, which Cloudinary does not allow you to upload by default. You might see that one page that says in order to unlock this capability you need to contact them, but that might just be old collateral. You can actually just make some changes in your settings here.

Now, with access to our file object, we can call on Cloudinary's uploader for unsigned_upload (again, you might not need to change settings depending on what you're doing). The SDK then expects two things. One is the 'file', which has the type of String, so what you really need is the filepath from within the file object. The other is the name of the preset from the settings I mentioned before.

try {
    const data = await cloudinary.uploader.unsigned_upload(
      file.filepath,
      'preset-name'
    );
    return res.status(200).json(data.secure_url);
  } catch (error) {
    console.error(error);
  }
Enter fullscreen mode Exit fullscreen mode

Once you do that, you can come back to the front end where your API call will now be met with the response from Cloudinary. I made the API call to just get back the secure_url for the uploaded file, which is why I sent back data.secure_url.

And that's it. A simple way to access your files with a RESTful API in Next.js!

Top comments (0)