I am .NET developer and learning MEAN stack, developing an API for simple post and comment app. I am using MVC pattern without view as it is API. Here is how I am trying to create my model function :
const User = module.exports = mongoose.model('User',UserSchema);
module.exports.getUsers = function(callback){
User.find()
.exec(function(err,list_users){
if(err) { callback(); }
return list_users;
});
}
And here is the controller :
exports.user_list = function(req,res,next){
User.getUsers((err,list_users) => {
if(err){ next(err);}
res.json({user_list : list_users});
})
}
I am getting an error as getUsers is not getting imported. I am trying here to separate the data access code from controller and make controller only to manage request and response as I have been doing in .NET. Any help how can I achieve this?
I would look through the documentation to get familiar with how to use mongoose.
First thing I see is that you are not defining the methods correctly according to the documentation here http://mongoosejs.com/docs/guide.html#methods.
But you do not have to define a find() function. Mongoose already has it built in.
So the next step would be how are you creating the instance of the Model in your controller.
Your model should be:
const mongoose = require('mongoose'),
UserSchema = mongoose.Schema();
const User = new UserSchema({
// define user
});
module.exports = mongoose.model('User',UserSchema);
And the controller:
const User = require('path_to_User_model');
exports.user_list = function(req,res,next){
User.find((err,result)=>{
if(err)
// log err and send response
else
// send the result ex: res.json(result);
});
}
also read through the setup on the npmjs website. https://www.npmjs.com/package/mongoose
So to create have your data functions and have your controller use that function you may try
const mongoose = require('mongoose'),
UserSchema = mongoose.Schema();
const User = new UserSchema({
// define user
});
// data functions
User.methods.getUsers = function (){
User.find((err,result)=>{
if(err)
// log err and return something
return false;
else
return result;
});
}
module.exports = mongoose.model('User',UserSchema);
then the controller
const User = require('path_to_User_model');
exports.user_list = function(req,res,next){
var users = User.getUsers();
if (!users)
res.status(500).send('error_page');
else
res.json(users);
}
Related
I'm working on a project that will be a multi-tenant Saas application, and am having difficulty implementing a way to log into various databases depending on the user login info. Right now, I just want to split traffic between a Sandbox database (for demo purposes, and will be wiped on a regular basis), and an Alpha database (for current client testing and development). I have written the middleware below, config.js, that detects the user ID on login and assigns a database object using mongoose.createConnection(). This key-value pair is then added to a store using memory-cache. Here is the config.js code:
var mcache = require('memory-cache'),
Promise = require("bluebird"),
mongoose = require('mongoose');
Promise.promisifyAll(require("mongoose"));
(function () {
'use strict';
var dbSand = mongoose.createConnection(process.env.DB_SAND);
var dbAlpha = mongoose.createConnection(process.env.DB_ALPHA);
function dbPathConfigMiddlewareWrapper (){
return function setDbPath(req, res, next){
if ( req ){
if (!mcache.get(req.session.id) && req.body.email){
var login = req.body.email;
if (login === 'demo#mysite.com'){
mcache.put(req.session.id, dbSand);
} else {
mcache.put(req.session.id, dbAlpha);
}
}
req.dbPath = mcache.get(req.session.id);
next();
}
};
}
module.exports = dbPathConfigMiddlewareWrapper;
}());
So far so good. But I have been unsuccessful in calling the correct database in my routes. When I was just using a single database, I could easily use this:
var connStr = process.env.DBPATH;
if(mongoose.connection.readyState === 0){
mongoose.connect(connStr, function(err) {
if (err) throw err;
console.log('Successfully connected to MongoDB');
});
}
Now, I'm trying this to no avail:
var connStr = req.dbPath; //where req.dbPath is assigned in the config middleware above.
if(connStr.connection.readyState === 0){
mongoose.connect(req.dbPath, function(err) {
if (err) throw err;
console.log('Successfully connected to MongoDB');
});
}
Any guidance here would be greatly appreciated. This seems like it should be much more straightforward, and the documentation alludes to it but does not elaborate.
Here, I think, the problem is you are saving a database object to your key value storage. mcache.put(req.session.id, dbSand);. Which caused error in if(connStr.connection.readyState === 0).
You can stringify your object. mcache.put(req.session.id, JSON.stringify(dbSand));. And get the object's string and parse it into JSON like var connStr = JSON.parse(req.dbPath);.
You don't call mongoose.connect() if you're manually creating connections.
Instead, you have to register your models for each connection, which is a bit of a PITA but as far as I know there's no way around that. It may require some restructuring of your code.
Here's some untested code on how you could set something like that up.
Your middleware file:
// Create connections
const registerModels = require('./register-models');
let dbSand = mongoose.createConnection(process.env.DB_SAND);
let dbAlpha = mongoose.createConnection(process.env.DB_ALPHA);
// Register your models for each connection.
registerModels(dbSand);
registerModels(dbAlpha);
function dbPathConfigMiddlewareWrapper() { ... }
register-models.js:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let UserSchema = new mongoose.Schema(...);
module.exports = function(conn) {
conn.model('User', UserSchema);
};
This does mean that you can't use User.find(...) in your routes, because that only works when you're using a single connection (the default one that gets created with mongoose.connect(), which you're not using).
Instead, you should use something like this in your routes:
req.dbPath.model('User').find(...);
Using mongoose if I try to find a document stored in the database using db.model() like mongoose docs suggests, I get no results :(
However, if I use db.collection().find() I get results.
Why is that? and what will be the best approach using the example code below?
In app.js
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect(config.database.url); // database configuration
app.locals.db = mongoose.connection;
app.locals.db.on('error', function(msg){
console.log("db connection failed", msg);
});
app.locals.db.once('open', function(){
console.log("db connected successfully");
});
In routes -> person.js
var Person = require('Person');
router.post('/search', function(req, res) {
var person = new Person(req.app.locals.db);
person.getPerson(req.body.name, function(found, docs) {
if (found === false)
res.render('pageTpl', { results: 'person not found' });
else
res.render('pageTpl', { results: docs });
});
In Person.js
// schema here returns new mongoose.Schema({ fields : types })
const personSchema = require('./schemas/personSchema');
function Person(db) {
this.db = db;
}
Person.prototype.getPerson = function(term, callback) {
var Person = this.db.model('people', personSchema);
var q = Person.find({ name: /^term/ }).sort('age').limit(2);
q.exec(function(err, results) {
if (err) return callback (false, err);
// This returns [] results
console.log(results);
callback(true, results);
});
});
module.exports = Person;
The problem is with this line: var Person = this.db.model('people', personSchema);
Mongoose automagically converts model to plural so try the following: var Person = this.db.model('person', personSchema);
After some more research I figure out why I was not getting results. It all fall into the details. In Person.js I have the following line:
var q = Person.find({ name: /^term/ }).sort('age').limit(2);
If you look closely, the problem resides in here /^term/ where term in a variable but does not get translate into its value. Instead I did:
var regTerm = new RegExp("^" + term);
Now, regTerm can be used as regular expession within find.
Hope this helps someone in the future.
I'm building a basic blog in Node.js / Express using MongoDB w/ Mongoose ORM.
I have a pre 'save' hook that I'd like to use to auto-generate a blog/idea slug for me. This works fine and well, except for the part where I want to query to see if there are any other existing posts with the same slug before continuing.
However, it appears that this does not have access to .find or .findOne() and so I keep getting an error.
What's the best way to approach this?
IdeaSchema.pre('save', function(next) {
var idea = this;
function generate_slug(text) {
return text.toLowerCase().replace(/[^\w ]+/g,'').replace(/ +/g,'-').trim();
};
idea.slug = generate_slug(idea.title);
// this has no method 'find'
this.findOne({slug: idea.slug}, function(err, doc) {
console.log(err);
console.log(doc);
});
//console.log(idea);
next();
});
Unfortunately, it's not documented very well (no mention of it in the Document.js API docs), but Documents have access to their models through the constructor field - I use it all the time for logging things from plugins, which gives me access to which model they're attached to.
module.exports = function readonly(schema, options) {
schema.pre('save', function(next) {
console.log(this.constructor.modelName + " is running the pre-save hook.");
// some other code here ...
next();
});
});
For your situation, you should be able to do:
IdeaSchema.pre('save', function(next) {
var idea = this;
function generate_slug(text) {
return text.toLowerCase().replace(/[^\w ]+/g,'').replace(/ +/g,'-').trim();
};
idea.slug = generate_slug(idea.title);
// this now works
this.constructor.findOne({slug: idea.slug}, function(err, doc) {
console.log(err);
console.log(doc);
next(err, doc);
});
//console.log(idea);
});
In this you have got the document, not the model. Method findOne is not present on the document.
If you need the model, you can always retrieve it as is shown here. But more clever would be to just assign the model to a variable at the point of creation.
Then use this variable anywhere you desire. If it is in another file, then use module.exports and require to get it anywhere else in your project.
Something like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/dbname', function (err) {
// if we failed to connect, abort
if (err) throw err;
var IdeaSchema = Schema({
...
});
var IdeaModel = mongoose.model('Idea', IdeaSchema);
IdeaSchema.pre('save', function(next) {
var idea = this;
function generate_slug(text) {
return text.toLowerCase().replace(/[^\w ]+/g,'').replace(/ +/g,'-').trim();
};
idea.slug = generate_slug(idea.title);
// this has no method 'find'
IdeaModel.findOne({slug: idea.slug}, function(err, doc) {
console.log(err);
console.log(doc);
});
//console.log(idea);
next();
});
// we connected ok
})
I am trying to stub the mongoose dependency used in this object:
var Page = function(db) {
var mongoose = db || require('mongoose');
if(!this instanceof Page) {
return new Page(db);
}
function save(params) {
var PageSchema = mongoose.model('Page');
var pageModel = new PageSchema({
...
});
pageModel.save();
}
Page.prototype.save = save;
}
module.exports = Page;
Using the answer for this question, I've tried doing this:
mongoose = require 'mongoose'
sinon.stub mongoose.Model, 'save'
But I got the error:
TypeError: Attempted to wrap undefined property save as function
I also tried this:
sinon.stub PageSchema.prototype, 'save'
And then I got the error:
TypeError: Should wrap property of object
Can anyone help with this? What am I doing wrong?
I've analysed mongoose source and don't think this is possible. Save function is not defined on model, but dynamically generated by hooks npm which enables pre/post middleware functionality.
However, you can stub save on instance like this:
page = new Page();
sinon.stub(page, 'save', function(cb){ cb(null) })
UPDATE: Stubbing out pageModel
First, you need to make pageModel accessible by setting it as own property of Page (this.pageModel = xxx). Then, you can stub it like shown bellow:
mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
mongoose.set('debug', true);
schema = new mongoose.Schema({title: String});
mongoose.model('Page', schema);
var Page = function(db) {
var mongoose = db || require('mongoose');
if(!this instanceof Page) {
return new Page(db);
}
var PageSchema = mongoose.model('Page');
this.pageModel = new PageSchema();
function save(params, cb) {
console.log("page.save");
this.pageModel.set(params);
this.pageModel.save(function (err, product) {
console.log("pageModel.save");
cb(err, product);
});
}
Page.prototype.save = save;
};
page = new Page();
sinon = require('sinon');
sinon.stub(page.pageModel, 'save', function(cb){
cb("fake error", null);
});
page.save({ title: 'awesome' }, function (err, product) {
if(err) return console.log("ERROR:", err);
console.log("DONE");
});
I recommend you to use mock instead of stub, that will check the method really exists on the original object.
var page = new Page();
// If you are using callbacks, use yields so your callback will be called
sinon.mock(page)
.expects('save')
.yields(someError, someResult);
// If you are using Promises, use 'resolves' (using sinon-as-promised npm)
sinon.mock(page)
.expects('save')
.resolves(someResult);
Take a look to sinon-mongoose. You can expects chained methods (on both, Mongoose Models and Documents) with just a few lines (there are working examples on the repo).
page = new Page();
sinon.stub(page, 'save', function(cb){ cb(null) })
Above code is deprecated.
Please try to add fake function for your stub as below -
sinon.stub(page, 'save').callsFake(function(cb){
// do your Fake code
cb(null)
})
I am trying to run a simple mongoose/node example which I found here on stackoverflow:
var mongoose = require('mongoose'),
db = mongoose.connect('mongodb://localhost/db'),
Schema = mongoose.Schema;
var sentinel = setTimeout(function(){
throw "failed to connect to MongoDB after one minute!";
}, 60*1000); // 60 seconds
mongoose.model('User', new Schema({
properties: {
name : { type: String, index: true }
}
}));
var User = db.model('User');
var u = new User();
u.name = 'Foo';
u.save();
User.find().all(function(arr) {
clearTimeout(sentinel); // cancel the timeout sentinel
console.log('Users found');
console.log(arr);
console.log('length='+arr.length);
});
process.stdin.resume();
If I get the code right there should be an output in the terminal at the end of the script, where the message "Users found" and all users from the collection should be printed. But I just get the timeout message. Why that?
I am running my server on an Amazon EC2 micro instance. Node, Mongodb and mongoose are installed and a Mongodb server is running (I can interact with it from the terminal via "mongo"). I have also created the directory /data/db.
I don't know about mongoose but u.save() might be asynchronous because it writes to the DB. Try
u.save(function (err){
if(err) console.log(err);
User.find().all(function(arr) {
clearTimeout(sentinel); // cancel the timeout sentinel
console.log('Users found');
console.log(arr);
console.log('length='+arr.length);
});
});
Edit: This works fine
var mongoose = require('mongoose');
var connection = mongoose.connect('mongodb://localhost/my_database');
var Schema = mongoose.Schema
var User = new Schema({
author : String
, type : String
});
var MyUserModel = mongoose.model('User', User); //create and access the model User
var u = new MyUserModel();
u.author = 'authorname';
u.save(function(err){
if (err) console.log(err);
});
MyUserModel.find({}, function (err,docs) {
console.log(docs);
});
I handled this problem by adding one additional step in each router where I use DB.
It's a little bit messy but it works and 100% no leaks.
Something like this:
// file: 'routes/api/v0/users.js'
router
var User = require('../../../models/user').User,
rest = require('../../../controllers/api/v0/rest')(User),
checkDB = require('../../../middleware/checkDB');
module.exports = function (app) {
app.get('/api/v0/users', checkDB, rest.get);
app.get('/api/v0/users/:id', checkDB, rest.getById);
app.post('/api/v0/users', checkDB, rest.post);
app.delete('/api/v0/users', checkDB, rest.deleteById);
app.put('/api/v0/users', checkDB, rest.putById);
};
// file: 'middleware/checkDB.js'
var HttpError = require('../error').HttpError,
mongoose = require('../lib/mongoose');
// method which checks is DB ready for work or not
module.exports = function(req, res, next) {
if (mongoose.connection.readyState !== 1) {
return next(new HttpError(500, "DataBase disconnected"));
}
next();
};
PS If you know solution better, please let me know.