Multiple optional query parameters in Express? - node.js

I am using Express and I have a route which makes a GET request to my 'challenges' collection in MongoDB. This will find a specific challenge based on the section number and challenge number.
My code is as follows:
// #route GET api/challenge?section=_&challenge=_
// #description Get challenge by section number and challenge number
// #access Private
router.get('/', auth, async (req, res) => {
try {
const challenge = await Challenge.find({
section_no: req.query.section,
challenge_no : req.query.challenge,
});
if (!challenge) {
return res.status(400).json({ msg: 'Challenge not found' });
}
return res.json(challenge);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
Now this works fine when querying it on Postman. When I enter both query param values 'section' & 'challenge' I get the desired result and it is able to return that particular challenge data.
But my question is:
How can I modify this code so that I can optionally query ONLY section OR BOTH section & challenge. So for example, if I was to query the following:
api/challenge?section=1
It would return all data on all challenges in section 1
But I still want to be able to return a specific challenge by using the query:
api/challenge?section=1&challenge=1
And it would return all data on the first challenge in section 1
Thank you in advance!

You can create a filter object where you put your query strings if exists here is what the code looks like:
router.get('/', auth, async (req, res) => {
try {
const filter = {};
if (req.query.section) filter.section_no = req.query.section;
if (req.query.challenge) filter.challenge_no = req.query.challenge;
const challenge = await Challenge.find(filter);
if (!challenge) {
return res.status(400).json({ msg: 'Challenge not found' });
}
return res.json(challenge);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});

One alternative is to use the spread operator, since the keys are the same as database query:
router.get('/', auth, async (req, res) => {
try {
const challenge = await Challenge.find({
...req.query,
});
if (!challenge) {
return res.status(400).json({ msg: 'Challenge not found' });
}
return res.json(challenge);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
In this case, note that query should have the keys "challenge_no" and "section_no" or, the inverse, query should support "challenge" and "section" keys from the query object.

Related

How to store redis.get value to a variable

I'm trying to store Redis key value to a variable in nodejs, something like
let gPost;
redis.get("posts", async function (err, post) {
if (err) console.log(err);
if (post) gPost = post;
}
but this approach is giving me undefined. Is there any way by which I can store value to Redis? I've already searched for it and a few posts suggested using callbacks. But what I basically want is something like this:
router.post("/:id/likes", async (req, res) => {
try {
redis.get(`posts.${req.params.id}.likes`, function (err, likeCount) {
if (err) console.error(err.message);
redis.get(`posts.${req.params.id}`, async function (err, post) {
if (err) console.log(err);
if (post) {
await customCallback(likeCount, post, req, res);
const retPost = JSON.parse(post);
return res.send({ retPost, redis: true });
} else {
try {
const reqPost = await Post.findById(req.params.id).lean().exec();
redis.set(`posts.${req.params.id}`, JSON.stringify(reqPost));
await customCallback(likeCount, reqPost, req, res);
const retPost = JSON.parse(post);
return res.send({ retPost, redis: false });
} catch (err) {
console.log(err);
}
}
});
console.log(upPost);
});
} catch (err) {
return res.status(500).send({ message: err.message });
}
});
So, here I want to increase my likes count on a post. But I don't want to hit any unnecessary requests to the database. Here first I'm getting posts.id.likes and inside it, I'm trying to fetch that post. If a post is found I'll increase my likes there only. Else, I'll make an API call to the database to fetch that post. Can you where I'm getting it wrong, or any other efficient approach I can use? Thanks.
If you're using a recent version of node-redis, you can just use promises.
Your code seems to simplify to something like
/**
* Get a post from Redis or the database.
*
* If found in the database, caches it in Redis.
*
* Returns a pair: post object and whether it was from Redis.
* #param id Post ID.
*/
async function getPost(id) {
const redisKey = `posts.${id}`;
const postData = await redis.get(redisKey);
if (postData) {
return [JSON.parse(postData), true];
}
const postObj = await Post.findById(id).lean().exec();
await redis.set(redisKey, JSON.stringify(post));
return [postObj, false];
}
router.post("/:id/likes", async (req, res) => {
try {
const { id } = req.params;
const [postObj, fromRedis] = await getPost(id);
const likeCount = await redis.get(`posts.${id}.likes`);
await customCallback(likeCount, postObj, req, res);
return res.send({ postObj, redis: fromRedis });
} catch (err) {
return res.status(500).send({ message: err.message });
}
});

GET endpoint returns a 404

I am trying to retrieve specific user data from my Postgres DB.
This code, which retrievs all user data works:
app.get("/employees", async (req, res) => {
try {
const allEmployees = await pool.query("SELECT * FROM employees");
res.json(allEmployees.rows);
} catch (err) {
console.error(err.message);
}
});
But this code meant to retrieve one user doesn't. It returns a 404 on Postman.
app.get("/employees/:id", async (req, res) => {
try {
const { id } = req.params;
const oneEmployee = await pool.query("SELECT * FROM employees WHERE emp_id = $1", [
id
]);
res.json(oneEmployee.rows[0]);
} catch (err) {
console.error(err.message);
}
});
I don't seem to figure out what the problem is.
#AnujPancholi an update. I used the node-postgres queries documentation and changed my code as follows:
app.get("/employees/:emp_id", async (req,res) => {
const query = {
// give the query a unique name
name: 'fetch-user',
text: 'SELECT * FROM employees WHERE emp_id = $1'
}
query.values = [req.params.emp_id];
// callback
await pool.query(query, (err, response) => {
if (err) {
console.log(err.stack);
} else {
res.json(response.rows);
}
});
});
then on Postman my endpoint to a GET route
http://localhost:3000/employees/4
I did not enter any values on the params section. Thanks for pointing me in the right direction, especially on the Postman part.

Refactoring RESTful API into smaller functions

Background
I have a NodeJS app that is meant to be used as a RESTful API. It is connected with a MongoDB database in the backend using Mongoose. The app is built upon the idea of nested documents. It stores wikis, sections and notes with the following schema:
const noteSchema = new mongoose.Schema({ title: String, content: String });
const sectionSchema = new mongoose.Schema({ title: String, notes: [noteSchema] });
const wikiSchema = new mongoose.Schema({ title: String, sections: [sectionSchema] });
All of which are accessed via a single model of the wiki:
const wikiModel = mongoose.model("Wiki", wikiSchema);
A user can do GET, POST, PUT, DELETE requests on each of the endpoints to manipulate the data inside. If someone wants to ping the Notes endpoint (the furthest down in the hierarchy), it must first check the wiki and then the section endpoint, to ensure that each of them exists.
Here's an example:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
wikiModel.findOne({ title: req.params.wikiTitle }, function(err, wiki) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (wiki) {
const sectionTitle = req.params.sectionTitle;
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (section) {
const noteTitle = req.params.noteTitle;
wikiModel.findOne({ 'sections.notes.title': noteTitle }, function(err, n) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (n) {
const section = n.sections.find((s) => { return s.title === sectionTitle; });
const note = section.notes.find((n) => { return n.title === noteTitle; });
if (note.content) {
res.send('\n' + note.title + '\n\n' + note.content);
} else {
res.send('\n' + note.title + '\n\n[ No content to show ]');
}
} else {
res.send('\nNo such note exists');
}
});
} else {
res.send('\nNo such section exists');
}
});
} else {
res.send('\nNo such wiki exists');
}
});
});
This is a very lengthy method and the first two queries are actually frequently throughout the app. I also understand a MongoDB query is an asynchronous operation and thus, why I put each consequent MongoDB query within it's parent (the one I wish to finish before that one begins).
Question
Is there a way to split each MongoDB query into its own method or introduce promises in a way that would shorten the code? I would rather prefer advice that ultimately causes the splitting of my code into individual methods as what you see above is one of many endpoints which all use the same queries.
So in the end result I would like to have something close to the likes of:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
if (getWiki(req.params.wikiTitle)) {
// Continue with second query
if (getSection(req.params.sectionTitle)) {
// Continue with third query...
}
}
});
function getWiki(wikiTitle) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return wiki
} else {
res.send('No wiki found');
return null;
}
});
}
function getSection(sectionTitle) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return section
} else {
res.send('No section found');
return null;
}
});
}
I am hoping this will significantly cut the length of code and also utilise re-usability of code. Any advice on how I could come close to achieving something like this is welcome.
You can definitely use callbacks in the same way as the ones call your model. For example:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
getWiki(req.params.wikiTitle, function (err, title) {
if (err) {
return res.send(err);
}
getSection(req.params.sectionTitle, function (err, section) {
if (err) {
return res.send(err);
}
// Todo: use title and section, etc...
});
});
});
function getWiki(wikiTitle, cb) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return cb(null, wiki);
} else {
return cb('No wiki found');
}
});
}
function getSection(sectionTitle, cb) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return cb(null, section);
} else {
return cb('No section found');
}
});
}
This is a standard way of using async functions in node. By convention, the first parameter is always an error parameter.
If you want your code to be cleaner, you can try to use guard clauses / early outs to exit error cases early. This will cut down on your need for if / else conditional statements.
You can also look into libraries like async for cleaner chaining of asynchronous calls.
When you are comfortable, you can also look into using promises and the 'async' javascript keyword (different from the async library above, confusing, I know) which will also allow you to cut down on the lines of code you have to write to get nice async code.
You should use async functions (Promises) like
app.get('somePath', async (req, res, next) => {
try {
const doc = await model.find({ someField: 'some value' }).exec(); // exec returns promise
res.send({ document: doc });
} catch (error) {
// here you can handle all errors or/and call next for the error middleware
next(error);
}
});

