Mongoose not creating document on mongodb - mongodb

I am using Next.js and MongoDB (mongoose) to make an education portal website. The school admin can add important notices from their dashboard and the student can view them from theirs. Most mongoose operations have to be done in an API that next.js provides them self so my code to create the document on MongoDB is located in /API/ directory. Code:
/**
* #param {import("next").NextApiRequest} req
* #param {import("next").NextApiResponse} res
*/
import circulars from '../../../model/hw'
export default async function stuffss(req, res) {
try{
const {number, text} = req.body
console.log("Connecting to mongo")
mongoose.connect("mongodb+srv://usernameherer:passowordhere#cluster0.clusteridhere.mongodb.net/stuff?retryWrites=true&w=majority")
console.log("Connected to mongo")
console.log("Creating document")
const createdCircular = await circulars.create(req.body)
console.log("Created document")
res.json({ createdCircular })
}
catch(error) {
console.log(error)
res.json({ error })
}
}
The circulars schema:
const mongoose = require('mongoose')
const circularsSchema = new mongoose.Schema({
number: {
unique: true,
required: true,
type: Number
},
text: {
required: true,
type: String
}
})
const circulars = mongoose.models.circularss || mongoose.model("circularss", circularsSchema)
export default circulars
Sending a post request using insomnia gives the following result:
{
"createdCircular": {
"number": 15463,
"text": "loreum ipsum",
"_id": "63cb86421ca543a731cb39c3",
"__v": 0
}
}
but when I go and look for it on the MongoDB website it doesn't have any results.
I believe it may be because of how I am connecting or that I am not sending it properly to a collection.
This is the structure of my database:
Is there a way to connect to the 'stuff' database and 'circulars' collection?

Looks like that the database needs to be specified in connection string.
E.g.
...clusteridhere.mongodb.net/stuff?retryWrites=true&w=majority
Ref: https://mongoosejs.com/docs/connections.html

Here is how I fixed this issue. In my schema I had put the same circulars which created created a new collection for it, I also had to remove await from mongoose.connect. This combined fixed the issue for me. You should also make sure that mongoose.models.name is the same as the mongoose.model("name", schema)

Related

Why are db collections created for Mongoose 6 subdocs?

I'm looking at upgrading my projects mongoose version from 5.10.19 to latest (6.5.1). I'm noticing that I have a lot more collections in my database than I did before. I made a simple example to test this out and when I run it on mongoose 5, I only see the collection "mains" but mongoose 6 creates "mains" and "subs". I'd expect that the subdocument models would not have their own collection like mongoose 5 behaves.
import { connect, model, Schema } from 'mongoose';
const mongoUrl = 'mongodb://localhost:27017/test';
(async () => {
const subSchema: Schema = new Schema({ color: String, yes: Boolean });
const mainSchema: Schema = new Schema({ name: String, sub: subSchema });
const MainModel = model('Main', mainSchema);
model('Sub', subSchema);
await connect(mongoUrl, { ssl: true, sslValidate: false });
console.log(`Successfully connected to mongodb: "${mongoUrl}"`);
await MainModel.create({ name: 'One', sub: { color: 'Yellow', yes: true } });
})()
.then(() => {
console.log('\nSuccess');
process.exit();
})
.catch(() => {
console.log('\nFailure');
process.exit();
});
Is there a mongoose setting I'm missing that's causing this to happen?
Also, on Node 12.20.12.
It's probably because of this change:
autoCreate is true by default unless readPreference is secondary or secondaryPreferred, which means Mongoose will attempt to create every model's underlying collection before creating indexes.
So turning off autoCreate (and possibly autoIndex) on subSchema should fix it. Alternatively, just don't create a model of it (only the schema).

Mongoose: deleteOne middleware for cascading delete not working

