I am new to RESTful APIs and I've successfully implemented the GET and DELETE command for my API (GET localhost:4000/api, DELETE localhost:4000/api on Postman works fine).
Code for my get looks like:
router.get('/', function(req, res) {
user.find({}, function(err, users) {
if(err){
res.status(404).send({
message: err,
data: []
});
} else {
res.status(200).send({
message: 'OK',
data: users
});
}
});
});
Now I want to implement using parameters. For example, I want to implement something like sorting where
http://localhost/4000/api/users?sort={"name": 1}
(1- ascending; -1 - descending)
would mean sorting the name in ascending order.
What I am not sure how to do is:
How do I make the ?sort thing work?
How do I select which field to sort?
Please help!
You can only pass order(asc, desc), if you want to sort by name you can do like that
http://localhost/4000/api/users?order=-1
or
http://localhost/4000/api/users?&order=1
then in your controller
router.get('/', function(req, res) {
let order = req.query.order;
user
.find({})
.sort({"name": order})
.exec(function(err, users) {
if(err){
res.status(404).send({
message: err,
data: []
});
} else {
res.status(200).send({
message: 'OK',
data: users
});
}
});
});
These works if you use mongoose.js for mongodb
One cool solution that I frequently use is the following form
/api/users?sort=-name|+firstname
I use | for multiple fields sorting, and - for desc, + for asc
In express:
const { sort } = req.query; // sort = '-name|+firstname';
const order = sort.split('|') // will return an array ['-name', '+firstname']
.reduce((order, item) => {
const direction = item.charAt(0) === '-' ? -1 : 1;
const field = item.substr(1);
order[field] = direction;
return order;
}, {})
// order {'name': -1, 'firstname': 1}
users.find({}).sort(order); // do your logic
Related
So, I am new to nodejs. I don't just want to solve this problem, but I also want to learn this concept.
1 Prize has Many Winners. Both are separate tables. I first get list of prizes related to a certain id. I loop through those prizes using Promises.all() and then, for each prize, I query for winners.
Here is my code:
router.post("/getResult", function (req, res) {
const lottery_id = req.body.lottery_id;
const data = [];
//Find list of prices for given lottery_id. Note the sorting applied here
Prize.find({"lotid": lottery_id}).sort({name: 1})
.then(async function (prizes) {
try {
await Promise.all(prizes.map(async (prize) => {
//Sorting here works fine as confirmed by this log.
console.log(prize.name);
await Winner.find({"id": prize._id})
.then(function (winners) {
data.push({
prize: prize,
winners: winners
});
})
}));
//Sorting here is completely messed up.
// Something wrong with second query "Winner.find() and pushing data
res.send({success: 1, data: data});
} catch (err) {
console.log("Error " + err);
res.send({success: 0, data: err});
}
}).catch(function (err) {
res.send({success: 0, error: err});
})
});
The final result that I get doesn't follow the sorting applied to prize. May be, the query Winner.find() for 2nd prize finishes before 1st prize and hence, it is pushed in data before 1st prize.
You are seeing an unexpected sorted result because there is no coordination with how you are pushing values into the data array. prizes.map() does iterate over the prizes array sequentially, however, there is no guarantee that each mapped promise, or more specifically each Winner.find(), will become fulfilled in the same order chronologically.
One way you could fix this is to use the return value of Promise.all(). Here is an example:
router.post("/getResult", async function (req, res) {
const lottery_id = req.body.lottery_id;
try {
const prizes = await Prize.find({ "lotid": lottery_id }).sort({ name: 1 });
const prizeWinners = await Promise.all(prizes.map(async function(prize) {
const winners = await Winner.find({ "id": prize._id });
return {
prize,
winners
};
}));
res.send({ success: 1, data: prizeWinners });
} catch (err) {
res.send({ success: 0, error: err });
}
});
I am trying to pass URL parameter into the SQL query. I have a column called "puppy_id" and one of the values is puppy1.
I want to call this URL :- localhost:3000/api/puppies/puppy1
and it should execute the query in the database SELECT * FROM puppytable WHERE puppy_id='puppy1' and return the output.
I have no problem to connect to the database. But, it is showing that no data returned. I think, I am doing something wrong in executing the query.
My Code :-
index.js
var express = require('express');
var router = express.Router();
var db = require('../queries');
router.get('/api/puppies/:puppy_id', db.getPuppyStatus);
module.exports = router;
queries.js
module.exports = {
getPuppyStatus: getPuppyStatus
};
function getPuppyStatus(req, res, next) {
var puppyID = parseInt(req.params.puppy_id);
db.any('select * from puppytable where puppy_id =$1', puppyID)
.then(function (data) {
res.status(200)
.json({
status: 'success',
data: data,
message: 'Retrieved puppies'
});
})
.catch(function (err) {
return next(err);
});
}
queries.js is in root of project directory.
It is calling from here in index.js
var db = require('../queries');
This is my output :-
{"status":"success","data":[],"message":"Retrieved puppies"}
To debug when I am doing console.log(puppyID); , it is giving me NaN
What should be the recommended way to do this ?
I don't see where req.params.family_id is coming from, but it looks like it should be req.params.puppy_id - as below - otherwise it would be undefined, which would not match anything in your database.
function getPuppyStatus(req, res, next) {
var puppyID = req.params.puppy_id;
//call puppy_id, not family_id
//puppy_id is also a string being passed in, it can't be turned into an integer
db.any('select * from puppytable where puppy_id =$1', puppyID)
.then(function (data) {
res.status(200)
.json({
status: 'success',
data: data,
message: 'Retrieved puppies'
});
})
.catch(function (err) {
return next(err);
});
}
You're converting to a number a string "puppy1". This is the reason you're getting NaN.
I don't know what's the type of the id in your column.
You've two options:
id as number, try to send a number instead of a string and you're code should be fine.
id as string, remove the parseInt.
var puppyID = req.params.puppy_id;
what's the proper way to check for undefined values? What I want to do is to have a PUT method that will update those fields that are not empty. For example, if I send req.body.name = 'John' and no req.body.job I want my request to only change the name.
Some code:
router.put('/:id', (req, res) => {
const query = {_id: req.params.id};
const update = {
$set: {
name: req.body.name,
job: req.body.job
}
};
User.findOneAndUpdate(query, update,
(err, userUpdated) => {
if (err) {
console.log('Error while updating');
console.log(err);
} else {
res.send(userUpdated);
}
});
});
This will of course throw an error:
CastError: Cast to number failed for value "undefined" at path "job"
Now I can manually check if req.body.job is empty and if it is set it's value to the value the user had previously, but that seems like a hack, not elegant and a lot of writing for each route.
I have checked the docs but none of the options provided there seem to do the job. I also came across something like express validator but this will probably just do a return if the value is empty. Another options would be to simply send the value from the front-end part.
I'm new to backend development and I'm not sure if I'm doing stuff the "right way". So please, any comment on how it should be done would be nice (also if my code looks odd, feel free to guide me :)), thanks!
You can write your own method to do this.
For example this example
var req = {body: {name: undefined, job: 'yes'}};
const _ = require('lodash');
const out = {};
_(req.body).forEach((value,key) => {
if (!_.isEmpty(value)){
out[key] = value;
}
});
console.log(out);
Is having this output
{ job: 'yes' }
You can also write it as middleware, if you want, if you write it as this
function onlyNotEmpty(req, res, next) => {
const out = {};
_(req.body).forEach((value, key) => {
if (!_.isEmpty(value)) {
out[key] = value;
}
});
req.bodyNotEmpty = out;
next();
}
Then you can write your method with middleware
router.put('/:id', onlyNotEmpty, (req, res) => {
const query = {_id: req.params.id};
const update = {
$set: req.bodyNotEmpty
};
// This will be the same
});
Here is my code :
server.get(url_prefix + '/user/:user_id/photos', function(req, res, next) {
if (!req.headers['x-session-id']) {
res.send({
status: {
error: 1,
message: "Session ID not present in request header"
}
})
} else {
User.findOne({
session_id: req.headers['x-session-id']
}, function(err, user) {
if (user) {
var user_id = req.params.user_id
Album.find({userId : user_id})
.populate('images')
.exec(function (err, albums) {
if (albums) {
albums.forEach(function(album, j) {
var album_images = album.images
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if(like){
albums[j].images[i].userLike = true;
}
})
})
})
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
})
} else
return notify_error(res, "No Results", 1, 404)
})
}
else {
res.send({
status: {
error: 1,
message: "Invalid Session ID"
}
})
}
})
}
})
I am trying to add a extra value (albums[j].images[i].userLike = true;) to my images array, which is inside album array.
The problem is return res.send({ send the data before we get response from the foreach
How can I make it work, so that return should happen only after foreach has completed all the iteration
You will have to wait with invoking res.send until you fetched all the likes for all the images in each of the albums. E.g.
var pendingImageLikes = album_images.length;
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if (like) {
albums[j].images[i].userLike = true;
}
if (!--pendingImageLikes) {
// we fetched all likes
res.send(
// ...
);
}
});
You might need to special case for album_images.length === 0.
Also, this does not take into account that you have multiple albums with multiple images each. You would have to delay res.send there in a very similar way to make this actually work. You might want to consider using a flow control library like first (or any other of your preference, just search for "flow control library") to make this a bit easier.
Also, you might want to consider not relying on semicolon insertion and manually type your semicolons. It prevents ambiguous expressions and makes the code easier to read.
Since you need your code to wait until all of the find operations have completed, I'd suggest you consider using the async package, and specifically something like each (reference). It makes using async loops cleaner, especially when dealing with MongoDB documents and queries. There are lots of nice features, including the ability to sequentially perform a series of functions or waterfall (when you want to perform a series, but pass the results from step to step).
> npm install async
Add to your module:
var async = require("async");
Your code would look something like this:
albums.forEach(function(album, j) {
async.each(album.images, function(album, done) {
Like.findOne({imageID: image._id, userIDs:user._id}, function(err, like){
if(!err && like){
albums[j].images[i].userLike = true;
}
done(err); // callback that this one has finished
})
})
}, function (err) { // called when all iterations have called done()
if (!err) {
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
});
}
return notify_error(res, "No Results", 1, 404);
});
});
I am using filterSeries of the async npm but when I call the truthy next on the object, for some reason it only passes the user and not the parts trying to be taken out of the query...
If you notice what is wrong with my code or have a more efficient way of going about this, because I also, heard that looping through each users and calling a query is a bad idea instead to do a $in or something but not sure how.
The main thing is I want to combine both documents and feed it back as data...
Here is the code:
exports.searchContactPost = function(req, res) {
if(req.body.searchContacts === '') { res.send('Oops you searching for nothing, well here is nothing!'); };
async.waterfall([
function(callback) {
User.find({$or:[
{firstName: req.body.searchContacts.toLowerCase()},
{lastName: req.body.searchContacts.toLowerCase()},
{email: req.body.searchContacts.toLowerCase()}]
}, function(err, users) {
if(err || users.length === 0) { res.send(err);}
callback(null, users)
});
},
function(users, callback) {
async.filterSeries(users, function(user, next) {
console.log(user);
Friend.findOne({userId: req.signedCookies.userid, friend_id: user}, function(err, friend) {
if(err) {
console.log("houston we got a problem.")
}
var object = {'fav': friend.favorites, 'notes': friend.notes, 'labels': friend.labels, 'user': user, 'status':friend.friend_status};
console.log(friend);
next(object.status === 3);
})
}, function(friendResults){
console.log(friendResults);
callback(null, friendResults);
});
}
],
function(err, results) {
res.render('contactListResults', {title: 'Weblio', friendsFound: results});
});
};
The async filter function takes an array of items and filters out items from that array based on a true or false callback. Therefore you will get back a subset of the original array passed into the filter. Which in this case is users, I believe your trying to build up a friend object and return it, which won't work. What you should do instead is just query the database for all friends of the appropriate status instead of using a filter.