DEV Community

Tomer Raitz
Tomer Raitz

Posted on

How to upload and customize images using sharp in Node.js

Alt Text
If you don't know "Sharp" - you should, It's an amazing package that lets you customize images before uploading it to the FTP. if you don't need to customize the image you can just use "fs" (but keep reading if you want to know more about this package).

So why I need to use customize image packages at all? For me, I didn't have a choice, because I got the wrong orientation of the image And needed to turn it to the regular side. This is one example of many, If you are interested to know how to implements "sharp" Keep reading.

Step 1: installation of all the npm packages

npm i sharp
you can read more in the npm site: sharp-npm

npm i express
you can read more in the npm site: express-npm

Step 2: Create index.js file (the main server file)

I must admit I Love OOP, I know javascript it's not OOP language, but for me, it's more organized, this is my index.js:

"use strict"

const Api = require('./express/routes/Api');
class Server {
  constructor() {
    this.express = require('express');
    this.app = this.express();
    this.path = require('path');
    this.apiRouters = this.express.Router();
    this.api = {};
    this.port = 0;
    this.bodyParser = require('body-parser');
  }
  /**
   * startExpressConfig - start Express Config
   */
  startExpressConfig() {
    this.app.use(this.bodyParser.urlencoded({
      extended: false,
      limit: '50mb'
    }));
    this.app.use(this.bodyParser.json({
      limit: '50mb'
    }));
    process.env.PORT ? this.port = process.env.PORT : this.port = 8000 //! process.env.PORT - production
  }
  /**
   * errorMiddleware - print error (in server and client regarding he api)
   */
  errorMiddleware() {
    this.app.use(function (err, req, res, next) {
      if (err.message === "Cannot read property 'catch' of undefined") { //! if user didn't found
        let errorMessage = `Got wrong with the request, please check the req.body`
        console.error(`client send incurrent request at : `, req.body)
        res.status(422).send({
          errorMessage
        })
      } else {
        console.error(`${err.message}`)
        res.status(422).send({
          error: err.message
        })
      }
    })
  }
  /**
   * activeApi - Open api routes
   */
  activeApi() {
    this.api = new Api(this.apiRouters,  this.path);
    this.app.use('/', this.apiRouters);
    // error middleware
    this.errorMiddleware()
    this.api.uploadImage();
    this.api.getImage();
  }
  /**
   * addAppLister - Active server port
   */
  addAppLister() {
    this.app.listen(this.port, () => {
      console.log(`Running on port ${this.port}`)
    })
  }
  /**
   * activateServer - Active all index methods
   */
  activateServer() {
    this.startExpressConfig();
    this.activeApi();
    this.addAppLister();
  }
}
const server = new Server();
server.activateServer();
Enter fullscreen mode Exit fullscreen mode

Step 2: Creating The API module

Create Api.js file (at my hierarchy folder it's under /express/routes/):

"use strict"

const ImageUpload = require('../../image_module/ImageController');

class Api {
    constructor(router, path) {
        this.router = router;
        this.path = path;
        this.imageUpload = new ImageUpload(path);
    }
    /**
     * getImage - get Image from server 
     * ! Input - imageName - the name og the image, request looks like http://localhost:8000/images?imageName=image_1586948956767.jpg
     * ! Output - the image
     * TODO : make valid get image request
     */
    getImage() {
        this.router.get('/images', (req, res, next) => {
            let name = req.query.imageName
            const path = this.path.join(__dirname, '../../images/')
            res.status(200).sendFile(`${path}${name}`);
        })
    }
    /**
     * uploadImage - upload image to server
     * ! Input -  request body looks like {"base64":"/9j/....","height":960,"width":1280,"pictureOrientation":1,"deviceOrientation":1}
     * ! Output - the image name
     */
    uploadImage() {
        this.router.post('/upload/image', async (req, res, next) => {
            const imageName = `image_${Date.now()}.jpg`
            let answer = await this.imageUpload.addImage(req, imageName)
            if (answer === "O.K") {
                await res.status(200).send(imageName);
            } else {
                console.error(`${answer}`)
                await res.status(422).send({
                    error: answer
                })
            }
            gc();
            return;
        })
    }
}
module.exports = Api
Enter fullscreen mode Exit fullscreen mode

Let's focus on the uploadImage method:

  • The imageName must be a unique name.
  • The addImage return if the image was uploaded (you will see on the next steps)

Step 3: Build the Image upload module

First, let's set the basic class variables by creating the constructor:

    constructor(path) {
        this.path = path;
        this.sharp = require("sharp")
        this.authoriseFilles = {
            R0lGODdh: "image/gif",
            R0lGODlh: "image/gif",
            iVBORw0KGgo: "image/png",
            "/9j/": "image/jpg",
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • The authoriseFilles it's the list of files that authorized to upload to the server FTP.

Now, we need to create the method that checks if the file is valid, like this:

    /**
     * detectMimeType - check if the file is authorized (only images)
     * @param {sting} base64 - base64 string encoding
     */
    detectMimeType(base64) {
        let answer = ""
        for (let string in this.authoriseFilles) {
            if (base64.indexOf(string) === 0) {
                answer = "O.K";
            }
        }!answer ? answer = "not vaild fille" : null;
        return answer;
    }
Enter fullscreen mode Exit fullscreen mode

Let's create the readFille method:

    /**
     * 
     * @param {string} path - image path and name
     * @param {Buffer} fille - Buffer fille to upload to server
     * @param {number} imageOrientation - image Orientation : check if the orientation is correct
     */
    async readFile(path, fille, imageOrientation) {
        gc();
        this.sharp.cache(false)
        let data = await this.sharp(fille).metadata()
        if (data.orientation !== imageOrientation) {
            await this.sharp(fille).rotate(360).resize(data.width).toFile(path);
        } else {
            await this.sharp(fille).toFile(path);
        }
        gc();
        return
    }
Enter fullscreen mode Exit fullscreen mode
  • The this.sharp.cache(false) for disabling the sharp cache (it helps with problems regarding memory leak)
  • await this.sharp(fille).metadata() get object data on the file - with it we can check the orientation
  • If the orientation isn't correct, sharp will rotate it to the right orientation and upload it to the path (toFile(path))

Now, let's build the controller method:

    /**
     * addImage - main function of this module
     * @param {object} req - the requrest object
     * @param {sting} imageNmae - the image name
     */
    async addImage(req, imageNmae) {
        let answer = await this.detectMimeType(req.body.base64);
        if (answer === "O.K") {
            const imgdata = JSON.stringify(req.body.base64);
            const buf = Buffer.from(imgdata, 'base64');
            const path = this.path.join(__dirname, '../images/') + imageNmae;
            this.readFile(path, buf, req.body.pictureOrientation)
        }
        return answer;
    }
Enter fullscreen mode Exit fullscreen mode

Conclusion

The "rotate image" it's only one example of what sharp can do. There is a lot of other packages for customizing images, I tried to use jimp but it caused me a memory leak (maybe I didn't use it right, I'm not sure). The bottom line it's important for you to know about this option.

If you want to see the all code you can see it (and clone it if you want) in this GitHub repository: image-uploader

If you found this article useful, consider ❤️ hearting, 🦄 unicorning and 🔖 bookmarking it on DEV.to. It helps more than you know.

Top comments (2)

Collapse
 
avivberger profile image
avivberger

Great article!
Reccomended, thanks

Collapse
 
shadoath profile image
Skylar Bolton

Fille or File?