With remove being deprecated in Mongoose 5.7.13, I want to use deleteOne instead. I need to get the id of the deleted document so that I can then delete further related documents in other collections in a cascade. I thought that "this" within the context of the pre middleware hook was meant to refer to the removed document, but instead it's just an empty object. Is there a canonical working example of this? I'm still currently using 5.7.12 at this point - will that make a difference here?
Here is the code I'm currently working with. The issue is that I can't get the projectId at the start because the reference is completely empty. Doing this on post rather than pre, or switching the option to run on query rather than document all yield the same result.
ProjectSchema.pre("deleteOne", {document:true}, (next) => {
const projectId = this._id;
ListModel.find({parentProject:projectId}, (err, lists) => {
if(err){
console.log("error cascading project delete to lists", {err});
}
lists.map(list => {
ListModel.deleteOne({_id:list._id}, (err, result) => {
if(err) {
console.log("error on project delete cascade", {err});
}
});
});
});
});
It depends whether you call deleteOne on document or on model. The later just have no document to bind it to.
The former gives you the document as you expect:
const project = await ProjectModel.findOne();
project.deleteOne();
The later gives you the Query. There is no _id in the query, but it has this.op for example, which in this middleware will be "deleteOne":
await ProjectModel.deleteOne();
The only way to get the document id in this case is to ensure it is provided in the query:
await ProjectModel.deleteOne({_id: "alex"});
Then you can get it in the middleware from the filter:
const projectId = this.getFilter()["_id"]
You can specify query: false in second parameter of the middleware to ensure the it is not invoked when you call deleteOne on model. So the best you can do:
ProjectSchema.pre("deleteOne", {document:true, query: false}, (next) => {
const projectId = this._id;
....
});
ProjectSchema.pre("deleteOne", {document:false, query: true}, (next) => {
const projectId = this.getFilter()["_id"];
if (typeof projectId === "undefined") {
// no way to make cascade deletion since there is no _id
// in the delete query
// I would throw an exception, but it's up to you how to deal with it
// to ensure data integrity
}
});
Please take a look at corresponding tests on v5.7.12: https://github.com/Automattic/mongoose/blob/5.7.12/test/model.middleware.test.js#L436
In the mongoose docs it says "Model.deleteOne() does not trigger pre('remove') or post('remove') hooks."
There is solution if you can refactor your delete operations with findByIdAndDelete, it triggers the findOneAndDelete middleware,
So we can add this middleware to Project Schema.
Project model:
const mongoose = require("mongoose");
const ProjectChild = require("./projectChild");
const ProjectSchema = new mongoose.Schema({
name: String
});
ProjectSchema.post("findOneAndDelete", async function(doc) {
console.log(doc);
if (doc) {
const deleteResult = await ProjectChild.deleteMany({
parentProject: doc._id
});
console.log("Child delete result: ", deleteResult);
}
});
module.exports = mongoose.model("Project", ProjectSchema);
ProjectChild model:
const mongoose = require("mongoose");
const projectChildSchema = new mongoose.Schema({
name: String,
parentProject: {
type: mongoose.Schema.Types.ObjectId,
ref: "Project"
}
});
module.exports = mongoose.model("ProjectChild", projectChildSchema);
I created a project like this:
{
"_id": "5dea699cb10c442260245abf",
"name": "Project 1",
"__v": 0
}
And created 2 project child for this project:
Child 1
{
"_id": "5dea69c7b10c442260245ac0",
"name": "Child 1 (project 1)",
"parentProject": "5dea699cb10c442260245abf",
"__v": 0
}
Child 2
{
"_id": "5dea69e8b10c442260245ac1",
"name": "Child 2 (project 1)",
"parentProject": "5dea699cb10c442260245abf",
"__v": 0
}
I created a sample route to delete a project by its id like this:
router.delete("/project/:id", async (req, res) => {
const result = await Project.findByIdAndDelete(req.params.id);
res.send(result);
});
When I send a DELETE request to this route, we see the following info in the console:
console.log(doc);
{ _id: 5dea699cb10c442260245abf, name: 'Project 1', __v: 0 }
console.log("Child delete result: ", deleteResult);
Child delete result: { n: 2, ok: 1, deletedCount: 2 }
So we could deleted the 2 children of the project, when we deleted the project.
As an alternative you can also use findOneAndRemove, it triggers findOneAndRemove post middleware.
So in the ProjectSchema we replace the post middleware like this:
ProjectSchema.post("findOneAndRemove", async function(doc) {
console.log(doc);
if (doc) {
const deleteResult = await ProjectChild.deleteMany({
parentProject: doc._id
});
console.log("Child delete result: ", deleteResult);
}
});
When we use a findOneAndRemove operation, the result will be the same as the first alternative:
const result = await Project.findOneAndRemove({ _id: req.params.id });

Circular Reference Issue in Mongoose pre-hook

