I have a driver.js that contains a driver schema. Also the driverController.js, which contains my rest methods. GET, POST, DELETE, and PUT.
What i would like to do is
GET - http://localhost:3000/drivers?available=true
and have it return all of the drivers that are available.
My driver schema simply looks like this:
var mongoose = require('mongoose');
var DriverSchema = new mongoose.Schema({
name: String,
available: Boolean,
latitude: Number,
longitude: Number
});
mongoose.model('Driver', DriverSchema);
module.exports = mongoose.model('Driver');
I looked at some documentation, but I haven't been able to do anything.
Here's my GET method in which I'm attempting to add parameters
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
Driver.find({}, function (err, driver) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
var available = req.query.available;
if (available == driver.available )
res.status(200).send(available );
else
res.status(200).send("Nice! " + driver.available);
});
});
This comparison doesn't ever work. It always goes to the else statement. I'm not quite sure why but the output is "Nice! undefined" Even though I have plenty of drivers in my database, and if I only put inside the else statement
res.status(200).send("Nice! " + driver);
Then it gives me the list of drivers.
Nonetheless, I would like to be able to use query parameters in order to find drivers.
Any hints or tips would be greatly appreciated, as this is a project and I have never worked with restAPI, or javascript before. Thanks!
NOTE: Mongoose, express. node.js, and mongoDB are being used.
if I only put inside the else statement res.status(200).send("Nice! " + driver); Then it gives me the list of drivers.
it's a list of drivers, with if (available == driver.available ) you're comparing a boolean with an array of objects,
instead of fetching all the drivers and checking if they have availabe == true , add the condition to the .find() and return the result :
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
Driver.find({ available : req.query.available }, function (err, drivers) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
res.status(200).send(drivers);
});
});
EDIT :
you can do this to add filter depending on the query string :
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
var params = {};
Object.keys(req.query).forEach((v, k) => params[k] = v);
Driver.find(params, function (err, drivers) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
res.status(200).send(drivers);
});
});
having ?name=Wario&available=true will create an object like { name : 'wario', available : true and pass it to the .find()
Related
Sorry for the bad title, I didn't really know how to phrase it.
So I have a DB with some regions. Each of them has an unique code. I want all of them to be available in this route, but obviously the ID can be, let's say, from 10-19. If the ID is something else, then 404 should be rendered.
I have an idea on how to do this and it's a middleware. This is how I'd do it:
middleware.doesRegionExist = function(req, res, next)
{
var regions = [10, 11, 12];
if(regions.includes(req.params.id))
{
return next();
}
res.redirect("/404");
}
router.get('/regiune/:id', middleware.doesRegionExist, function(req, res, next)
{
res.send(req.params.id);
});
So my questions are: is this the best way to achieve this? I feel like it's not because I have to hardcode an array with the regions available in the middleware.
And why does my idea not work? (I get 404 everytime)
////edit: I had to change the if statement with this: if(regions.includes(Number(req.params.id))). Now it works. Is it the best way to do this though?
////edit2: I have this middleware for checking if the user has access to this variable:
middleware.access = function(req, res, next)
{
if(!req.isAuthenticated())
{
User.findOne({'username': req.body.username, "privileges.region": {$in: [1, req.body.regiune]}}, function(err, usr)
{
if(err)
{
console.log("middleWare.access - " + err);
return res.redirect("404");
}
else
{
console.log(usr);
if(usr === null)
{
return res.redirect("404");
}
else
{
return next();
}
}
})
}
}
so I can't use another middleware because I have to use this (and as far as I know you can't use more than 1 middleware in a route). Should I just copy middleware.doesRegionExist into the route?
You need two things here:
restricting route shape to prevent requests like: GET /region/<very-long-alphanumeric-string>
checking region existence in database and returning 404 if it not exists
Routing
Let's start from routing. Express' routes are support regex, so you can configure them very widely. For ex. if you expect to have id in 00-99 range only you can do the following:
router.get('/region/:id([0-9]{2})', function(req, res, next){
const numericId = parseInt(req.params.id);
// ... your code
next();
});
You can find more details on this topic here: https://expressjs.com/en/guide/routing.html, https://github.com/pillarjs/path-to-regexp
Database handling
Since your data are not constant over time and stored in database (am I right?), the only robust way to check if data really exists in the moment of making request is to perform database query.
All in one
If Region model is mongoose-based, your express handler should be something like this:
router.get('/region/:id([0-9]{2})', access, function(req, res, next){
const numericId = parseInt(req.params.id);
Region.findOne({regionId: numericId}, function (err, region){
if(err){
return res.redirect("404");
} else {
if(usr === null){
return res.redirect("404");
} else {
return next();
}
}
});
});
Everything depends on the need you want to solve, for example:
If the id you are going to analyze is a series of continuous numbers you can verify if the id is in the correct range.
const id = Number(req.params.id);
if (id >= 10 && id <= 12) {
return next();
}
res.redirect("/404");
In other cases you can have valid ids in an array or in some other data source.
You can have multiple middleware nesting one after another.
router.get('/', middleware1, middleware2, middleware3, function (req, res) {
var model = new IndexModel();
res.send('<code><pre>' + JSON.stringify(model, null, 2) + '</pre></code>');
});
I leave the documentation of Routing in Express, so you can review the section of Route handlers.
var LINK = mongoose.model('link', {
id: mongoose.Schema.Types.ObjectId,
linkA: Boolean,
linkB: Boolean,
}, 'link')
(model for LINK - here as reference)
When using mongoose on nodeJS I can use the find() function to return all documents in a collection:
var Test = LINK.find({}, (err, user) => {
if (err) console.log("error: " + err)
else console.log("users: " + user)
}, 4000)
which returns all the data in terminal. Easy, right?
But the problems arise when I try to use a query:
var Test = LINK.find({linkA:'true'}, (err, user) => {
if (err) console.log("error: " + err)
else console.log("users: " + user)
}, 4000)
The query at first didn't return any results (even though there were documents on mongodb populated beforehand). But after adding documents to mongodb via mongoose- the documents added can be accessed, but not any of the others that were created on the mongodb console.
Is there a reason for this? I'm suspicious that I'm using the find() function wrong but it seems to work fine when the documents were added via mongoose or I use an empty query so I'm not sure.
Any help would be appreciated.
Thanks.
The problem was the way I was storing the information.
As pointed out by Amiram Korach the documents had stored boolean values with quotation marks by mistake and so mongoose was recognising it as a string value.
I am use nodejs npm package sql
I currently have an array of product skus like so..
var skus = ['product1', 'product2', 'product3'];
My sql store in a file as follows...
SELECT *
FROM stock AS s
WHERE s.sku IN (#skus)
Then I also have my prepared statement code as follows..
var connection = new sql.Connection(config, function(err) {
var ps = new sql.PreparedStatement(connection);
//Add params
if(params != undefined){
for(var key in params){
ps.input(key, sql.VarChar(200));
}
}
ps.prepare(sqlstatement, function(err) {
ps.execute(params, function(err, data) {
callback(null, data);
ps.unprepare(function(err) {
});
});
});
});
}
skus is contained correctly within the params object as the statement works fine when I am using it for simple WHERE X = #YI am just struggling with how I need pass the array of skus to allow them to work in the prepared statement.
I am amend the string using split and join to comma seperate them etc etc but I can't get these methods to work.
I assumed that I would need the param string to look like the following 'product1','product2','product3'.
would be also useful if someone could shed some light on how to debug the completed prepared statement so I can see what is actually being queried to SQL (with params inplace)
Many thanks in advance!
It appears that the sql object (i.e. the mssql module) has no attribute to handle arrays of anything. Moreover, specifying a scalar type in the call to ps.input similarly does not work.
The next best thing is to build keys for your array of parameters into your sql statement itself:
var connection = new sql.Connection(config, function(err) {
var ps = new sql.PreparedStatement(connection);
// Construct an object of parameters, using arbitrary keys
var paramsObj = params.reduce((obj, val, idx) => {
obj[`id${idx}`] = val;
ps.input(`id${idx}`, sql.VarChar(200));
return obj;
}, {});
// Manually insert the params' arbitrary keys into the statement
var stmt = 'select * from table where id in (' + Object.keys(paramsObj).map((o) => {return '#'+o}).join(',') + ')';
ps.prepare(stmt, function(err) {
ps.execute(paramsObj, function(err, data) {
callback(null, data);
ps.unprepare(function(err) {
});
});
});
});
}
I have node running with express as the server side framework.
I have created the following endpoint:
app.post('/post/save', auth.auth, function (req, res) {
Post.findById(req.body._id, function (err, post) {
post = post || new Post();
post.author.name = req.user.getName();
post.author.id = req.user._id;
post.title = req.body.title;
post.body = req.body.body;
post.save(function (err, object) {
err && res.send(500);
res.status(200).send({ /*id: object._id*/ });
});
});
});
When I call this the first time, it works.
When I call this the second time, it fails. The request just keeps pending, and the object returned from the save function call is undefined.
req.body._id is undefined in both the requests. I try to create 2 new posts in a row.
What I want to do is to check if a document exist, if it does, update it and then save it, or create a new document.
I know stuff like upsert exist, but I cant use it because I need the pre-save middleware to trigger, and it only triggers before .save.
Can anyone see the error?
What if you put your logic to a callback, and then - either create or find a Post based on the request query value, passing your callback function? Just dont forget to remove this assignment: post.author.id = req.user._id;
app.post('/post/save', auth.auth, function (req, res) {
var callback = function(post) {
post.author.name = req.user.getName();
post.title = req.body.title;
post.body = req.body.body;
post.save(function (err, object) {
err && res.send(500);
res.status(200).send({ /*id: object._id*/ });
});
};
if (req.body._id) {
Post.findById(req.body._id, function (err, post) {
callback(post);
});
} else {
var post = new Post();
callback(post);
}
});
My original post worked, once I removed the unique field from the model, and dropped the collections in the database.
It might have been enough to drop the indexes; see Leonid Beschastnys comment;
when you're setting a field to be unique, Mongoose creates an unique
index on this field. This index persist in MongoDB even after removing
unique: true flag. Dropping collection indexes should resolve your
problem
I'm using mongoose to insert some data into mongodb. The code looks like:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
var user1 = docs[0], user2 = docs[1];
// insert channels
conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
var channel1 = docs[0], channel2 = docs[1];
// insert articles
conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
var article1 = docs[0], article2 = docs[1];
}
});
};
You can see there are a lot of nested callbacks there, so I'm trying to use q to refactor it.
I hope the code will look like:
Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.end();
But I don't know how to do it.
You'll want to use Q.nfcall, documented in the README and the Wiki. All Mongoose methods are Node-style. I'll also use .spread instead of manually destructuring .then.
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
var users = conn.collection('users');
var channels = conn.collection('channels');
var articles = conn.collection('articles');
function getInsertedArticles() {
return Q.nfcall(users.insert.bind(users), [{/*user1*/},{/*user2*/}]).spread(function (user1, user2) {
return Q.nfcall(channels.insert.bind(channels), [{userId:user1._id},{userId:user2._id}]).spread(function (channel1, channel2) {
return Q.nfcall(articles.insert.bind(articles), [{userId:user1._id,channelId:channel1._id},{}]);
});
})
}
getInsertedArticles()
.spread(function (article1, article2) {
// you only get here if all three of the above steps succeeded
})
.fail(function (error) {
// you get here if any of the above three steps failed
}
);
In practice, you will rarely want to use .spread, since you usually are inserting an array that you don't know the size of. In that case the code can look more like this (here I also illustrate Q.nbind).
To compare with the original one is not quite fair, because your original has no error handling. A corrected Node-style version of the original would be like so:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
function getInsertedArticles(cb) {
// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
if (err) {
cb(err);
return;
}
var user1 = docs[0], user2 = docs[1];
// insert channels
conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
if (err) {
cb(err);
return;
}
var channel1 = docs[0], channel2 = docs[1];
// insert articles
conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
if (err) {
cb(err);
return;
}
var article1 = docs[0], article2 = docs[1];
cb(null, [article1, article2]);
}
});
};
}
getInsertedArticles(function (err, articles) {
if (err) {
// you get here if any of the three steps failed.
// `articles` is `undefined`.
} else {
// you get here if all three succeeded.
// `err` is null.
}
});
With alternative deferred promise implementation, you may do it as following:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
// Setup 'pinsert', promise version of 'insert' method
var promisify = require('deferred').promisify
mongoose.Collection.prototype.pinsert = promisify(mongoose.Collection.prototype.insert);
var user1, user2;
// insert users
conn.collection('users').pinsert([{/*user1*/},{/*user2*/}])
// insert channels
.then(function (users) {
user1 = users[0]; user2 = users[1];
return conn.collection('channels').pinsert([{userId:user1._id},{userId:user2._id}]);
})
// insert articles
.match(function (channel1, channel2) {
return conn.collection('articles').pinsert([{userId:user1._id,channelId:channel1._id},{}]);
})
.done(function (articles) {
// Do something with articles
}, function (err) {
// Handle any error that might have occurred on the way
});
Considering Model.save instead of Collection.insert (quite the same in our case).
You don't need to use Q, you can wrap yourself the save method and return directly a Mongoose Promise.
First create an utility method to wrap the save function, that's not very clean but something like:
//Utility function (put it in a better place)
var saveInPromise = function (model) {
var promise = new mongoose.Promise();
model.save(function (err, result) {
promise.resolve(err, result);
});
return promise;
}
Then you can use it instead of save to chain your promises
var User = mongoose.model('User');
var Channel = mongoose.model('Channel');
var Article = mongoose.model('Article');
//Step 1
var user = new User({data: 'value'});
saveInPromise(user).then(function () {
//Step 2
var channel = new Channel({user: user.id})
return saveInPromise(channel);
}).then(function (channel) {
//Step 3
var article = new Article({channel: channel.id})
return saveInPromise(article);
}, function (err) {
//A single place to handle your errors
});
I guess that's the kind of simplicity we are looking for.. right? Of course the utility function can be implemented with better integration with Mongoose.
Let me know what you think about that.
By the way there is an issue about that exact problem in the Mongoose Github:
Add 'promise' return value to model save operation
I hope it's gonna be solved soon. I think it takes some times because they are thinking of switching from mpromise to Q: See here and then here.
Two years later, this question just popped up in my RSS client ...
Things have moved on somewhat since May 2012 and we might choose to solve this one in a different way now. More specifically, the Javascript community has become "reduce-aware" since the decision to include Array.prototype.reduce (and other Array methods) in ECMAScript5. Array.prototype.reduce was always (and still is) available as a polyfill but was little appreciated by many of us at that time. Those who were running ahead of the curve may demur on this point, of course.
The problem posed in the question appears to be formulaic, with rules as follows :
The objects in the array passed as the first param to conn.collection(table).insert() build as follows (where N corresponds to the object's index in an array):
[ {}, ... ]
[ {userId:userN._id}, ... ]
[ {userId:userN._id, channelId:channelN._id}, ... ]
table names (in order) are : users, channels, articles.
the corresopnding object properties are : user, channel, article (ie the table names without the pluralizing 's').
A general pattern from this article by Taoofcode) for making asynchronous call in series is :
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function(result) {
return doSomethingAsyncWithResult(item, result);
});
}, q());
}
With quite light adaptation, this pattern can be made to orchestrate the required sequencing :
function cascadeInsert(tables, n) {
/*
/* tables: array of unpluralisd table names
/* n: number of users to insert.
/* returns promise of completion|error
*/
var ids = []; // this outer array is available to the inner functions (to be read and written to).
for(var i=0; i<n; i++) { ids.push({}); } //initialize the ids array with n plain objects.
return tables.reduce(function (promise, t) {
return promise.then(function (docs) {
for(var i=0; i<ids.length; i++) {
if(!docs[i]) throw (new Error(t + ": returned documents list does not match the request"));//or simply `continue;` to be error tolerant (if acceptable server-side).
ids[i][t+'Id'] = docs[i]._id; //progressively add properties to the `ids` objects
}
return insert(ids, t + 's');
});
}, Q());
}
Lastly, here's the promise-returning worker function, insert() :
function insert(ids, t) {
/*
/* ids: array of plain objects with properties as defined by the rules
/* t: table name.
/* returns promise of docs
*/
var dfrd = Q.defer();
conn.collection(t).insert(ids, function(err, docs) {
(err) ? dfrd.reject(err) : dfrd.resolve(docs);
});
return dfrd.promise;
}
Thus, you can specify as parameters passed to cascadeInsert, the actual table/property names and the number of users to insert.
cascadeInsert( ['user', 'channel', 'article'], 2 ).then(function () {
// you get here if everything was successful
}).catch(function (err) {
// you get here if anything failed
});
This works nicely because the tables in the question all have regular plurals (user => users, channel => channels). If any of them was irregular (eg stimulus => stimuli, child => children), then we would need to rethink - (and probably implement a lookup hash). In any case, the adaptation would be fairly trivial.
Today we have mongoose-q as well. A plugin to mongoose that gives you stuff like execQ and saveQ which return Q promises.