Sending Files to Cloudinary Over REST API with Next.js
Tags: nextjs, tutorial, javascript, api
Published: 2023-09-26 19:08:47

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>
  );
}
```
The first thing I will bring in to easily manage the form data is [React Hook Form](https://react-hook-form.com/). 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
```

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

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

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
```
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)
};
```
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](https://nextjs.org/docs/pages/building-your-application/routing/api-routes).

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.');
}
```
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](https://www.npmjs.com/package/formidable).

```
npm install formidable
```
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.');
}
```
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
```
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,
});
```
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](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables).

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](https://cloudinary.com/documentation/upload_images#unsigned_upload).
 
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);
  }
```
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!