In my MongoDB/Node backend environment I am using Mongoose pre and post hook middleware to check what's changed on the document, in order to create some system notes as a result.
One problem I'm running into is that when I try and lookup the record for the document in question I get a "Customer.findOne()" is not a function error. This is ONLY a problem when I'm looking up a record from the same collection from which the model just launched this pre and post hook triggers file. In other words, if my "Customer" model kicks off functions in a pre hook function in an external file, then I get an error if I then try and lookup a Customer with a standard findOne():
My customer model looks something like this:
module.exports = mongoose.model(
"Customer",
mongoose
.Schema(
{
__v: {
type: Number,
select: false
},
deleted: {
type: Boolean,
default: false
},
// Other props
searchResults: [
{
matchKey: String,
matchValue: String
}
]
},
{
timestamps: true
}
)
.pre("save", function(next) {
const doc = this;
trigger.preSave(doc);
next();
})
.post("save", function(doc) {
trigger.postSave(doc);
})
.post("update", function(doc) {
trigger.postSave(doc);
})
.post("findOneAndUpdate", function(doc) {
trigger.postSave(doc);
})
);
... the problematic findOne() function in the triggers file being called from the model looks like this:
const Customer = require("../../models/customer");
exports.preSave = async function(doc) {
this.preSaveDoc = await Customer.findOne({
_id: doc._id
}).exec();
};
To clarify, this is NOT a problem if I'm using a findOne() to lookup a record from a different collection in this same triggers file. Then it works fine. See below when finding a Contact -- no problem here:
const Contact = require("../../models/contact");
exports.preSave = async function(doc) {
this.preSaveDoc = await Contact.findOne({
_id: doc._id
}).exec();
};
The workaround I've found is to use Mongo instead of Mongoose, like so:
exports.preSave = async function(doc) {
let MongoClient = await require("../../config/database")();
let db = MongoClient.connection.db;
db.collection("customers")
.findOne({ _id: doc._id })
.then(doc => {
this.preSaveDoc = doc;
});
}
... but I'd prefer to use Mongoose syntax here. How can I use a findOne() in a pre-hook function being called from the same model/collection as the lookup type?
I have ran similar issue few days ago.
Effectively it is a circular dependency problem. When you call .findOne() on your customer model it doesn't exist as it is not exported yet.
You should probably try something like that :
const customerSchema = mongoose.Schema(...);
customerSchema.pre("save", async function(next) {
const customer = await Customer.findOne({
_id: this._id
}).exec();
trigger.setPreSaveDoc(customer);
next();
})
const Customer = mongoose.model("Customer", customerSchema)
module.export Customer;
Here customer will be defined because it is not called (the pre hook) before its creation.
As an easier way (I am not sure about it) but you could try to move the Contact import in your Trigger file under the save function export. That way I think the decencies may works.
Did it helps ?

How to Connect/Query Mongoose to an existing Database

I am working on trying to connect a Node/Express server to an existing MongoDB Database/Collection. I have already successfully connected to the database. However, I am having a tremendously difficult time setting up my models/schema to query.
The MongoDB is MongoDB Atlas and has one collection with over 800,000 documents. The name of the single collection is "delitosCollection".
I have tried the following to with no success:
var CrimeData = mongoose.model('DelitosCollection', new Schema({}),'delitosCollection');
mongoose.connection.on('open', function(ref){
console.log("connected to the mongo server");
CrimeData.find({}, (err,results) => {
if(err){
console.log("ERROR")
throw err
}
console.log("results: ", results.length)
} )
});
I know the connection is working as I am receiving the console.log with no errors. However, results.length is returning 0 when it should be over 800,000. Have spent way too many hours on this.
Create an empty schema for each collection you want to use
and then create a model to be used in your project
the model take 3 parameter
1)name of the model
2)schema name
3)collection name ( from mongodb atlas)
like that
const mongoose = require('mongoose');
mongoose.connect('mongodb uri')
const userSchema = new mongoose.Schema({});
const User = mongoose.model('User', userSchema, 'user');
then you can use the model normally
User.find({})
connection to mongo db
// Connect to mongoDB
mongoose.connect('mongodb://localhost/[yourDbName]',{useNewUrlParser:true})
.then(function(){
console.log('mongoDB connected');
})
.catch(function(){
console.log('Error :');
})
after that you will have to create your schema and then only you can query the database
create your schema like this
// Crimes Schema
const CrimeDetailsSchema= new Schema({
first_name: {
type: String,
required: true
},
last_name: {
type: String,
required: true
},
email: {
type: String,
required: true
}
});
const Profile = module.exports = mongoose.model('delitosCollection', CrimeDetailsSchema, 'delitosCollection');
after that create your queries
you can get an idea about that in mongoose documentation here
You can refer to the answer given below, just pass an empty object in schema
like db.model('users', new Schema({}))

How to access a preexisting collection with Mongoose?