Mongoose check if user owns document before updating

I am attempting to check if a user owns a document before updating it or deleting it, and would like to keep this as DRY as possible. Ideally, I would not have to make two calls to the database where I would first findById().then(doc => {check if user owns document and then -> doc.findByIdAndUpdate() }) but rather keep this as one call to the DB.
I am constantly having to execute this check on express routes and have thought about implementing this layer of logic on the mongoose .pre('update') middleware. but am unaware how to pass the incoming userid from the req object to my middleware validation function?
Are there any better layers to implement this checking functionality? or am I going to have to make the two requests to the database every time I want to check if a user owns a document and write this out in every express route?
My current implementation is:
const addDocToDoc = (req, res, next) => {
let doc1id = req.params.id;
let doc2id = req.params.doc2id;
Doc1.findById(doc1id)
.then(doc1 => {
if(userCanAlter(doc1, req.user, res)) {
doc1.doc2s.push(doc2id)
return doc1.save().then(updatedDoc1 => res.send(updatedDoc1))
}
}).catch(next)
}
Where userCanAlter() looks like this:
function userCanAlter(instance, user, res) {
if (!instance) { res.status(404).send("Document does not exist."); return false}
if (instance.user != user) { res.status(401).send("User unauthorized"); return false}
else return true;
}
Obviously, this is a very simple update but the more complex updates would require more configuration before saving.
Current implementation in question found to be the best & DRY’est implementation.
You can simply wrap your user in find query and use findOne(), Something like:
const addDocToDoc = (req, res, next) => {
const {
user = ''
} = req;
const {
id = '', doc2id = ''
} = req.params;
Doc1.findOne({
_id: id,
user
})
.then(doc => {
if (!doc) {
return res.status(400).json({
message: 'User Not Found!!'
});
}
doc.doc2s.push(doc2id);
doc.save()
.then(updatedDoc1 => res.status(200).json(updatedDoc1))
.catch(err => res.status(500).json({
message: 'Error While Updating!!',
error: err
}));
})
.catch(err => res.status(500).json({
message: 'Error While Fetching!!',
error: err
}));
}
Also I'd suggest if you work a bit on naming things, as this may mess up things a few times.
In case if you wanna throw specific error for unauthorized user, you can stick to your way of implementation, just don't need a separate method to check ownership. I've simplified it with async/await and the code is:
const addDocToDoc = async (req, res, next) => {
try {
const {
user = ''
} = req;
const {
id = '', doc2id = ''
} = req.params;
const doc = await Doc1.findById(id);
if (!doc || !doc.user || doc.user !== user) {
return res.status(401).json({
message: 'Unauthorized User!!'
});
}
doc.doc2s.push(doc2id);
const updatedDoc1 = await doc.save();
return res.status(200).json(updatedDoc1);
} catch (err) {
res.status(500).json({
message: 'Error While Updating Record!!',
error: err
});
}
}
Ps: You may need some modification as i couldn't get a chance to run it.
Hope this helps :)

