DEV Community

Cover image for How to compress and upload an image to Cloudinary using Node.js
Francisco Mendes
Francisco Mendes

Posted on

How to compress and upload an image to Cloudinary using Node.js

In the past, I had already written two articles related to this topic. One was how to upload images to Cloudinary and the other was how to compress images with Node.js.

Today I decided to combine the knowledge from those articles. That is, I decided to compress the images and upload them to Cloudinary.

In this way, they will only spend credits on the space of the images and not on their compression and transformation.

Despite explaining step by step what we are going to do today, I recommend you go read the articles I mentioned.

Now with the introduction done, let's code!

Let's code

First let's install the necessary dependencies:

npm install express multer cloudinary sharp
Enter fullscreen mode Exit fullscreen mode

Now we need a basic API:

const express = express();

const app = express();

app.get("/", (req, res) => {
  return res.json({ message: "Hello world 🇵🇹" });
});

const start = () => {
  try {
    app.listen(3333);
  } catch (error) {
    console.error(error);
    process.exit();
  }
};
start();
Enter fullscreen mode Exit fullscreen mode

After that we will configure multer and use MemoryStorage:

const express = express();
const multer = require("multer");

const app = express();
const storage = multer.memoryStorage();
const upload = multer({ storage });

app.get("/", (req, res) => {
  return res.json({ message: "Hello world 🇵🇹" });
});

const start = () => {
  try {
    app.listen(3333);
  } catch (error) {
    console.error(error);
    process.exit();
  }
};
start();
Enter fullscreen mode Exit fullscreen mode

Next, let's configure Cloudinary using its SDK:

const express = express();
const multer = require("multer");
const cloudinary = require("cloudinary").v2;

const app = express();
const storage = multer.memoryStorage();
const upload = multer({ storage });

cloudinary.config({
  cloud_name: "YOUR_CLOUD_NAME",
  api_key: "YOUR_API_KEY",
  api_secret: "YOUR_API_SECRET",
});

app.get("/", (req, res) => {
  return res.json({ message: "Hello world 🇵🇹" });
});

const start = () => {
  try {
    app.listen(3333);
  } catch (error) {
    console.error(error);
    process.exit();
  }
};
start();
Enter fullscreen mode Exit fullscreen mode

From this point on, things will be different from other articles that have been written on this topic (written by me).

Regarding the image upload, we will send to Cloudinary the final result buffer we have after the transformations done with the sharp module.

For that, let's create a function that will read the data from the buffer of the image that we'll pass as argument. And later the data will be returned as a buffer object.

The function we are going to create is as follows:

const { Readable } = require("stream");

// Hidden for simplicity

const bufferToStream = (buffer) => {
  const readable = new Readable({
    read() {
      this.push(buffer);
      this.push(null);
    },
  });
  return readable;
}
Enter fullscreen mode Exit fullscreen mode

Now we can proceed to the creation of the endpoint that we are going to use to upload the respective image.

Once created we will add the multer to our endpoint so we can have access to the image data. And we're going to name the field "picture".

app.post("/", upload.single("picture"), async (req, res) => {
  // Logic goes here
});
Enter fullscreen mode Exit fullscreen mode

Now with the endpoint created, let's start working on transforming the image.

In this example I will just convert the image to webp format and decrease its quality (to 20%). Then I'll get the final result as a buffer. In this way:

app.post("/", upload.single("picture"), async (req, res) => {
  const data = await sharp(req.file.buffer).webp({ quality: 20 }).toBuffer();
  // Even more logic goes here
});
Enter fullscreen mode Exit fullscreen mode

Now we can start dealing with the configuration of sending our (buffered) image. So we will use the .upload_stream() method (because we will be uploading a data stream). Then we'll define our destination folder (which I named "DEV").

And finally we will have a callback with two arguments, the first is the error and the second is the result. If an error occurs, we will log the error in the terminal. If we were successful, we will return a response with the image link.

