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 });
}
});
Related
I'm fairly new to nodejs and have stumbled into a problem with my code.
The documentation for SQL Server and a guide I found on Youtube both handle their code this way, but after starting to use bycrypt I've noticed my function ends after the request is complete although I'm using .then().
Anyways, here's my code so far:
router.post('/login', (req, res) => {
getLoginDetails(req.body.username, req.body.password).then(result => {
console.log(result);
res.json(result);
})
});
async function getLoginDetails(username, password) {
await pool1Connect;
try {
const request = pool1.request();
request.input('username', sql.NVarChar, username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
if (err) {
return ({err: err})
}
if (result.recordset.length > 0) {
bcrypt.compare(password, result.recordset[0].user_password, (err, response) => {
if (response) {
console.log(result.recordset);
return(result.recordset);
} else {
return({message: "Wrong password or username!"})
}
})
return(result)
} else {
return({message: "user not found!"})
}
})
} catch (err) {
return err;
}
}
I tried logging both the request and the return value from the function getLoginDetails and the request log came faster, so I assume it's not waiting for the program to actually finish and I can't figure out why...
Sorry if that's obvious, but I'd love to get some help here!
EDIT:
router.post('/login', async (req, res) => {
// res.send(getLoginDetails(req.body.username, req.body.password))
await pool1Connect
try {
const request = pool1.request();
request.input('username', sql.NVarChar, req.body.username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
console.log(result);
bcrypt.compare(req.body.password, result.recordset[0].user_password, (err, response) => {
if (response) {
res.send(result);
} else {
res.send('wrong password')
}
})
//res.send(result)
})
} catch (err) {
res.send(err);
}
});
This code works, but when I tried to encapsulate it in a function it still didn't work.
#Anatoly mentioned .query not finishing in time which makes sense, but I thought mssql .query is an async function?
Your problem arises from an wrong assumption that callbacks and promises are alike, but on the contrary callbacks don't "respect" promise/async constructs
When the program hits the bottom of getLoginDetails the progrm execution has already split into 2 branches one branch returned you the (empty) result whereas the other one still busy with crypto operations.
Though it is true that an async function always returns a promise but that doesn't cover any future callbacks that might execute inside it. As soon as node reaches the end of function or any return statement the async function's promise get resolved(therefore future callbacks are meaningless), what you can do instead is handroll your own promise which encampasses the callbacks as well
router.post('/login', (req, res) => {
getLoginDetails(req.body.username, req.body.password))
.then((result)=>{
res.send(result);
})
.catch((err)=>{
res.send(err);
})
});
async function getLoginDetails(username, password) {
await pool1Connect
return new Promise( (resolve,reject) => {
try {
const request = pool1.request();
request.input('username', sql.NVarChar, username);
request.query('SELECT * FROM users WHERE username = #username', (err, result) => {
console.log(result);
bcrypt.compare(password, result.recordset[0].user_password, (err, response) => {
if (response) {
resolve(result);
} else {
resolve('wrong password')
}
})
})
} catch (err) {
reject(err);
}
});
}
You didn't return any result to getLoginDetails. Either you use async versions of request.query and bcrypt.compare (if any) or wrap request.query to new Promise((resolve, reject) like this:
const asyncResult = new Promise((resolve, reject) => {
request.query('SELECT ...
...
if (err) {
resolve({err: err}) // replace all return statements with resolve calls
}
...
})
const queryResult = await asyncResult;
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.
I'm using mongoCleint to connect to the database using node server. Able to post the data to the database and able to read back the data in JSON.stringify(items) but not able to return it. Problem is that res.json(getRequest) in not giving back any json despite 'JSON.stringify(items)` is giving me a list of items. Am I not doing it right?
app.get('/getItems', function (req, res) {
var getRequest;
MongoClient.connect(url, function(err, client) {
if(err){
console.log("there is an error")
} else {
console.log("running get request")
const db = client.db(dbName);
db.collection('documents').find({}).toArray(function(err, items) {
if(err) {
console.error(err)
} else {
console.log(JSON.stringify(items))
getRequest = JSON.stringify(items)
}
})
client.close();
}
});
console.log(res.json(getRequest))
return res.json({ success: true, data: getRequest });
})
The callbacks run asynchronously but you don't wait for them. Consider running the following code:
app.get('/getItems', async function (req, res) {
const client = await MongoClient.connect(url)
const db = client.db(dbName)
const getRequest = await db.collection('documents').find({}).toArray()
client.close()
return res.json({ success: true, data: getRequest })
})
I tried to comment #Cider 's response but I don't have enough points.. I think it's ok but is lacking the res.json so It won't return anything.
I'd do it this way:
app.get('/getItems', async function (req, res) {
const client = await MongoClient.connect(url)
const db = client.db(dbName)
const getRequest = await db.collection('documents').find({}).toArray()
client.close()
return res.json({ success: true, data: getRequest })
})
I need to send response after executing one asynchronous call using Node.js and MongoDB. I am explaining my code below.
module.exports.getDashboardDetail = async(req, res, next) =>{
console.log('Inside dashboard controller');
var customerVisited=await Allocation.collection.aggregate([
{$match:{}},
{$unwind:"$zone_list"},
{$unwind:"$zone_list.state_list"},
{$unwind:"$zone_list.state_list.location_list"},
{$unwind:"$zone_list.state_list.location_list.task_list"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned.feedback_detail"},
{$group:{
_id:"total_feedback",
count:{$sum:1}
}
}
])
.toArray((err,docs)=>{
if (!err) {
customerVisited=docs
console.log('custm',customerVisited);
}else{
console.log('err',err);
}
})
var fosdata=await User.collection.countDocuments({},function(err,docs){
if (!err) {
fosdata=docs;
//res.send(data);
//console.log('nos of users',docs);
}
})
var data = {"no_of_visited_customer": customerVisited,"no_of_fos": fosdata,"no_of_alerts": 15,"status":'success'};
res.status(200).send(data);
//return res.status(200).json({ status: true, data : _.pick(data )});
}
Here I need to send the response after the aggregation method execution. Here Before coming the db result the response is sending.
You can write something like this,
var customerVisited=await Allocation.collection.aggregate([
{$match:{}},
{$unwind:"$zone_list"},
{$unwind:"$zone_list.state_list"},
{$unwind:"$zone_list.state_list.location_list"},
{$unwind:"$zone_list.state_list.location_list.task_list"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned.feedback_detail"},
{$group:{
_id:"total_feedback",
count:{$sum:1}
}
}
])
.toArray((err,docs)=>{
if (!err) {
customerVisited=docs
console.log('custm',customerVisited);
}else{
console.log('err',err);
}
})
var data = {"no_of_visited_customer": customerVisited};
res.status(200).send(data);
But make sure the function in which this code is written is async (just add the word async before the word function), for example, if your function is,
function test() {
// some code
}
It should be
async function test() {
// some code
}
Why using toArray(err, docs) ?
// use your paths to models
const Allocation = require('../models/Allocation')
const User = require('../models/User')
module.exports.getDashboardDetail = async (req, res, next) => {
try {
console.log('Inside dashboard controller');
var customerVisited = await Allocation.collection.aggregate([
{$match:{}},
{$unwind:"$zone_list"},
{$unwind:"$zone_list.state_list"},
{$unwind:"$zone_list.state_list.location_list"},
{$unwind:"$zone_list.state_list.location_list.task_list"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned.feedback_detail"},
{$group: {
_id:"total_feedback",
count:{$sum:1}
}
}
])
.toArray()
var fosdata = await User.collection.countDocuments({})
var data = {
"no_of_visited_customer": customerVisited,
"no_of_fos": fosdata,
"no_of_alerts": 15,
"status":'success'}
res.status(200).send(data);
} catch (e) {
// if it is just a controller we can set status and send response
// res.status(400).send(`Error ${e}`)
// if using express middleware pass error to express with next(e)
next(e)
}
}
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);