Put and Delete Comment API endpoint - Express MongoDB/Mongoose - node.js

I have created an API endpoint that a client can post to and an API endpoint that retrieves the comments.
I am trying to create another API endpoint which allows the client to update an existing comment by specifying the id of the comment they want to change, and a final endpoint to delete. Here is what I have created so far:
var express = require('express');
var router = express.Router();
var Comment = require('../models/comments');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
/**
* Adds comments to our database */
router.post('/addComment', function(req, res, next) {
// Extract the request body which contains the comments
comment = new Comment(req.body);
comment.save(function (err, savedComment) {
if (err) throw err;
res.json({
"id": savedComment._id
});
});
});
/**
* Returns all comments from our database
*/
router.get('/getComments', function(req, res, next) {
Comment.find({}, function (err, comments) { if (err)
res.send(err); res.json(comments);
}) });
module.exports = router;

Here are examples of PUT and DELETE functions using ES6 syntax. The update function expects a title and content in the posted body. Yours will look different.
module.exports.commentUpdateOne = (req, res) => {
const { id } = req.params;
Comment
.findById(id)
.exec((err, comment) => {
let response = {};
if (err) {
response = responseDueToError(err);
res.status(response.status).json(response.message);
} else if (!comment) {
response = responseDueToNotFound();
res.status(response.status).json(response.message);
} else {
comment.title = req.body.title;
comment.content = req.body.content;
comment
.save((saveErr) => {
if (saveErr) {
response = responseDueToError(saveErr);
} else {
console.log(`Updated commentpost with id ${id}`);
response.status = HttpStatus.NO_CONTENT;
}
res.status(response.status).json(response.message);
});
}
});
};
module.exports.commentDeleteOne = (req, res) => {
const { id } = req.params;
Comment
.findByIdAndRemove(id)
.exec((err, comment) => {
let response = {};
if (err) {
response = responseDueToError(err);
} else if (!comment) {
response = responseDueToNotFound();
} else {
console.log('Deleted comment with id', id);
response.status = HttpStatus.NO_CONTENT;
}
res.status(response.status).json(response.message);
});
};
Usually when you update with PUT in a REST API, you have to provide the entire document's content, even the fields you are not changing. If you leave out a field it will be removed from the document. In this particular example the update function expects both title and content, so you have to provide both. You can write the update logic however you want though.
The router has functions for put and delete. So it will look something like this:
router
.get('comment/:id', getFunction)
.put('/comment/:id', putFunction)
.delete('/comment/:id', deleteFunction);

Related

Express returns 404 even though the route exists