Like this:

app.post("/", upload.single("picture"), async (req, res) => {
  const data = await sharp(req.file.buffer).webp({ quality: 20 }).toBuffer();
  const stream = cloudinary.uploader.upload_stream(
    { folder: "DEV" },
    (error, result) => {
      if (error) return console.error(error);
      return res.json({ URL: result.secure_url });
    }
  );
  // Almost done
});
Enter fullscreen mode Exit fullscreen mode

We already have the transformed image buffer and the stream configuration that we are going to do. Now we just grab the image and send it to Cloudinary. For that we will use the .pipe() method in our bufferToStream function.

That is, in our readable stream we will pass our transformed image buffer as the only argument. And in the pipe method we will pass our stream (destination) as the only argument.

Like this:

app.post("/", upload.single("picture"), async (req, res) => {
  const data = await sharp(req.file.buffer).webp({ quality: 20 }).toBuffer();
  const stream = cloudinary.uploader.upload_stream(
    { folder: "DEV" },
    (error, result) => {
      if (error) return console.error(error);
      return res.json({ URL: result.secure_url });
    }
  );
  bufferToStream(data).pipe(stream);
});
Enter fullscreen mode Exit fullscreen mode

The final code should look like this:

const express = require("express");
const multer = require("multer");
const sharp = require("sharp");
const cloudinary = require("cloudinary").v2;
const { Readable } = require("stream");

const app = express();
const storage = multer.memoryStorage();
const upload = multer({ storage });

cloudinary.config({
  cloud_name: "YOUR_CLOUD_NAME",
  api_key: "YOUR_API_KEY",
  api_secret: "YOUR_API_SECRET",
});

const bufferToStream = (buffer) => {
  const readable = new Readable({
    read() {
      this.push(buffer);
      this.push(null);
    },
  });
  return readable;
};

app.get("/", (req, res) => {
  return res.json({ message: "Hello world 🔥🇵🇹" });
});

app.post("/", upload.single("picture"), async (req, res) => {
  const data = await sharp(req.file.buffer).webp({ quality: 20 }).toBuffer();
  const stream = cloudinary.uploader.upload_stream(
    { folder: "DEV" },
    (error, result) => {
      if (error) return console.error(error);
      return res.json({ URL: result.secure_url });
    }
  );
  bufferToStream(data).pipe(stream);
});

const start = () => {
  try {
    app.listen(3333);
  } catch (error) {
    console.error(error);
    process.exit();
  }
};
start();
Enter fullscreen mode Exit fullscreen mode

Have a great day!

I hope it helped you 👋

Top comments (8)

Collapse
 
davc93 profile image
Diego Vergara

Thanks it was useful.👌

Collapse
 
a14313 profile image
Carlo Autor

This is very useful. Do you have for s3 bucket?

Collapse
 
harshitrv profile image
Harshit Kr Vishwakarma

Awesome it helps me greatly.

Collapse
 
nurullah7733 profile image
Hasan Md Nurullah

Awesome but how to upload multiple image?

Collapse
 
etanol17 profile image
etanol17

how do i take the result and save it to database with mongoose?

Collapse
 
jsoftwares profile image
Jeffrey Onochie • Edited

I think the easiest way would be to take the URL returned by result.secure_url in the code below, then you can call an update on your appropriate model to add the URL.
const stream = cloudinary.uploader.upload_stream(
{ folder: "DEV" },
async (error, result) => {
if (error) return console.error(error);
await Model.findOneAndUpdate({filter}, {image: result.secure_url});
return res.json({ URL: result.secure_url });
}
);

Collapse
 
abdulvahidkp profile image
abdulvahidkp

Did you got the answer

Collapse
 
femil profile image
Femil Savaliya • Edited

Thank You for this amazing article 👏👏
I have uploaded img in png format in cloudanary but i want webp format img while fetching it in node js(strapi) so how can i achive that ?