0

I am relatively new to nodejs. I am trying to save an image provided in a url (from api). After some online research, I decided to use createWriteStream method from nodejs to extract the image from url and save into the local directory. But I would like to be able to save directly to the mongodb. I am not able to figure out how to change the destination to a collection in mongodb.

My code snippet looks like below:

const fs = require('fs');
const request = require('request');
const Image = require('./models/Image');

const connectDB = require('./db');

connectDB();

const imageName = 'photo-' + Date.now();
const ext = 'jpg';


const url = 'https://some_path_for_image_by_api';

const getData = async (u, cb) => {
  const myImage = await request(url).pipe(
    fs.createWriteStream(`${imageName}.${ext}`)
  );
  cb(myImage);
};

const saveData = async (d) => {
  const image = await new Image(d);

  image.save(function (err, data) {
    if (err) return console.error(err);
    else return console.log('Image saved successfully', data);
  });
};

getData(url, saveData);

Here is my Image schema definition:

const mongoose = require('mongoose');

const ImageSchema = new mongoose.Schema({
  createdAt: {
    type: Date,
    default: Date.now,
  },
  img: {
    data: Buffer,
    contentType: String,
  },

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

With this code, the image is saved from a url to my root directory in the folder (not desired). The image document is created but just with ObjectId and version, not actual data. The goal is to be able to save the image retrieved from a url directly to mongodb as a document while not saving anything to the root directory. Maybe I am not doing the right way. How do I save it to the db instead of being saved to some folders in the directory or to the root directory?Any help and suggestion will be much appreciated.

Wrisper
  • 1
  • 1
  • Does this answer your question? [How to use GridFS to store images using Node.js and Mongoose](https://stackoverflow.com/questions/8135718/how-to-use-gridfs-to-store-images-using-node-js-and-mongoose) – Kunal Mukherjee Oct 10 '20 at 14:39
  • Can you share your Image model code? – MattM Oct 10 '20 at 17:34
  • @MattM, ```const mongoose = require('mongoose'); const ImageSchema = new mongoose.Schema({ createdAt: { type: Date, default: Date.now, }, }); module.exports = mongoose.model('Image', ImageSchema); ``` – Wrisper Oct 10 '20 at 18:16
  • @KunalMukherjee, not quite. Thank you though. – Wrisper Oct 10 '20 at 18:19

1 Answers1

0

Looks like the only field in your model is createdAt, so the image data itself wouldn't be saved. In order to save the image data, you'd need to add an appropriate field to the model.

From Mongoose docs:

The strict option, (enabled by default), ensures that values passed to our model constructor that were not specified in our schema do not get saved to the db.

https://mongoosejs.com/docs/guide.html#strict

Edit since posting schema: Try this.

const ImageSchema = new mongoose.Schema({
  createdAt: {
    type: Date,
    default: Date.now(),
  },
  img: {
    type: Buffer
  }
});

Then, you need to set that field by name in your model. Something like:

const image = new Image({ img: d });

where the field name is buffer.

I'm not familiar with fs so I can't help much there, but this assumes that d contains a byte array. If it's not currently a byte array, I think the next step would be converting it to one so it can be saved to MongoDB. This answer may help with that part:

https://stackoverflow.com/a/31217387/1520071

MattM
  • 1,159
  • 1
  • 13
  • 25
  • Even if a add a field to the schema as { image: { type: Buffer } } How do I save to the db when I trigger the function? Currently, with just adding that field it is not saving that image to the db. – Wrisper Oct 10 '20 at 21:55
  • Can you update your code in your question so this change is reflected? Otherwise I can't be sure exactly what's happening. With the current code, you still need to set the field on the model to the image buffer. Not sure if you did that already. I'll update my answer to be more explicit about this. – MattM Oct 10 '20 at 22:56
  • Just updated the code with image schema and a little more clarification on the question – Wrisper Oct 10 '20 at 23:11
  • Nice, looks good. I think you need to change the model a bit still. Try `img: Buffer`. I'll update my answer again. Edit: I noticed your createdAt's default might've been set improperly due to missing parentheses. I added those to my suggestion too. That should at least fix `createdAt` not getting populated. – MattM Oct 10 '20 at 23:21
  • Getting this: { img: { CastError: Cast to Buffer failed for value "WriteStream { ......| messageFormat: undefined, kind: 'Buffer', value: [WriteStream], path: 'img', reason: null } }, _message: 'Image validation failed' } – Wrisper Oct 10 '20 at 23:37
  • Okay cool, good progress. So now it's down to just passing the stream to MongoDB. In the answer I linked to above, I noticed there are other examples using fs.readFile instead of fs.createWriteStream. Can you give that a shot? The goal is to get the data into a byte array to save into MongoDB. Worth noting, I'm not very familiar with the `fs` functionality, so I'm just pointing to other examples at this point. – MattM Oct 10 '20 at 23:56
  • Getting this error after changing to fs.readFile: UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_CALLBACK]: Callback must be a function – Wrisper Oct 11 '20 at 03:44
  • Keep at it, I think you're getting close, just need to spend a little more time on the details and the error message. Again, it's difficult to help at this stage because I'm not seeing your code changes and I'm also not familiar with `fs`. But, you should also try to debug this error. One thing I noticed is that, in the answer I linked to, they first saved the image data locally before they could use readFile on it (it's not coming from a URL that way). But, the error you're getting doesn't quite seem related to that. – MattM Oct 11 '20 at 17:38