Mongoose routes for updating fields provided

I've been reading some CRUD / Mongoose guides, but haven't a good explainer for conditionally updating fields.
So for example, an action called updateItem is used in one place to update item.price but in another place it updates item.color. Does anyone know a good explanation or tutorial for Mongoose CRUD APIs that shows this?
I'm getting the blow code to work fine, but I have a feeling it could be cleaner :)
Thanks!!!
router.put('/tasks/:id', (req, res) => {
Task.findByIdAndUpdate(req.params.id,
req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours }, { new: true })
.then(task => {
res.status(201).json(task)
})
.catch(err => {
console.log('Our error', err)
})
});
Another approach you could take is to first retrieve the object, and then only update the value if it is passed into the put request. An example of that could be something like this:
router.put('/tasks/:id', (req, res) => {
let price = req.body.price;
let color = req.body.color;
Task.findById(req.params.id, function (err, task) {
if (err) return handleError(err);
task.color = color || task.color;
task.price = price || task.price;
task.save(function(err, updatedTask) {
if err return handleError(err);
return res.send(updatedTask);
});
});
});
Here's another cleaner approach using async-await functions:
// Import promisify from utils
const promisify = require('utils').promisify;
// Wrap findByIdAndUpdate into a promise
const updateOwnerPromise = promisify(Task.findByIdAndUpdate);
// Write an async handler now
updateOwnerPromiseAsync = async (req, res) => {
const replacementObject = req.body.owner ? { owner: req.body.owner } : { hours: req.body.hours };
try {
await updateOwnerPromise(replacementObject, { new:true} );
return res.status(200).send({ message: 'Owner updated successfully!' });
} catch(err) {
// TODO: handle error here
console.log('Our error', err)
return res.status(500).send({ message: 'Failed to update owner, because of some issue at the server!' });
}
}
// Modify the express route with the handler
router.put('/tasks/:id', updateOwnerPromiseAsync);

Resources