Re-create an image from binary in Node.js and Mongoose

Viewed 2572

I'm working on creating an upload/download images feature in Node.js. So far there is the POST request which saves the image as binary in MongoDB and also a GET request which returns that image back. But I don't know how can I use that response, it is an array of numbers and don't know how to transform it.

Here is the Mongo Model:

image.js const mongoose = require('mongoose'); const Schema = mongoose.Schema;

const ImageItem = new Schema({
  id: {
    type: String
  },
  value: {
    type: Buffer
  },
});

module.exports = Image = mongoose.model('image', ImageItem);

the POST image which creates an entry in DB:

const image = require('../models/image');
const user = require('../models/user');

const multer = require('multer');
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './uploads/');
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname + new Date().toISOString());
  },
});


const upload = multer({
  storage: storage,
});

module.exports = function (app) {
  app.post('/upload', upload.single('value'), (req, res, next) => {
    const newImage = new image({
      id: req.body.id,
      value: req.file.path,
    });
    newImage
      .save()
      .then((result) => {
        console.log(result);
        res.status(201).json({
          message: 'created succesfully',
        });
      })
      .catch((err) => {
        console.log(err);
        res.status(500).json({
          error: err,
        });
      });
  });
};

and the entry created in DB:

enter image description here

for getting the image I created a GET request:

const image = require('../models/image');

module.exports = function (app) {
  app.get('/upload/:id', (req, res) => {
    console.log('req.body.id', req.params);
    image.find({ id: req.params.id }, function (err, results) {
      if (err) {
        res.send(`error: ${err}`);
      } else {
        res.send(results);
      }
    });
  });
};

which tested in Postman returns a JSON containing an array of numbers:

[
    {
        "_id": "5ebd1c112892f4230d2d4ab4",
        "id": "email123@test.com",
        "value": {
            "type": "Buffer",
            "data": [
                117,
                112,
                108,
                111,
                97,
                100,
                115,
                47,
                117,
                115,
                101,
                114,
                80,
                105,
                99,
                116,
                117,
                114,
                101,
                46,
                112,
                110,
                103,
                50,
                48,
                50,
                48,
                45,
                48,
                53,
                45,
                49,
                52,
                84,
                49,
                48,
                58,
                50,
                51,
                58,
                49,
                51,
                46,
                57,
                51,
                52,
                90
            ]
        },
        "__v": 0
    }
]

How can I use this data to get the actual image?

2 Answers

You can create a Buffer from that array.

const imageBuffer = Buffer.from(row.value.data); // [117, 112, 108...]

In any case checking your ImageItem schema, row.value is going to be a Buffer.

Now all you need to do is set the correct content-type and respond the Buffer instead of the Mongoose schema, using res.send.

app.get('/upload/:id', (req, res) => {
    console.log('req.body.id', req.params);
    image.find({ id: req.params.id }, function (err, results) {
      if (err) {
        res.send(`error: ${err}`);
      } else {
        const [row] = results;
        res.header('Content-Type', 'image/png');
        res.send(row.value);
      }
    });
});

If you don't know the Content-Type you can use file-type package to get it from the Buffer.

const { mime } = fileType(row.value)

Since you're only getting a particular image you may want to use .findOne instead of .find


Now you have other issue, you're storing the file path, instead of the binary image which is what you want.

Those bytes you posted equals: uploads/userPicture.png2020-05-14T10:23:13.934Z"

const data = new TextDecoder().decode(new Uint8Array([117,112,108,111,97,100,115,47,117,115,101,114,80,105,99,116,117,114,101,46,112,110,103,50,48,50,48,45,48,53,45,49,52,84,49,48,58,50,51,58,49,51,46,57,51,52]))

console.log(data);

You have to save the actual image, not the file path for your code to work.

const fs = require('fs').promises;
// ....

const newImage = new image({
      id: req.body.id,
      value: await fs.readFile(req.file.path)
});

When you send your image to a client, mongoose calls the method "toJSON", by default the Buffer JSON object looks like you showed. You could overwrite the toJSON method for the mongoose scheme (here you find info - https://mongoosejs.com/docs/guide.html#toJSON) and your server will return a base64 representation of you image instead.

const mongoose = require("mongoose"),
  Schema = mongoose.Schema;

var imageSchema = new Schema({
  data: {
    type: Buffer,
    required: true,
  },
  type: {
    type: String,
    required: true,
  },
});

imageSchema.set("toJSON", {
  virtuals: true,
  versionKey: false,
  transform: function (doc, ret) {
    const base64 = doc.data.toString("base64");
    ret.data = `data:image${doc.type};base64,${base64}`;

    return ret;
  },
});

module.exports = mongoose.model("Image", imageSchema);

Related