This is my router module:
router.post("/", function (req, res, next) {
var url = req.body.url;
youtubedl.getInfo(url, function (err, info) {
// handle errors here
if (err) {
console.log(err);
res.status(500).send("youtube-dl error");
} else {
var fields = info.title.split('-');
var artist = sanitizeArtistOrTitle(fields[0].trim());
// TODO: handle possibility of - in artist/song name
var title = sanitizeArtistOrTitle(fields[1].trim());
const options = {
apiKey: geniusKey,
title: title,
artist: artist,
optimizeQuery: true
};
geniusSong(options)
.then(function (result) {
urlFields = result.url.split('/');
// check if the name of the song and artist are in the url
// since the api seems to return a random lyric page
// when the actual page cannot be found
var titleAndArtist = urlFields[3].split('-').join(' ').toLowerCase();
if (titleAndArtist.includes(artist.toLowerCase()) &&
titleAndArtist.includes(title.toLowerCase())) {
// get the lyrics and write to a file
req.options = options;
next();
} else {
res.status(500).send("genius API error on retrieving lyrics");
}
}).catch(function (err) {
console.log(err);
res.status(500).send("genius API unknown error");
})
}
})
}, function (req, res) {
console.log(__dirname);
geniusLyrics(req.options)
.then(lyrics => sanitizeLyrics(lyrics))
.then(sanitizedLyrics => fsPromise.writeFile("./aux_files/words.txt",
sanitizedLyrics.toString()))
.then(console.log("written to file"))
.then(res.status(200).sendFile(path.join(__dirname, "../aux_files", "words.txt"),
{headers: {'Content-Type': 'text/plain'}}))
.catch(function (err) {
console.log(err);
res.status(500).send("Could not write lyrics to file");
});
})
function sanitizeLyrics(lyrics) {
var regexp = /\[[\w ]*\]/g;
return lyrics.replace(regexp, '');
}
// remove unncessary parts of the video title e.g. "feat. ...",
// "[Official Music Video]"
function sanitizeArtistOrTitle(value) {
var regexp = /(ft\..*$|\(.*$|\[.*$|feat\..*$)/
return value.replace(regexp, '');
}
module.exports = router
And in app.js I have:
var lyrics = require('./routes/lyrics');
app.use('/api/lyrics', lyrics);
What I don't understand is that the route returns a 404 when it's called by my frontend:
POST /api/lyrics 404 4400.863 ms - 351
I can see in my filesystem that the file has been written, so I know that the middleware executed successfully, but I'm not sure where the error is coming from. If I try to fetch from the route again (after the file has been created), there is no error and everything works properly.

Location API not returning location id to server

I'm following an tutorial where i am supposed to consume an mongoose API using express and display location information to the view. However I am getting an 404 error on the front end everytime i click the button that calls the API request. The source code for this tutorial can be found here. https://github.com/cliveharber/gettingMean-2/tree/chapter-07
Here is the API request handler on the server side:
const getLocationInfo = (req, res, callback) => {
const path = `/api/locations/${req.params.locationid}`;
const requestOptions = {
url: `${apiOptions.server}${path}`,
method: 'GET',
json: {}
};
request(
requestOptions,
(err, {statusCode}, body) => {
const data = body;
if (statusCode === 200) {
data.coords = {
lng: body.coords[0],
lat: body.coords[1]
}
callback(req, res, data);
} else {
showError(req, res, statusCode);
}
}
);
};
const locationInfo = (req, res) => {
getLocationInfo(req, res,
(req, res, responseData) => renderDetailPage(req, res, responseData)
);
};
The Render handler of the page i am trying to reach:
const renderDetailPage = (req, res, location) => {
res.render('location-info',
{
title: location.name,
pageHeader: {
title: location.name,
},
sidebar: {
context: 'is on Loc8r because it has accessible wifi and space to sit down with your laptop and get some work done.',
callToAction: 'If you\'ve been and you like it - or if you don\'t - please leave a review to help other people just like you.'
},
location
}
);
};
This is the Mongoose API controller that retrieves the location data from the database:
const locationsReadOne = (req, res) => {
Loc
.findById(req.params.locationid)
.exec((err, location) => {
if (!location) {
return res
.status(404)
.json({
"message": "location not found"
});
} else if (err) {
return res
.status(404)
.json(err);
}
res
.status(200)
.json(location);
});
};
This is the express routing code:
const express = require('express');
const router = express.Router();
const ctrlLocations = require('../controllers/locations');
const ctrlOthers = require('../controllers/others');
router.get('/', ctrlLocations.homelist);
router.get('/locations/:location._id', ctrlLocations.locationInfo);
And finally this is a snippet of the Pug code that contains the button which calls the get request to retrieve the location id:
each location in locations
.card
.card-block
h4
a(href=`/locations/${location_id}`)= location.name
+outputRating(location.rating)
span.badge.badge-pill.badge-default.float-right= location.distance
p.address= location.address
.facilities
each facility in location.facilities
span.badge.badge-warning= facility
Let me know if i need to provide additional information. Appreciate any help.

How to execute response after asynchrous call made using Node.js

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

Calling an API endpoint from within another route in Node / Express

I have myRoute.js with a route (GET) defined and I want to call an api endpoint from another route (api.js), and I'm not sure what the right way to do this is. The api.js route is working properly (image and code below).
api.js
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
works as expected:
myRoute.js
I would like when a user goes to localhost:3000/USER_ID that the route definition gets information from the api. Psuedo code below (someFunction).
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
let fromApi = someFunction(`localhost:3000/getAllGroups/${uid}`); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Not sure if i understand you correct but anyway i will try to help. So you have an api like
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
If you would like to reuse it you can extract a function from the code above like so:
async function getAllGroupsByUserId(uid){
const result = [];
try{
const data = await db.getAllGroups(uid);
for (i in data) {
result.push(data[i].groupname);
};
return result;
}
catch(e) {
return e;
}
}
And then reuse it in your api & anywhere you want:
router.get('/getGroups/:uid', async function(req, res, next) {
const uid = req.params.uid;
const groups = await getAllGroupsByUserId(uid);
res.status(200).send(groups);
})
Same you can do in your another route:
router.get('/:uid', async function(req, res, next) {
const uid = req.params.uid;
const fromApi = await getAllGroupsByUserId(uid); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Seems like pretty clear :)
I would use fetch for this. You can replace someFunction with fetch, and then put the res.render code in a .then(). So, you would get this:
const fetch = require("node-fetch");
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}').then(res => res.json()).then(function(data) {
returned = data.json();
console.log(returned); //expecting array
res.render('./personal/index.jade', {JSON.stringify(returned)});
});
});
A more robust way with error handling would be to write something like this:
const fetch = require("node-fetch");
function handleErrors(response) {
if(!response.ok) {
throw new Error("Request failed " + response.statusText);
}
return response;
}
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}')
.then(handleErrors)
.then(res => res.json())
.then(function(data) {
console.log(data) ; //expecting array
res.render('./personal/index.jade', {JSON.stringify(data)});
})
.catch(function(err) {
// handle the error here
})
});
The ideal way would be to abstract your code into a method so you aren't calling yourself, as The Reason said. However, if you really want to call yourself, this will work.

