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

franciscomendes10866

Francisco Mendes

Posted on June 28, 2021

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 πŸ‘‹

πŸ’– πŸ’ͺ πŸ™… 🚩
franciscomendes10866
Francisco Mendes

Posted on June 28, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related