How to create mongoose model for GridFS collection? - node.js

So I am trying to create a mongoose model for GridFS collection but to no success.
let bucket;
(async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true });
const { db } = mongoose.connection;
bucket = new mongoose.mongo.GridFSBucket(db, { bucketName: 'tracks' });
}
catch(err) {
console.log(err);
}
})();
Here is my course schema and model:
const courseSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
tracks: [{
type: mongoose.Types.ObjectId,
ref: 'tracks.files'
}],
});
const Course = mongoose.model('Course', courseSchema);
And here is my tracks schema and model:
const trackSchema = new mongoose.Schema({
length: { type: Number },
chunkSize: { type: Number },
uploadDate: { type: Date },
filename: { type: String, trim: true, searchable: true },
md5: { type: String, trim: true, searchable: true },
}, { collection: 'tracks.files', id: false });
const Track = mongoose.model('Track', trackSchema);
And I am getting this error:
MongooseError [MissingSchemaError]: Schema hasn't been registered for model "tracks.files".
when I run this:
Course.findById('5d5ea99e54fb1b0a389db64a').populate('tracks').exec()
.then(test => console.log(test))
.catch(err => console.log(err));
There is absolutely zero documentation on these stuff and I am going insane over it. Am I the first person to ever save 16 MB+ files in Mongodb? Why is it so difficult to implement this? Can anyone please guide me to the right direction.

Late response but try replacing ref: 'tracks.files' with ref: 'trackSchema'.
The ref field is referenced within populate('trackSchema') and must be a reference to another Schema. Check saving-refs out if you want to learn more about populating fields and references in Mongoose.
I also wouldn't recommend creating schemas of any sort for implementing GridFS, I'd let Mongo take care of this as it may lead to file corruption or missing/outdated documents if implemented incorrectly.
As for the lack of official documentation for GridFS, especially GridFS with Mongoose, I used Mongo's native GridFsBucket class (as of my knowledge, no still maintained official Mongoose-GridFS API exist), the best docs I used when attempting this myself was gridfs. With a tutorial here streaming.
To use Mongo's native GridFSBucket with Mongoose just grab its mongo property and Mongoose's connection instance from its connection property.
const mongoose = require(mongoose);
var gridFSBucket = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
bucketName: 'images'
});

Related

Update multiple documents instance in MongoDb collection with Mongoose. save() is not a function

Mongoose newbe here. I got the following function to update the references (deleting them) in the document Post when a Tag is deleted. When I call my GraphQl API this is what I got:
message": "posts.save is not a function"
The function in my gql resolver:
async deleteTag(root, { id }, context) {
const posts = await Post.find();
const tag = await Tag.findById(id);
if(!tag){
const error = new Error('Tag not found!');
error.code = 404;
throw error;
}
posts?.forEach(async (post) => {
await post.tags.pull(id);
})
await posts.save()
await Tag.findByIdAndRemove(id);
return true;
}
This is the Post model:
const PostSchema = new Schema({
body: {
type: String,
required: true
},
tags: {
type: [Schema.Types.ObjectId],
ref: 'Tag',
required: false
},
});
and this is the Tag model:
const TagSchema = new Schema(
{
name: {
type: String,
required: true
},
},
{ timestamps: true }
);
Looks like I can't call the method save() on the array of objects returned by Exercise.find()
I used the same pattern in other functions, the difference is that there I used .findById()
Any solution? Advice and best practice advide are super welcome.
You have to save the posts individually:
posts?.forEach(async (post) => {
await post.tags.pull(id);
await post.save();
})
Or use Model.updateMany() combined with the $pull operator.
FWIW, you should probably limit the number of matching Post documents by selecting only documents that have the specific tag listed:
await Post.find({ 'tags._id' : id });

Node Mongoose - saving embedded documents on API once receiving mongoId

