Express routes are conflicting - express

I have the following express routes defined:
// Questions Routes
app.route('/questions')
.get(questions.list)
.post(users.requiresLogin, questions.create);
app.route('/questions/:questionId')
.get(questions.read)
.put(users.requiresLogin, questions.hasAuthorization, questions.update)
.delete(users.requiresLogin, questions.hasAuthorization, questions.delete);
app.route('/questions/list/:page')
.get(questions.questionList);
app.route('/questions/count/')
.get(questions.count);
Along with this callback trigger for the questionId route parameter:
app.param('questionId', questions.questionByID);
The expected behavior was to have /questions/count requests route to the count method in a controller, but it is instead being routed to the questionByID method and I'm seeing this error:
CastError: Cast to ObjectId failed for value "count" at path "_id"
...
at exports.questionByID (/path/controllers/questions.server.controller.js:137:56)
...
I think this appears because mongoose is trying to convert the literal "count" from the route to an ObjectId type, which would make sense if I was using the /questions/:questionId route but it doesn't if I'm using /questions/count to make the request.
These are the two relevant methods in questions.server.controller.js
exports.count = function (req, res) {
console.log('attempting to count these damn questions!');
Question.count({}, function (err, count) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
console.log(count);
res.jsonp(count);
}
});
};
/**
* Question middleware
*/
exports.questionByID = function (req, res, next, id) {
Question.findById(id).populate('user', 'displayName').exec(function (err, question) {
if (err) return next(err);
if (!question) return next(new Error('Failed to load Question ' + id));
req.question = question;
next();
});
};
I've fixed it by setting the route to:
app.route('/questionscount')
.get(questions.count);
But it looks bad and I don't want to do it like that, does anyone know why this is happening?
Thanks in advance.

Since express evaluates the routes in addition order, you should put the more specific routes first. In your example it just evaluates "count" as the questionId parameter because it matches the expression.
If you put the app.route('/questions/count/') route before the
app.route('/questions/:questionId') it should work.

If count and question_id have diffirence you can put Regex to route for distinguish them

Related

Nestjs: Cannot PUT, Cannot DELETE (404 not found)

I'm on a task to write a simple CRUD program for a users list, following a similar nestjs example. While GET, POST and GET by id works fine, PUT and DELETE does not work properly. I get 'User does not exist' however user exists in database.
Controller
#Controller('users')
export class UsersController {
constructor(private userService: UsersService) {}
.....
//Update a user's details
#Put('/update')
async updateUser(
#Res() res,
#Query('userid') userID,
#Body() createUserDto: CreateUserDto
) {
const user = await this.userService.updateUser(userID, createUserDto);
if (!user) throw new NotFoundException('User does not exist!');
return res.status(HttpStatus.OK).json({
message: 'User has been successfully updated',
user
})
}
//Delete a user
#ApiParam({ name: 'id' })
#Delete('/delete')
async deleteUser(#Res() res, #Query('userid') userID) {
const user = await this.userService.deleteUser(userID);
if (!user) throw new NotFoundException('Customer does not exist');
return res.status(HttpStatus.OK).json({
message: 'User has been deleted',
user
})
}
Service
// Edit user details
async updateUser(userID, createUserDto: CreateUserDto): Promise<User> {
const updatedUser = await this.userModel
.findByIdAndUpdate(userID, createUserDto, { new: true });
return updatedUser;
}
// Delete a customer
async deleteUser(userID): Promise<any> {
const deletedUser = await this.userModel
.findByIdAndRemove(userID);
return deletedUser;
}
I'm using swagger to perform my tests. I'm passing id as a parameter to find and update user.
Based on your code repository, you aren't using URL Parameters, but rather you are using Query Parameters. The difference in the two is how they are passed to the server and how they are told to the server to listen for them.
Query Parameters
With query parameters, you pass them to your server starting with a ? in the url, and concatenating each one after by using a &. An example could look something like http://localhost:3000?name=Test&id=a26408f3-69eb-4443-8af7-474b896a9e70. Notice that there are two Query parameters, one named name and one named id. In Nest, to get these parameters in your route handler, you would use the #Query() decorator. A sample class could look like
#Controller()
export class AppController {
#Get()
getHello(#Query() query: { name: string, id: string }) {
return `Hello ${name}, your ID is ${id}`;
}
}
Notice how with the url above, the route called is the base route (/), with the query parameters added on.
URL Parameters
URL parameters are a way to dynamically build your routes without needing to specify what each possible URL. This is useful for things like IDs that are dynamically generated. Taking a similar URL as above, the sample URL this time could look like http://localhost:3000/Test/a26408f3-69eb-4443-8af7-474b896a9e70. Notice how this time there is no ? or & and it just looks like a full URL. To specify URL Params in nest, you need to a a colon(:) before the param name in the resource declaration decorator, along with any other part of the path necessary. Then to access the URL Parameters, you need to use the #Param() decorator in the route handler, similar to how you would the #Query() decorator. The class sample for this would be
#Controller()
export class AppController {
#Get(':name/:id')
getHello(#Param() params: { name: string, id: string })
return `Hello ${name}, your ID is ${id}`;
}
}
Problem and Solution
You're currently calling off to http://localhost/users/update/<ID> acting as if you are using URL parameters, but in your route handler you are expecting #Query() to grab the id. Because of this, there is no handler to find /users/update/:id and so you are getting a 404 in return. You can either modify your server to listen for URL Parameters as described above, or you can modify the URL to send the request using Query Parameters instead of URL parameters.

Using promises in Mongoose

I am new to the Promise method used to retrieve multiple database records at the same time and I want to rewrite my existing code to use promises
I have this piece of code in Express:
getController.getData = function(req,res, collection, pagerender) {
var id = req.params.id;
collection.find({}, function(err, docs){
if(err) res.json(err);
else res.render(pagerender, {data:docs, ADusername: req.session.user_id, id: req.params.id});
console.log(docs);
});
};
Now I want to use promises here, so I can do more queries to the database. Anyone know how I can get this done?
First, check if collection.find({}) returns a promise. If it does, then you can call your code like:
collection.find({}).
then(function(docs){
res.render(pagerender, {data:docs, ADusername: req.session.user_id, id: req.params.id});
})
.catch( function(err) {
res.json(err);
})
If you want more calls here, just create new DB call and add another .then block.
I suggest you read the documentation on promises, just to get a general feeling about them (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then). You will also see how you can handle both success and rejection in the same function if you want.

Parse numbers from query strings with bodyParser.urlencoded() in express.js

In the front end, I use jQuery to send a GET request like this:
$.get('/api', {foo:123, bar:'123'), callback);
according to jQuery doc, the 2nd parameter is a plain object that will be converted into query string of the GET request.
In my node express back end, I use body-parser like this:
const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/api', (req, res) => {
console.log(req.query) // req.query should be the {foo:123, bar:'123'} I sent
});
However, req.query turns out to become {foo:'123', bar: '123'} where all the numbers were converted to strings. How can I revert to the exact same object I sent from front end?
Thanks!
HTTP understands that everything is a string same goes for query string parameters. In short, it is not possible. Just convert your data to integer using parseInt()
example
app.get('/api', (req, res) => {
console.log(parseInt(req.query.bar)) // req.query should be the {foo:123, bar:'123'} I sent
});
I wrote this snippet which parses the convertible ones:
query = Object.entries(req.query).reduce((acc, [key, value]) => {
acc[key] = isNaN(+value) ? value : +value
return acc
},{})

