Using Mongoose save() inside Formidable's parse() inside NextJS API - node.js

Am using formidable package and mongoose in my NextJS app. So I wrote the API call to allow user to upload a photo along with some data. And I process the files using the formidable and it's working fine. But after processing the file, I need to insert some data to my MongoDB through mongoose and then return the API response. If I use await on the save() method of mongoose to save the data, it throws the error saying the parent needs to be async function. But in my case, the parent is form.parse().
Am a bit confused here on how to solve this issue. I think am confused with Promises here.
Here's the stripped down code of my /pages/api/submit.js :
import dbConnect from '../../lib/dbConnect'
import User from '../../models/User'
import formidable from 'formidable'
export default async function handler(req, res) {
await dbConnect()
if (req.method === 'POST') {
const form = new formidable.IncomingForm();
// some config
form.uploadDir = './public/photos/';
form.parse(req, (err, fields, files) => {
//.. some logic
if (err) {
console.log('error parsing the file')
return res.status(400).json( {
error: true,
msg: "Unable to parse the file"
} )
}
// insert some data
var user = new User( { name: 'blah', file_name: 'x', file_size: 'xx' } );
//await user.save();
user.save( err => {
if( err ){
console.log( err )
return false
}
// success
res.status(200).json( {
error: false,
msg: "success"
} ).end()
} )
})
}
}

Just unwrap it from the callback-hell, so you have a nice async/await code
👉Live Demo
const asyncParse = (req) =>
new Promise((resolve, reject) => {
const form = new IncomingForm({ multiples: true });
form.parse(req, (err, fields, files) => {
if (err) return reject(err);
// resolve "returns" the promise so you will have a straighforward logic flow
resolve({ fields, files });
});
});
export default async function handler(req, res) {
console.log("Receiving");
if (req.method === "PUT") {
const result = await asyncParse(req);
// so you can use it here as a normal code flow
res.status(200).json({ result });
}
}
PS. Why necrobumping? Its one of the first Google results, sorry

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 });
}
});

Using results from two different asynchronous functions

I'm trying to learn Asynchronous programming with NodeJS and I'm having trouble understanding how to create usable functions.
I'm trying to compare the results of a HTTP get request and a file read all inside an "express" callback. What is the best way to split out two different async operations into their own functions so that they can be used again together in a different callback?
I Have it working when I write everything inside the express callback
app.get('/', (req, res) => {
axios.get('http://127.0.0.1:8080')
.then(function(response) {
var http_data = response.data
// Do more stuff with data
fs.readFile('fwversion_current', 'utf8', function(err, contents) {
var file_data = contents.trim()
// Do more stuff with data
if (http_data == file_data) {
res.send("Match")
}
else {
res.send("No Match")
}
});
});
But I'm hoping for something more like this so I can use these same operations in other places. I'm not sure the right node way to get there.
function getHttpData() {
axios.get('http://127.0.0.1:8080')
.then(function(response) {
var http_data = response.data
// Do more stuff with data
return http_data
});
}
function getFileData() {
fs.readFile('fwversion_current', 'utf8', function(err, contents) {
var file_data = contents.trim()
// Do more stuff with data
return file_data
});
}
app.get('/', (req, res) => {
let http_data = await getHttpData()
let file_data = await getFileData()
if (http_data == file_data) {
res.send("Match")
}
else {
res.send("No Match")
}
});
You will need to wrap those functions inside a function that returns a Promise, this will let you the ability to await for them to complete before continuing.
function getHttpData(url) {
// axios.get already returns a Promise so no need to wrap it
return axios.get(url)
.then(function(response) {
let http_data = response.data;
// Do more stuff with data
return http_data;
});
}
function getFileData(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, function(err, contents) {
if (err) {
reject(err);
return;
}
let file_data = contents.trim();
// Do more stuff with data
resolve(file_data);
});
});
}
Now when both functions returns a Promise we can await for them to complete.
Make the handler an async function because it's needed to use the await keyword, I'm using Promise.all to fire both requests simultaneously and not wait for one to complete before we fire the other.
Wrap it in a try catch to handle errors and send status 500
app.get('/', async (req, res) => {
try {
const [http_data, file_data] = await Promise.all([
getHttpData(url),
getFileData(path),
]);
http_data == file_data
? res.send('Match')
: res.send('No Match');
} catch (err) {
console.error(err);
res.status(500).send('Something went wrong');
}
});

Assign value to variable outside mongo query in nodejs