I would like to know the best approach to solve the current scenario.
I've got a node API which uses mongoose and bluebird. And some Android clients will post "movement" entities to it.
(Question at the end).
Let's say movement-model.js exports the Schema, and looks like this:
"use strict";
const mongoose = require('mongoose');
const _movementSchema = {
movementId: { type: Number, requried: true },
relMovementId: Number,
_party: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Party' }
}
module.exports = mongoose.Schema(_movementSchema, {collection: 'movement'});
And related exported Schema on party-model.js is as follows:
"use strict";
const mongoose = require('mongoose');
const _partySchema = {
active: { type: Boolean, default: true },
name: { type: String, trim: true, required: true },
logo: { type: Buffer },
coordenates: { lat: Number, long: Number },
startOn: { type: Date, required: true },
endOn: { type: Date, required: true }
}
module.exports = mongoose.Schema(_partySchema, {collection: 'party'});
Android client would send the JSON with ObjectId and not full populated object. So when the POST comes, I'm using it directly (i.e: let _movement = req.body;) and on the movement-dao.js I've got the createNew method and I'm exporting the Model:
"use strict";
const mongoose = require('mongoose');
const Promise = require('bluebird');
mongoose.Promise = Promise;
const movementSchema = require('../model/movement-model');
movementSchema.statics.createNew = (movement) => {
return new Promise((resolve, reject) => {
if (!_.isObject(movement)) {
return reject(new TypeError('Movement is not a valid object.'));
}
let _something = new Movement(movement);
_something.save((err, saved) => {
err ? reject(err)
: resolve(saved);
});
});
}
const Movement = mongoose.model('Movement', movementSchema);
module.exports = Movement;
What I want to accomplish is to: save the movement collection with the _party as the full party document is at the moment of the save, I mean an embedded document of a copy of the Party document, which will not be affected by the updates done to the Party document in the future.
While I cannot change the Android Client, so I will still be getting only the ObjectId from it.
JSON example of what Android client will post: {"movementId":1, "relMovementId":4138, "_party":"58dbfe26194cfc5a9ec9b7c5"}
I'm confused now, and not sure if due to the way Android is posting the JSON, I need two schemas; one for the object received (i.e: with ObjectId and ref to Party) and a second one for the object persisted (i.e: with the schema referenced _party: Party.Schema) or if I could do something simpler as some populate prior to save... or what.
For the sake of closing this up:
I've implemented one of the approaches I had in mind while writing the question. Movement schema changed so that: _party: Party.Schema
When I get a POST to create a new movement I do a getById and use the result of that exec to populate the value as an embedded doc.

How to get data from existing MongoDB database?

I have a database on mlab and now I was starting a new Project and trying to simply get data from there.
The Database has only one collection called Article.
On my Node js project, using Mongoose, I created the Model for it:
var mongoose = require('mongoose');
var articleSchema = new mongoose.Schema({
title: { type: String, required: true },
body: { type: String }
});
var Article = mongoose.model('Article', articleSchema);
module.exports = Article;
The in my controller I just did this:
Article.find({}, function (err, articles) {
res.send(articles);
});
I should receive more than 300 articles but the response is just an empty Array.
I was wondering if I need to run a few more command in order to connect to the db correctly, but I don't know it...
If you want to fetch on an existing Article collection:
var articleSchema = new mongoose.Schema({
title: { type: String, required: true },
body: { type: String }
}, { collection : 'Article' });

Storing a copy of a document embedded in another document in MongoDB via Mongoose