Select Mongoose Model Based on Express Route

I think this might be a basic question, but looking for the best approach.
I'm building an express app that should route to one of four different Mongoose models depending on the route.
Something like this:
app.get('/:trial', function(req, res){
var trial = req.params.trial;
trial.find(function(err, records) {
if (err)
res.send(err);
res.json(records); // returns all trial records in JSON format
});
});
I have 4 mongoose models named: trial1, trial2, trial3, trial4. I would like the trial parameter of the URL to determine which collection gets queried. Obviously the above won't work, but what should I do instead of rewriting the route four times instead?
Thanks in advance!
You can get models by name:
var mongoose = require('mongoose');
app.get('/:trial', function(req, res){
var trial = req.params.trial;
mongoose.Model(trial).find(function(err, records) {
if (err) {
// Return when we end the response here...
return res.send(err);
}
res.json(records); // returns all trial records in JSON format
});
});
Depending on circumstances, I would validate the value of trial first (so requesting /User doesn't dump all users to the client, for instance).

What is the role of exec() and next() call in cascade delete in mongoose middleware?

I'm new to using mongoose middleware and don't know if I'm following it well. Here is the purpose. After saving department, I want to populate university and save departmentId inside university object.
DepartmentSchema.post('save', function(next) {
var departmentId = this._id;
University.findOne({
_id: this.university
}, function(err, university) {
if (!university.departments) {
university.departments = [];
}
university.departments.push(new ObjectId(departmentId));
university.save(function(err) {
if (err) return console.log('err-->' + err);
// saved!
});
});
});
This is working fine but I'm not sure why in Cascade style delete in Mongoose they have used exec() and next() calls. Could you please tell me the purpose of these calls? I don't know what they do and not able to find relevant documentation. I just want to make sure I'm not missing anything.
clientSchema.pre('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
Sweepstakes.remove({
client_id: this._id
}).exec();
Submission.remove({
client_id: this._id
}).exec();
next();
});
Post middleware doesn't have reference to the next function and you cant do any flow control. Its actually passing the department that just got saved, so your code can be something like this:
DepartmentSchema.post('save', function(department) {
var departmentId = department._id;
In pre middleware you have access to the next middleware in the order of execution. Which is the order of definition on a particular hook.
// hook two middlewares before the execution of the save method
schema.pre('save', pre1);
schema.pre('save', pre2);
function pre1(next) {
// next is a reference to pre2 here
next()
}
function pre2(next) {
// next will reference the hooked method, in this case its 'save'
next(new Error('something went wrong');
}
// somewhere else in the code
MyModel.save(function(err, doc) {
//It'll get an error passed from pre2
});
Mongoose also gives you the ability to execute pre middlewares in parallel, in this case all middlewares will be executed in parallel but hooked method will not execute till the done is called from each middleware.
As for the exec() function, there are two ways of executing a query in Mongoose, either pass a callback to the query or chain it with an exec(): User.remove(criteria, callback) or User.remove(criteria).exec(callback), if you don't pass a callback to the query, it'll return a query object and it won't execute unless you chain it with exec()