Right now i have this code
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data)
postData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Events.list().then(data=> {
var jsonOutput=JSON.stringify(data)
eventData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data)
messageData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Users.list().then(data=> {
var jsonOutput=JSON.stringify(data)
userData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
//Then when all data from colections is retrieve i want to use the 4 variables that i created in the beggining
});
So basicly im trying to retrieve the data from my mongo database and then assign the results to that 4 variables that i create, but im not getting success.
For what i´ve been seeing i have to use async but im having some trouble doing it.
I don't like too much mrlanlee solution. This is a typical situation where using async / await can really make sense. Anyway, the Hugo's solution (the second one, with async await), even if it just works, will make the four queries in sequence, one after another to. If you want a clean, working and parallel solution, check this:
router.get('/export', async function(req, res, next) {
let data
try {
data = await Promise.all([
Posts.list(),
Events.list(),
Messages.list(),
Users.list()
]);
// at this point, data is an array. data[0] = Posts.list result, data[1] = Events.list result etc..
res.status(200).json(data)
} catch (e) {
res.status(500).send('error');
}
});
The other answer from Sashi is on the right track but you will probably run into errors. Since your catch statement on each promise returns 500, if multiple errors are caught during the query, Express will not send an error or 500 each time, instead it will throw an error trying to.
See below.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
try {
postData = Posts.list().then(data=> {
return JSON.stringify(data);
});
eventData = Events.list().then(data=> {
return JSON.stringify(data)
});
messageData = Messages.list().then(data=> {
return JSON.stringify(data);
})
userData = Users.list().then(data=> {
return JSON.stringify(data)
});
} catch (err) {
// this should catch your errors on all 4 promises above
return res.status(500).send('error')
}
// this part is optional, i wasn't sure if you were planning
// on returning all the data back in an object
const response = {
postData,
eventData,
messageData,
userData,
};
return res.status(200).send({ response })
});
For explanation of why you weren't able to mutate the variables, see Sashi's answer as he explains it.
The variables defined outside the async code is out of scope of the async functions. Hence you cannot store the returned value from the async functions in those variables.
This should work.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
postData = Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
eventData = Events.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
messageData = Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
userData = Users.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
});
Using Async/Await is a much neater solution.
router.get('/export', async function(req, res, next) {
var postData, eventData, messageData, userData;
try{
postData = await Posts.list();
eventData = await Events.list();
messageData = await Messages.list()
userData = await Users.list();
catch (e){
res.status(500).send('error');
}
});

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

Async REST PATCH best practice

I have a rest api that receives a patch request with the following possible parameters:
firstname
lastname
image
birthday
If I receive the image parameter I will need to upload the image to a cdn and after that to update and save the user profile.
If I don't receive the image parameter I will update and save the user profile.
What I had in mind was:
if photo then upload -> promise -> update fields -> save
else update fields -> save
My question: is there a best practice so I don't need to double a portion of code for each case?
app.patch('/users', authenticate, (req, res) => {
let body = _.pick(req.body, ['birthday', 'country', 'city', 'gender', 'firstname', 'lastname', 'photo']);
if ( body.photo ){
cloudinary.uploader.upload(body.photo,function(error, result) {
if( error ) {
res.status(400).send(error);
}
body.photo = result.url;
req.user = { ... req.user, ... body};
console.log ('update profile');
res.send(req.user);
});
}
req.user = { ... req.user, ... body};
console.log ('update profile');
res.send(req.user);
});
I have assumed that there is a createUser function that return a promise.
let userData = req.body;
userData.photo = undefined;
return Promise.resolve()
.then(function() {
if (req.body.photo) {
return promisifiedUpload(body.photo);
} else {
return undefined;
}
})
.then(function(result) {
if (result && result.url) {
userData.photo = result.url;
}
return createUser(userData);
})
.then(function(it) {
res.send(it);
})
.catch(function(err) {
res.status(400).send(err);
});
There are plenty of helper libraries that help you turn functions that work with node-style callbacks like function(err, resp){...} to promises, but for the sake of clarity this is how you could use one:
function promisifiedUpload(url) {
return new Promise(function(resolve, reject) {
cloudinary.uploader.uproad(url, function(err, resp) {
if (err) {
reject(err);
} else {
resolve(resp);
}
});
});
}
Some material that might come in handy:
ecma6 promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
npm util.promisify https://nodejs.org/api/util.html#util_util_promisify_original
How do I convert an existing callback API to promises?

Resources