We have a requirement to store a copy of a Mongo document, as an embedded subdocument in another document. It should have a reference to the original document. The copied document needs to be a deep copy, like a snapshot of the original.
The original document's schema (defined with Mongoose) is not fixed -
it currently uses a type of inheritance to allow different additions to the Schema depending on "type".
Is there a way to such a flexible embedded schema within a Mongoose model?
Is it something that needs to be injected at runtime, when we can know
the schema?
The models / schemas we have currently look like this:
///UserList Schema: - this should contain a deep copy of a List
user: {
type: ObjectId,
ref: 'User'
},
list: {
/* Not sure if this is a how we should store the reference
type: ObjectId,
ref: 'List'
*/
listId: ObjectId,
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}]
}
///List Schema:
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}],
createdBy: {
type: ObjectId,
ref: 'User'
}
The code we currently have uses inheritance to allow different item types. I realise this technique may not be the best way to achieve the flexibility we require and is not the focus of my question.
///Item Model + Schema
var mongoose = require('mongoose'),
nodeutils = require('util'),
Schema = mongoose.Schema,
ObjectId = Schema.Types.ObjectId;
function ItemSchema() {
var self = this;
Schema.apply(this, arguments);
self.add({
question: {
type: String,
required: true
}
});
self.methods.toDiscriminator = function(type) {
var Item = mongoose.model('Item');
this.__proto__ = new Item.discriminators[type](this);
return this;
};
}
nodeutils.inherits(ItemSchema, Schema);
module.exports = ItemSchema;
I think you just need to create an empty {} object for the document in your parent mongoose schema. This way you´ll be able to store any object with a hardcopy of all it´s data.
parentobj : {
name: Sring,
nestedObj: {}
}
I think at this point, what you´ll need is to mark your nested objet as modified before you save it. Here is an example of my mongoose code.
exports.update = function(req, res) {
User.findById(req.params.id, function (err, eluser) {
if (err) { return handleError(res, err); }
if(!eluser) { return res.send(404); }
var updated = _.merge(eluser, req.body);
//This makes NESTEDDATA OBJECT to be saved
updated.markModified('nestedData');
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, eluser);
});
});
};
In addition, if you need an array of different documents in nestedDocument, the right way is this one:
parentobj : {
name: Sring,
nestedObjs: [Schema.Types.Mixed]
}
Please check Mongoose Schema Types carefully
EDIT
As you said, I´ll add you final solution as including ItemSchema in the nestedObj array definition to clarifythe type of the object to a determined one..
var ItemSchema = new Schema({
item1: String,
item2: String
});
var parentobj = new Schema({
name: Sring,
nestedObj: [ItemSchema]
});
EDIT 2:
Remember adding new Items to the nestedArray, must be done with nestedArray.push(item)
regards!!

Skip or Disable validation for mongoose model save() call

I'm looking to create a new Document that is saved to the MongoDB regardless of if it is valid. I just want to temporarily skip mongoose validation upon the model save call.
In my case of CSV import, some required fields are not included in the CSV file, especially the reference fields to the other document. Then, the mongoose validation required check is not passed for the following example:
var product = mongoose.model("Product", Schema({
name: {
type: String,
required: true
},
price: {
type: Number,
required: true,
default: 0
},
supplier: {
type: Schema.Types.ObjectId,
ref: "Supplier",
required: true,
default: {}
}
}));
var data = {
name: 'Test',
price: 99
}; // this may be array of documents either
product(data).save(function(err) {
if (err) throw err;
});
Is it possible to let Mongoose know to not execute validation in the save() call?
[Edit]
I alternatively tried Model.create(), but it invokes the validation process too.
This is supported since v4.4.2:
doc.save({ validateBeforeSave: false });
Though there may be a way to disable validation that I am not aware of one of your options is to use methods that do not use middleware (and hence no validation). One of these is insert which accesses the Mongo driver directly.
Product.collection.insert({
item: "ABC1",
details: {
model: "14Q3",
manufacturer: "XYZ Company"
},
}, function(err, doc) {
console.log(err);
console.log(doc);
});
You can have multiple models that use the same collection, so create a second model without the required field constraints for use with CSV import:
var rawProduct = mongoose.model("RawProduct", Schema({
name: String,
price: Number
}), 'products');
The third parameter to model provides an explicit collection name, allowing you to have this model also use the products collection.
I was able to ignore validation and preserve the middleware behavior by replacing the validate method:
schema.method('saveWithoutValidation', function(next) {
var defaultValidate = this.validate;
this.validate = function(next) {next();};
var self = this;
this.save(function(err, doc, numberAffected) {
self.validate = defaultValidate;
next(err, doc, numberAffected);
});
});
I've tested it only with mongoose 3.8.23
schema config validateBeforeSave=false
use validate methed
// define
var GiftSchema = new mongoose.Schema({
name: {type: String, required: true},
image: {type: String}
},{validateBeforeSave:false});
// use
var it new Gift({...});
it.validate(function(err){
if (err) next(err)
else it.save(function (err, model) {
...
});
})

Resources