I have a large collection of 300 question objects in a database test. I can interact with this collection easily through MongoDB's interactive shell; however, when I try to get the collection through Mongoose in an express.js application I get an empty array.
My question is, how can I access this already existing dataset instead of recreating it in express? Here's some code:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
mongoose.model('question', new Schema({ url: String, text: String, id: Number }));
var questions = mongoose.model('question');
questions.find({}, function(err, data) { console.log(err, data, data.length); });
This outputs:
null [] 0
Mongoose added the ability to specify the collection name under the schema, or as the third argument when declaring the model. Otherwise it will use the pluralized version given by the name you map to the model.
Try something like the following, either schema-mapped:
new Schema({ url: String, text: String, id: Number},
{ collection : 'question' }); // collection name
or model mapped:
mongoose.model('Question',
new Schema({ url: String, text: String, id: Number}),
'question'); // collection name
Here's an abstraction of Will Nathan's answer if anyone just wants an easy copy-paste add-in function:
function find (name, query, cb) {
mongoose.connection.db.collection(name, function (err, collection) {
collection.find(query).toArray(cb);
});
}
simply do find(collection_name, query, callback); to be given the result.
for example, if I have a document { a : 1 } in a collection 'foo' and I want to list its properties, I do this:
find('foo', {a : 1}, function (err, docs) {
console.dir(docs);
});
//output: [ { _id: 4e22118fb83406f66a159da5, a: 1 } ]
You can do something like this, than you you'll access the native mongodb functions inside mongoose:
var mongoose = require("mongoose");
mongoose.connect('mongodb://localhost/local');
var connection = mongoose.connection;
connection.on('error', console.error.bind(console, 'connection error:'));
connection.once('open', function () {
connection.db.collection("YourCollectionName", function(err, collection){
collection.find({}).toArray(function(err, data){
console.log(data); // it will print your collection data
})
});
});
Update 2022
If you get an MongoInvalidArgumentError: The callback form of this helper has been removed. error message, here's the new syntax using async/await:
const mongoose = require("mongoose");
mongoose.connect('mongodb://localhost/productsDB');
const connection = mongoose.connection;
connection.on('error', console.error.bind(console, 'connection error:'));
connection.once('open', async function () {
const collection = connection.db.collection("Products");
collection.find({}).toArray(function(err, data){
console.log(data); // it will print your collection data
});
});
I had the same problem and was able to run a schema-less query using an existing Mongoose connection with the code below. I've added a simple constraint 'a=b' to show where you would add such a constraint:
var action = function (err, collection) {
// Locate all the entries using find
collection.find({'a':'b'}).toArray(function(err, results) {
/* whatever you want to do with the results in node such as the following
res.render('home', {
'title': 'MyTitle',
'data': results
});
*/
});
};
mongoose.connection.db.collection('question', action);
Are you sure you've connected to the db? (I ask because I don't see a port specified)
try:
mongoose.connection.on("open", function(){
console.log("mongodb is connected!!");
});
Also, you can do a "show collections" in mongo shell to see the collections within your db - maybe try adding a record via mongoose and see where it ends up?
From the look of your connection string, you should see the record in the "test" db.
Hope it helps!
Something else that was not obvious, to me at least, was that the when using Mongoose's third parameter to avoid replacing the actual collection with a new one with the same name, the new Schema(...) is actually only a placeholder, and doesn't interfere with the exisitng schema so
var User = mongoose.model('User', new Schema({ url: String, text: String, id: Number}, { collection : 'users' })); // collection name;
User.find({}, function(err, data) { console.log(err, data, data.length);});
works fine and returns all fields - even if the actual (remote) Schema contains none of these fields. Mongoose will still want it as new Schema(...), and a variable almost certainly won't hack it.
Go to MongoDB website, Login > Connect > Connect Application > Copy > Paste in 'database_url' > Collections > Copy/Paste in 'collection' .
var mongoose = require("mongoose");
mongoose.connect(' database_url ');
var conn = mongoose.connection;
conn.on('error', console.error.bind(console, 'connection error:'));
conn.once('open', function () {
conn.db.collection(" collection ", function(err, collection){
collection.find({}).toArray(function(err, data){
console.log(data); // data printed in console
})
});
});
I tried all the answers but nothing worked out, finally got the answer hoe to do it.
var mongoose = require('mongoose');
mongoose.connect('mongodb://0.0.0.0:27017/local');
// let model = require('./test1');
setTimeout(async () => {
let coll = mongoose.connection.db.collection(<Your collection name in plural form>);
// let data = await coll.find({}, {limit:2}).toArray();
// let data = await coll.find({name:"Vishal"}, {limit:2}).toArray();
// let data = await coll.find({name:"Vishal"}, {projection:{player:1, _id:0}}).toArray();
let data = await coll.find({}, {limit:3, sort:{name:-1}}).toArray();
console.log(data);
}, 2000);
I have also mentioned some of the criteria to filter out. Delete and update can also be done by this.
Thanks.
Make sure you're connecting to the right database as well as the right collection within the database.
You can include the name of the database in the connection string.
notice databasename in the following connection string:
var mongoose = require('mongoose');
const connectionString = 'mongodb+srv://username:password#hosturl.net/databasename';
mongoose.connect(connectionString);