Select Mongoose Model Based on Express Route - express

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).

Related

Spread operator not working with express.js

app.post('/addfriends', (req, res) => {
let newFriend = req.body.name;
friends.push(newFriend)
res.redirect('/friends')
})
This is a code i'm working on. just wondering how can i use {[...fiends], newFriend} in place of .push() mentioned above.
{[...fiends], newFriend} is not going to achieve what you are trying to do.
Try this (assuming your friends array is mutable):
let friends = ['Joe', 'Chris'];
app.post('/addfriends', (req, res) => {
const { name } = req.body;
friends = [...friends, name];
res.redirect('/friends')
})
P.S. As a sidenote, I would suggest you to read through RESTful API design guidelines for how to construct your endpoints.
It's generally bad API design to use verbs in endpoint url names (like "add", "delete" etc...). That's what REST methods are for.

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.

API Request Pagination

I am making a simple API request to Github to get all the repositories. The problem is that Github has a limitation and the max that it can send is 100 per request. There are users that have more than 100 repositories and I don't know how to access it or how to make pagination.
I am making GET request with Axios like this:
https://api.github.com/users/<AccountName>/repos?per_page=100
I can also put page number like so
https://api.github.com/users/<AccountName>/repos?page=3&per_page=100
But how do I make this work in app without making 10 API requests? I wouldn't even know how many requests I should make because I don't know what is the number that gets returned, does somebody have 100 or 1000 repos? I would like for everything to be returned and saved in array, for example.
EDIT:
Example: I am passing in accountName
var config = {
headers: {'Authorization': `token ${ACCESS_TOKEN}`}
}
const REQUEST: string = 'https://api.github.com/users/'
const apiCall = {
getData: async function (accountName) {
const encodedAccountName = encodeURIComponent(accountName)
const requestUrl = `${REQUEST}${encodedAccountName}`
const user = await axios.get(requestUrl, config)
// This return user and inside of user there is a link for fetching repos
const repo = await axios.get(`${user.data.repos_url}?per_page=100`, config)
...
You can get the repo count by requesting from the user account URL first. For example here is mine:
https://api.github.com/users/erikh2000
The response there includes a "public_repos" value. Bam! That's the magic number you want.
You next need to make multiple fetches if the repo count is over 100. I know you didn't want to, but hey... can't blame web services for trying to conserve their bandwidth. The good news is you can probably put them in a Promise.all() block and have them all fetch together and return at once. So code like...
const fetchAllTheRepos = (userName, repoCount) => {
const MAX_PER_PAGE = 100;
const baseUrl = 'https://api.github.com/users/' + userName +
'/repos?per_page=' + MAX_PER_PAGE;
//Start fetching every page of repos.
const fetchPromises = [], pageCount = Math.ceil(repoCount /
MAX_PER_PAGE);
for (let pageI = 1; pageI <= pageCount; ++pageI) {
const fetchPagePromise = fetch(baseUrl + '&page=' + pageI);
fetchPromises.push(fetchPagePromise);
}
//This promise resolves after all the fetching is done.
return Promise.all(fetchPromises)
.then((responses) => {
//Parse all the responses to JSON.
return Promise.all( responses.map((response) => response.json()) );
}).then((results) => {
//Copy the results into one big array that has all the friggin repos.
let repos = [];
results.forEach((result) => {
repos = repos.concat(result);
});
return repos;
});
};
//I left out the code to get the repo count, but that's pretty easy.
fetchAllTheRepos('erikh2000', 7).then((repos) => {
console.log(repos.length);
});
Simultaneously fetching all the pages may end up being more than Github wants to let you do at once for those accounts with lots of repos. I would put some "good citizen" limit on the number of repos you'll try to get at once, e.g. 1000. And then see if api.github.com agrees with your definition of a good citizen by watching for HTTP error responses. You can get into throttling solutions if needed, but probably a "grab it all at once" approach like above works fine.
On the other hand, if you are spidering through multiple accounts in one session, then maybe design the throttling in from the beginning just to you know... be nice. For that, look at a queue/worker pattern.

JSON API design - express

I want to write a JSON API.
My problem is, that sometimes I want to query for an ID, sometimes for a String.
One option would be to add a querystring, for example:
example.com/user/RandomName
example.com/user/1234556778898?id=true
and use it like:
api.get('user/:input', function(req, res) {
if(req.query.id) {
User.find({ '_id': req.params.input }, cb);
} else {
User.find({ 'name': req.params.input }, cb);
}
};
But this seems like bad practice to me, since it leads to a bunch of conditional expressions.
Are there more elegant ways?
I would suggest handling two endpoints. One for getting ALL the users and one for getting a SPECIFC user by ID.
example.com/users
example.com/users/:id
The second endpoint can be used to find a specific user by id.
The first endpoint can be used to find all users, but filters can be applied to this endpoint.
For example: example.com/users?name=RandomName
By doing this, you can very easily create a query in your Node service based on the parameters that are in the URL.
api.get('/users', function(req, res) {
// generate the query object based on URL parameters
var queryObject = {};
for (var key in req.query) {
queryObject[key] = req.query[key];
}
// find the users with the filter applied.
User.find(queryObject, cb);
};
By constructing your endpoints this way, you are following a RESTful API standard which will make it very easy for others to understand your code and your API. In addition, you are constructing an adaptable API as you can now filter your users by any field by adding the field as a parameter to the URL.
See this response for more information on when to use path parameters vs URL parameters.

Express routes are conflicting

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