expressjs: Best way to handle multiple optional route params - node.js

I'm using nodejs Express. I would like to have a router that receive id or username as request param as below:
router.get('/:id?/:username?', authMiddleware, function (req, res, next) {
models
.User
.findAll({
where: {
$or: {
id: req.params['id'],
username: req.params['username']
}
}
})
.then(function (rows) {
res.send(rows);
})
}
However, express seems to not understand my request on:
http://localhost:3000/api/1.0/users//david
Which I would like to query user by username instead of id
The only way works right now is defining a route as: /:id_or_username as below:
router.get('/:id_or_username', function (req, res, next) {
models
.User
.findAll({
where: {
$or: {
id: req.params['id_or_username'],
username: req.params['id_or_username']
}
}
})
.then(function (rows) {
res.send(rows);
})
}
But it is kind of dirty and I'm not happy with that code.
Is that possible to define router in this case using REST style instead of :idOrName param?

Assuming your id is an int, you can do something like this:
router.get('/:id_or_username', function (req, res, next) {
var where = {}
var param = req.params['id_or_username']
if (isNaN(param)) {
where.username = param
} else {
where.id = param
}
models.User.findAll({where}).then(function (rows) {
res.send(rows)
})
}
Now you can send the id or the username using the same paramether

I am not sure how your system is setup but this sounds like a better use for a req.query instead of req.param.
You should use req.query when you are not sure what the input might be, and use req.param when passing information you are sure will be there. There is probably a way to do this with req.param but I am not sure I can think of a reason why you'd want to do it that way.
This way you could do an action like:
action = "/users/"+ id ? id : username
and then req.query for it.

Related

Input field function when aggregating data in node and mongoDB

I am trying to develop a data aggregation feature for an application. The user should be able to enter a name and it'll search the database collection for that name and return the collection.
I am trying to implement it using node.js but i am confused as to how to pass a parameter for the name.
Controller:
exports.DAFacility_author_search = (req, res) => {
DAFacility.aggregate([
[
{
'$match': {
'author': author
}
}
]
]).then((DAFacility) => {
res.send(DAFacility);
})
.catch((e) => {
res.send(e);
});
};
Route:
router.get('/DAFacilityAuthor', DAFacilityController.DAFacility_author_search)
For the 'author': author i am trying to use author as the variable name but i dont exactly know how to structure the controller and router to take in a parameter so that i can retrieve a value on postman.
Any help would be great
Thank you
I suppose that your request in postman will be something like:
{
"author": "foo",
...
}
When your request is received by the API, the body is available in your req object in the controller, so, if you want to use author field in your aggregate, you simply access: req.body.author.
Your controller could have the next structure:
export const DAFacilityController = {
DAFacility_author_search: (req, res) => {
DAFacility.aggregate([
{'$match': {'author': req.body.author}}
])
.then((DAFacility) => {
res.send(DAFacility);
}).catch((e) => {
res.send(e);
});
},
DAFacility_another_method: (req, res) => {...},
}
The router is ok:
router.get('/DAFacilityAuthor', DAFacilityController.DAFacility_author_search);
router.get('/DAFacilityAuthor/<something>', DAFacilityController.DAFacility_another_method);
I hope I helped

Update record based on username given in Request body

I need to update value in Group db Group_name to the value send in Json payload.
Db schema
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: String,
Group_name: {
type: String,
default: '',
}
});
mongoose.model('User', UserSchema);
And API request
router.put('/join', async(req, res) => {
try {
const data = await User.updateOne(req.params.username, {
Group_name: req.body.Group_name
});
console.log(data)
res.send({ msg: "Group Updated!!!" })
} catch (err) {
console.error(err.message);
res.sendStatus(400).send('Server Error');
}
});
currently its updating only first record which is incorrect , my requirement is to check for all records based on username given and according to username given in request parameters ,i will update value of Group_name to the value sent in request body.
can anyone help me ?
Modify query condition.
const data = await User.updateOne(
{ username: req.params.username },
{ $set: { Group_name: req.body.Group_name } }
);
First of all, understand the difference between req.body & req.params
req.body means hidden parameters sent in request body like in post or put requests.
req.params means defined paramters in URL. For this, you must have it defined in your route like below
router.put('/join/:username', async (req, res) => {
// ^^^^^^^^ here it is defined, now you can access it like
const username = req.params.username;
//or
const {username} = req.params; // destructuring
}
there is one more thing and that is
req.query means undefined paramters attached to URL with ?/&
If you want to give username without pre defining like /join?username=john then use req.query
router.put('/join', async (req, res) => {
const {username} = req.query;
}
Then you should use updateMany() function instead of updateOne()
try {
const {username} = req.params;
const {Group_name} = req.body;
const data = await User.updateMany(
{username}, // find as many users where username matches
{Group_name} // update group name from body
);
console.log(data);
The consoled data would be like { n: 2, nModified: 2, ...} because the update queries don't return updated documents but status of the query. If you want to get updated record set, you have to query again with find().
// after update
const updatedRecord = await User.find({ username });
console.log(updatedRecord);
::POSTMAN::
Postman has two types of parameters
Params
Body
If you add in Params it will be added in URL /join?username=john#email.com&Group_name=GroupB and you have to access it in code with req.query.username or req.query.Group_name
If you add in Body it will be hidden and can be accessed with req.body.Group_name etc
Hope it helps!

How to update document in MongoDB through Mongoose

I am trying to update my document field with the following code, but I catch an error "Cannot GET /updation".
My code:
router.put('/updation', (req, res) => {
const query = { email: "babra#arzepak.com" };
const newEmail = { $set: { name: "babra", email: "nadralatif952#gmail.com" } };
Registration.updateOne()
.then((registration) => {
console.log("updating");
res.render('index', { title: 'updating registrations', registrations });
})
.catch(() => {
res.send('Sorry! Something went wrong.');
});
});
send a request with PUT method and you didn't use of query and newEmail as arguments for updateOne() pass them, and in new version of mongoose you don't need to $set for updating so change your code like this :
router.put('/updation', (req, res) => {
const query = { email: "babra#arzepak.com" }
const newEmail = { name: "babra", email: "nadralatif952#gmail.com" }
Registration.updateOne(query,newEmail)
.then((registration) => {
console.log("updating")
res.render('index', { title: 'updating registrations', registrations });
})
.catch(() => { res.send('Sorry! Something went wrong.'); });
})
Wrong Method
In your router you registered a route '/updation' for PUT method.
Hence only request with method PUT will be handled by your router. The error Cannot GET /updation means you are trying to handle a request with GET method which is simply not registered by your router.
A - If this code is supposed to get triggered when you visit the page via browser, then you should register the route for GET method, like router.get('/updation', ... ),
or
B - If this code is supposed to run seperate from your view (like a REST API should), then use a PUT request and end the request inside your router with res.status(200).json(yourdata)
📑 Sidenote :
It looks like you did not completely setup your mongoose query, I mean you defined the query but you don't use it in the database request. To use the defined query you propbably should change your code, according to the mongoose docs : https://mongoosejs.com/docs/api/model.html#model_Model.updateOne . I am not including a specific code example here, since it's not part of the question.

Update data in MongoDB using Mongoose and Node.js

I am trying to update certain info in a user collection, when the user is visiting a page.
But my method doesn't work. Can anyone help to get it fixed.
app.get('/add-your-accommodation/apartment-type', (req, res, next) => {
if (req.isAuthenticated()) {
res.render('apartment-type.ejs')
} else {
res.render('login.ejs')
}
var id = req.params.id
if(mongoose.Types.ObjectId.isValid(id)) {
User.findByIdAndUpdate(id, {$set: {accomtype: 'house'}},{new: true})
}
});
Your req.params.id is undefined since there is no mention of it in the route path. You can do this,
app.get('/add-your-accommodation/apartment-type', (req, res) => {
if (!req.isAuthenticated()) {
return res.render('login.ejs')
}
res.render('apartment-type.ejs')
var id = req.user._id //since you're using passport (LocalStrategy)
if(mongoose.Types.ObjectId.isValid(id)) {
User.findByIdAndUpdate(id, {$set: {accomtype: 'house'}})
}
})
Now when you call your API, do it like this,
GET /add-your-accommodation/apartment-type
I agree with #kedar-sedai, when you update/change something in your DB, you should not use a GET request. A good practise would be to use the PUT method, even if you have nothing to pass in the body. It makes it easier for you and other developers to understand what your code does at a glance.
Here are 4 HTTP requests that will work in most of the use cases :
GET
You want to retrieve information from your DB (ex: get users, get all the apartment types...)
POST
You want to add information (ex: register user, add an apartment, ...), or send information using the body of the POST request (ex: login, ...)
PUT
You want to update a value (ex: change username, change an apartment type, ...)
DELETE
You simply want to delete something in your DB (ex: delete a user...)
Try findOneAndUpdate. Also, use callback in your query function for getting the error or result.
app.get('/add-your-accommodation/apartment-type/:id', (req, res, next) => {
if (req.isAuthenticated()) {
res.render('apartment-type.ejs')
} else {
res.render('login.ejs')
}
var id = req.params.id
if(mongoose.Types.ObjectId.isValid(id)) {
User.findOneAndUpdate({_id: mongoose.Types.ObjectId(id)}, { $set: { accomtype:'house' } },(err, result)=>{
if (err) throw new Error(err);
console.log(result)
return
})
}
});

How to filter data for API in Node/MongoDB?

I'm trying to write a simple API to get products from database. I want to filter this data by given specific params, which are optional:
router.get('/articles/:type?/:year?/:model?', api_controller.articles);
My controller looks like this:
exports.articles = async (req, res, next) => {
const articles = await Article.find({
'models': {
$elemMatch: {
'year': req.params.year,
'category': req.params.type,
'name': req.params.model,
}
}
});
res.send(articles);
}
However this works only for URL with 3 params. How to flexible get data from database by querying API with no params, single param and multiple params?
So, you can't really do routing exactly like that. The concept of 'optional' route parameters doesn't work well with path params. For example, you're not able to search on just model the way you have it, if you want to specify model you need to put in type and year for sure.
If you must do it with the path, then you'll need to define three routes in reverse order of granularity:
router.get('/articles/:type/:year/:model', api_controller.articles);
router.get('/articles/:type/:year', api_controller.articles);
router.get('/articles/:type', api_controller.articles);
Not sure if your query is right since you didn't share your data structure, but in general if you try and match on null, mongodb will ignore the param, so you're set there I think.
Now, if it were me, I wouldn't match on path at all. Instead I'd just use a querystring for the search parameters, like so:
router.get('/articles', api_controller.articles);
Then in the controller:
// now someone calls /articles?year=2017&type=whatever or could call /articles?model=some-name
exports.articles = async (req, res, next) => {
const articles = await Article.find({
'models': {
$elemMatch: {
'year': req.query.year,
'category': req.query.type,
'name': req.query.model,
}
}
});
res.send(articles);
}

Resources