Upload Images to Cloudinary with Node.js and React

Ifeoma Imoh

The use of multimedia on the web has gone mainstream primarily because of obvious benefits such as increased user engagement, dwell time, improved SEO, etc. Getting it right with media assets on the web entails dealing with optimization, delivery, transformation, and other management tasks. Fortunately, technologies like Cloudinary offer scalable, performant, and easy-to-use tools for practically all media management concerns.

In this post, we will upload files from a simple React app using a Cloudinary backend SDK on a Node.js web server.

Here is a link to the demo on CodeSandbox.

Project Setup

Run this command in your terminal to create the working directory:

1mkdir cloudinary-react-node

We'll also need two subdirectories within the cloudinary-react-node directory. Run this command to create them:

1cd cloudinary-react-node
2mkdir frontend server

Our React app, and its dependencies, will be kept in the frontend folder, while our backend code will be in the server folder.

Let's initialize the working directory to be able to install the necessary dependencies that will be used for our backend. Run this command:

1npm init --y

Next, run the following command in your terminal to install the dependencies for our backend:

1npm install express multer cloudinary cors dotenv

The above command installs several modules: express will be used to build our web server, all the necessary routes, and handling middleware. multer is a middleware used to parse multipart form data, which includes files from the HTTP request body. The cloudinary SDK will provide us with easy-to-use APIs to upload parsed files to our Cloudinary account and do any media file manipulations as the need be. The cors module is also another middleware that would help us with CORS errors, and finally, the dotenv module will allow us to parse environment variables defined in a .env file.

Run this command to create a file within the server folder, where we will write our serverside logic:

1touch server/index.js

Now let's run the following command to set up our React app in the frontend folder created earlier:

1npx create-react-app frontend

Run this command to install the dependency for our React app — the axios module, which will be our HTTP client:

1cd frontend
2npm install axios

We are not done with our setup yet. We need a way to automate the task of starting our React app and our web server concurrently. Within the working directory, run this command in your terminal to install the concurrently module:

1npm i concurrently --save-dev

Next, update the scripts object within the package.json file in the root of the working directory to match the following:

1"scripts": {
2 "backend": "node server/index.js",
3 "frontend": "npm run start --prefix frontend",
4 "dev": "concurrently \"npm run backend\" \"npm run frontend\""
5 },

Here, we included three scripts, the first two are for starting our Node server and our React app, respectively, and the third uses the concurrently module to start both of them concurrently.

Setting up Cloudinary

To use Cloudinary's provisioned services, you need to first sign up for a free Cloudinary account if you don’t have one already. Displayed on your account’s Management Console (aka Dashboard) are important details: your cloud name, API key, etc.

Next, let’s create environment variables to hold the details of our Cloudinary account. Create a new file called .env at the root of your project and add the following to it:

1CLOUD_NAME = YOUR CLOUD NAME HERE
2 API_KEY = YOUR API API KEY
3 API_SECRET = YOUR API API SECRET

You can update the .env file with your Cloudinary credentials.

Setting up the Server and Defining the Upload Endpoint

In the following steps, we will update our server.js file with the logic needed to set up our web server and include the endpoint that will enable us to upload files to Cloudinary.

  1. Let's start by including the necessary imports we will be needing. Add the following to your server.js file:
1require("dotenv").config();
2const express = require("express");
3const cloudinary = require("cloudinary").v2;
4const cors = require("cors");
5const Multer = require("multer");
  1. Next, let's set up our Cloudinary SDK and define a function that will help us with file uploads.
1cloudinary.config({
2 cloud_name: process.env.CLOUD_NAME,
3 api_key: process.env.API_KEY,
4 api_secret: process.env.API_SECRET,
5});
6async function handleUpload(file) {
7 const res = await cloudinary.uploader.upload(file, {
8 resource_type: "auto",
9 });
10 return res;
11}

In the code above, we import Cloudinary and configure it with an object consisting of our Cloudinary credentials. Next, we define the handleUpload function, which accepts a file and attempts to upload the file. The upload is made possible by calling the upload method on the cloudinary object, and it accepts two parameters. The first is the file to be uploaded, and the second is an object that holds the options we want for the uploaded file.

When we do uploads from the server, the upload call assumes that the file is an image, i.e., resource_type: "image", but setting the resource_type to auto automatically detects the file type during the upload process. Once the file is successfully uploaded, the response we get from the API is returned.

  1. Next, let's set up the multer middleware.
1const storage = new Multer.memoryStorage();
2const upload = Multer({
3 storage,
4});

