Image Compression with Node.js
Tags: javascript, node, backend
Published: 2021-05-15 12:07:09

Currently, I feel that the market is increasingly oriented towards the use of third-party services to make part of our pipeline. One of the most common is to compress images.

However, I will show that with a simple [Express.js](https://expressjs.com/) api we can upload an image, change the Mime Type and still decrease its size.

I don't think I need to talk about Express.js because everyone who uses Node.js has used this framework at some point. Today I'm going to focus on [multer](https://expressjs.com/en/resources/middleware/multer.html) and [sharp](https://sharp.pixelplumbing.com/).

Nowadays uploading images is one of the most basic operations of all applications. Multer is a middleware for handling multipart/form-data that is mainly used to upload files. By combining Express.js with multer, we will be able to easily implement the file upload feature.

Sharp is a module for Node.js to convert images of the most diverse formats and varied dimensions to a smaller size, without having to worry about the color space, channels and alpha transparency, because all of these are treated correctly.

The image format that we are going to use in this example is [WebP](https://developers.google.com/speed/webp/), which offers compression greater than png and jpg, which helps to load web pages more quickly. And it is supported by all browsers.

## How to use

First we will create a new project in Node.js and then we will install the necessary dependencies:
```
# We will use the default values.
npm init -y

# Necessary dependencies.
npm install express multer sharp
```

After that we will create a simple api:
```js
const express = require("express");
 
const app = express();
 
app.get("/", (req, res) => {
 return res.json({ message: "Hello world 🔥🇵🇹" });
});
 
app.listen(3000);
```

Once the application is working properly, we will move on to configuring multer. If you visited the multer documentation, I believe you saw that we can save the images in two ways: DiskStorage or MemoryStorage.

In this case, we will use MemoryStorage, because we want to have access to the buffer that is made available by multer. And we will also use the static function of Express.js so that later we can serve our images.

```js
const express = require("express");
const multer = require("multer");
 
const app = express();
const storage = multer.memoryStorage();
const upload = multer({ storage });
 
app.use(express.static("./uploads"));
 
app.get("/", (req, res) => {
 return res.json({ message: "Hello world 🔥🇵🇹" });
});
 
app.listen(3000);
```

After that, we will create an endpoint to make the http request with the POST verb, however we will add the middleware upload and we will only upload a single file which we will call “picture”.

```js
app.post("/", upload.single("picture"), async (req, res) => {
 // The logic goes here.
});
```

The next step is to check if the "uploads" folder exists in our workspace, if it doesn't, we will want Node.js to create it for us. For that we need to have access to the file system, so we will use the [fs](https://nodejs.org/api/fs.html) module.

```js
app.post("/", upload.single("picture"), async (req, res) => {
 fs.access("./uploads", (error) => {
   if (error) {
     fs.mkdirSync("./uploads");
   }
 });
 // Even more logic goes here.
});
```

Now we can start working with sharp, but first I like to create a random string to have it before the name of the image, this is because several images can have the same name and so it is better to be careful. In this case I am using a timestamp (to make it easier to understand) but the ideal was to have a random string of 16 characters. Then we will access the **buffer** and the **originalname** of the image thanks to multer.

```js
app.post("/", upload.single("picture"), async (req, res) => {
 fs.access("./uploads", (error) => {
   if (error) {
     fs.mkdirSync("./uploads");
   }
 });
 const { buffer, originalname } = req.file;
 const timestamp = new Date().toISOString();
 const ref = `${timestamp}-${originalname}.webp`;
 // Even more logic goes here.
});
```

Now just pass the image buffer to sharp and then configure the desired quality, the format we want and where the file goes. In this case, I will want the file in the “uploads” folder with the name we have assigned (the **ref** variable).

```js
app.post("/", upload.single("picture"), async (req, res) => {
 fs.access("./uploads", (error) => {
   if (error) {
     fs.mkdirSync("./uploads");
   }
 });
 const { buffer, originalname } = req.file;
 const timestamp = new Date().toISOString();
 const ref = `${timestamp}-${originalname}.webp`;
 await sharp(buffer)
   .webp({ quality: 20 })
   .toFile("./uploads/" + ref);
 // Almost finished...
});
```

Last but not least, I will create a variable called **link** that will be the url with which we will be able to view our new image in the browser.

The final code should be as follows:

```js
const express = require("express");
const multer = require("multer");
const sharp = require("sharp");
const fs = require("fs");

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

app.use(express.static("./uploads"));

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

app.post("/", upload.single("picture"), async (req, res) => {
  fs.access("./uploads", (error) => {
    if (error) {
      fs.mkdirSync("./uploads");
    }
  });
  const { buffer, originalname } = req.file;
  const timestamp = new Date().toISOString();
  const ref = `${timestamp}-${originalname}.webp`;
  await sharp(buffer)
    .webp({ quality: 20 })
    .toFile("./uploads/" + ref);
  const link = `http://localhost:3000/${ref}`;
  return res.json({ link });
});

app.listen(3000);
```

Just use your favorite http client (in this case I used [Insomnia](https://insomnia.rest/products/insomnia)) and don't forget to send the image with multipart/form-data and the name of the field must be “picture” and the type is file.

Like this:

![screenshot](https://res.cloudinary.com/dj5iihhqv/image/upload/v1621080333/insomnia_h9y5by.png)

## What about you?

What is your favorite image format?