How to make reusable function code in Expressjs?

I am newbie in ExpressJs and module pattern in my project. Now, i am stuck that how to use created controller function in another controller. Please look at example :-
menu.ctrl.js
------------
module.exports.save=function(req,res,next){
//here some logic
//somethings like validate req.body,etc
menu.save(function(err){
if(err) return next(err);
res.json({msg:'menu save'})
})
}
user.ctrl.js
------------
var user=require('./user.model')
var menuCtrl=require('./menu.ctrl')
module.exports.save=function(req,res,next){
//here some logic
user.save(function(err){
if(err) return next(err);
//HERE I WANT TO USE `menuCtrl.save()` function
res.json({msg:'success'});
})
}
Decoupling your controller logic from your model logic will allow you reuse logic and make your application easier to maintain.
The idea is that controllers' purpose is to format input and output to and from you application, while models handle actual data manipulation. (This is a typical Rails-like MVC pattern for REST APIs)
To your example:
menuController.js
var menuModel = require('./menuModel');
module.exports.save = function(req, res, next) {
menuModel.save(req.body, function(err) {
if(err) return next(err);
res.json({msg:'menu save'})
});
};
menuModel.js
module.exports.save = function(body, callback) {
// Save menu to the DB
menu.save(body, callback);
};
userController.js
var userModel = require('./userModel');
module.exports.save = function(req, res, next) {
userModel .save(function(err){
if(err) return next(err);
res.json({msg:'success'});
});
}
userModel.js
var menuModel = require('./menuModel');
module.exports.save = function(body, callback) {
// Save user to the DB
user.save(body, function(err, res) {
if (err) return callback(err);
menuModel.save(body, callback);
});
};
Rule of thumb, keep as less business logic as possible in controllers.
//Here is a solution if you are using same route file
//
var getNotificationSetting = async function (user_id) {
let params = {}
params = await NotifcationSetting.findAll({
where: { ns_user_id : user_id },
});
return params;
}
//now calling in action
router.get('/', async function(req, res, next) {
let params = {}
//for setting section
params = await getNotificationSetting(req.session.user.user_id);
});

Resources