Multer provides us with two storage options: disk and memory storage. In the snippet above, we create and initialize a Multer instance and select the memory storage option.

Now, let's use the Express module to create a web server to listen for requests. We will also create an endpoint to handle file uploads.

1const app = express();
2app.use(cors());
3
4app.post("/upload", upload.single("my_file"), async (req, res) => {
5 try {
6 const b64 = Buffer.from(req.file.buffer).toString("base64");
7 let dataURI = "data:" + req.file.mimetype + ";base64," + b64;
8 const cldRes = await handleUpload(dataURI);
9 res.json(cldRes);
10 } catch (error) {
11 console.log(error);
12 res.send({
13 message: error.message,
14 });
15 }
16});
17const port = 6000;
18app.listen(port, () => {
19 console.log(`Server Listening on ${port}`);
20});

We create an instance of express and store it in a variable called app; this is followed by including the cors middleware. Then we create an endpoint that will route any POST requests made to the /upload path, with two middlewares to handle the request.

The first is the multer middleware we created earlier. The upload.single("my_file") call returns a function that is set up to parse a single file contained in the request body with a named attribute — my file (this is the name we would bind to the file before it is uploaded on the client-side). Once the file is parsed successfully, the multer middleware attaches a file property to the request body, i.e., req.file, which contains information about the file. Since we are using the memoryStorage option to store our file, the object looks like this for some images:

The Second middleware then takes the request body, converts the buffer in the parsed file to base64, and further transforms this representation to a data URI, which is then fed to the handleUpload function to upload. Once it succeeds, we send the upload response we get from cloudinary back to the client-side, and if it fails, the error response is sent back.

Building the Frontend

We now have our web server ready. Next, let's build our frontend application that will communicate with it. Open the frontend folder and update the content of the src/App.js file with the following:

1import "./index.css";
2import { useState } from "react";
3import axios from "axios";
4
5function App() {
6 const [file, setFile] = useState(null);
7 const [loading, setLoading] = useState(false);
8 const [res, setRes] = useState({});
9 const handleSelectFile = (e) => setFile(e.target.files[0]);
10 const handleUpload = async () => {
11 try {
12 setLoading(true);
13 const data = new FormData();
14 data.append("my_file", file);
15 const res = await axios.post("http://localhost:6000/upload", data);
16 setRes(res.data);
17 } catch (error) {
18 alert(error.message);
19 } finally {
20 setLoading(false);
21 }
22 };
23
24 return (
25 <div className="App">
26 <label htmlFor="file" className="btn-grey">
27 {" "}
28 select file
29 </label>
30 {file && <center> {file.name}</center>}
31 <input
32 id="file"
33 type="file"
34 onChange={handleSelectFile}
35 multiple={false}
36 />
37 <code>
38 {Object.keys(res).length > 0
39 ? Object.keys(res).map((key) => (
40 <p className="output-item" key={key}>
41 <span>{key}:</span>
42 <span>
43 {typeof res[key] === "object" ? "object" : res[key]}
44 </span>
45 </p>
46 ))
47 : null}
48 </code>
49 {file && (
50 <>
51 <button onClick={handleUpload} className="btn-green">
52 {loading ? "uploading..." : "upload to cloudinary"}
53 </button>
54 </>
55 )}
56 </div>
57 );
58}
59export default App;

This component is quite straightforward; it simply allows the user to select a file from their computer, provides them with a button to upload the file, and displays the result received from the server on the screen if the upload is successful. The centerpiece of this component is the function called handleUpload, which performs file uploading through the following steps:

  1. It embeds the file selected by the user into the request payload using the FormData API with a name attribute called my_file — a string that matches the one we configured on our server using the Multer middleware.

  2. The payload is then used to initiate an HTTP request to our backend. If the request is successful, the response is stored in a state variable called res, which is iterated on and rendered to the screen; otherwise, an error message is displayed.

We'll also need some styles to give the application a nice appearance. Copy the styles in this codeSandbox link to your index.css file.

To preview the running application, let's start our web server and React app. Run this command in your terminal:

1npm run dev

As seen above, we get the upload response displayed on the screen. You can also visit the media gallery section on your cloudinary account to see the uploaded file.

Find the complete project here on GitHub.

Conclusion

When working with media assets, platforms like Cloudinary provide you with an advantage. Hopefully, using this as a jumping-off point, you can go further and explore the platform's advanced features.

Ifeoma Imoh

Software Developer

Ifeoma is a software developer and technical content creator in love with all things JavaScript.