|Node.JS| Whenever I get params.id, I get jumbotron.css - node.js

So first of all here is my code
router
.get('/find/:_id',(req,res)=>{
const id = req.params._id;
console.log(id);
if(id !== 'jumbotron.css'){
Doc.findById(id)
.then((doc)=>{
res.render('table/content',{
title: doc.subject,
docs: doc
})
})
.catch((err)=>{
console.log(err)
})
}
});
This is the output
enterthe server running on 3000
Connected successfully to mongodb
5b00373bbea0a253106e0930
jumbotron.css
so whenever I make a GET req using that Id, two items are printed,
first one is id and the second one is 'jumbotron.css',
I have no idea where I am getting jumbotron.css as req.params.id.
This would not let me get into that url.

On your table/content HTML, you probably have:
<link rel="stylesheet" type="text/css" href="jumbotron.css">
Instead of href="/jumbotron.css" or href="/path/to/css/jumbotron.css"
And since it's relative to the current URL it is performing a request to:
/find/jumbotron.css
That's why you're getting a second request with id === 'jumbotron.css'

Related

Best way to implement notification alerts in Node/Express app?

I have inherited a Node/Express code base and my task is to implement a notification alert in the navigation menu. The app database has a table of 'pending accounts', and the alert needs to expose the number of these pending accounts. When an account is 'approved' or 'denied', this notification alert needs to update and reflect the new total of pending accounts.
I know how to do the styling and html here, my question is how best to instantiate, maintain and pass a global dynamic variable that reflects the number of pending accounts, and how to get this variable exposed in the header view which contains the navbar where the notification is to be displayed.
This project a pretty standard Node/Express app, however it uses the view engine Pug. At the root of the view hierarchy is a layout.pug file, which loads most of the scripts and stylesheets, and this layout view in turn loads the header Pug view. When this header view loads, and every time it loads, I need this updated 'pending accounts count' value available to insert into the header view. This is what I am at a bit of a loss on how to go about.
Below is the layout.pug markup with the inclusion of the header pug view. Everything else in the project is pretty straightforward vanilla Node/Express I believe, but I am not very experienced with this stack so if any other code is needed please don't hesitate to ask and I will post. Thanks.
doctype html
html(lang="en")
head
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(name='viewport', content='width=device-width, initial-scale=1.0, shrink-to-fit=no')
meta(name='theme-color', content='#4DA5F4')
meta(name='csrf-token', content=_csrf)
script(src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js")
block head
body
include partials/header
I tried including a script in my header.pug view, which contains the navbar element that I want to append the notification too...
link(href='/css/header/header.css' rel='stylesheet')
script(src='/js/registration/registrationsTable.js')
...
Which would run the following function on DOM load....
function getNumberOfPendingRegistrations() {
axios({
method: 'get',
url: '/admin/getNumberOfPendingRegistrations'
}).then((response) => {
if (response.status === 200) {
const { numPendingUsers } = response.data;
console.log(response.data);
} else {
console.log(response.status);
}
})
.catch((error) => {
console.error(error);
});
}
(() => {
document.addEventListener('DOMContentLoaded', () => {
getNumberOfPendingRegistrations();
which would then call the following express function....
exports.getNumberOfPendingRegistrations = (req, res) => {
RegisteredUser.find({ status: 'PENDING' }, (err, allPendingUsers) => {
if (!err) {
let numPendingUsers = 0;
allPendingUsers.forEach(() => {
numPendingUsers++;
});
return res.send(200, { numPendingUsers });
}
console.log('error');
throw err;
});
};
which would then return numPendingUsers to the axios then() function and make that variable available to the header.pug view here....
li.nav-item
a.nav-link(href='/admin/registeredUsers')
span.fa-solid.fa-pen-to-square(style="font-size: 0.7em;")
span(style="margin-left: 0.1em;") Registrations
span.notification=numPendingUsers
NumPendingUsers returns correctly to the axios .then() promise, but is somehow never made available in the header.pug view. It is always undefined. I'm not sure if its a timing issue w when the DOM is loaded, or if I'm making the variable available in .then() incorrecly or what. And also I feel like there must be a simpler way to accomplish all of this.
Figured out that I simply needed to implement a middleware to pass this data to every route. Duh.

Why does an await within a server endpoint blocks other incoming requests?

I have two different versions of an endpoint for testing.
The first one is this:
app.get('/order', async (req, res) => {
console.log("OK")
getPrintFile()
})
And the second one is this:
app.get('/order', async (req, res) => {
console.log("OK")
await getPrintFile()
})
the getPrintFile is an async function that returns a promise when every operation is done. Withing the function I upload an image to a
server, I download a new image, and re upload that new image to another server.
I noticed that in the first example, without the await, if I send a lot of requests to the "order" endpoint,
I get the "OK" instantly for each request, which is what I want because that "OK" will get replaced by a res.status("200"). I need
to send a status 200 immediatly after getting the endpoint hit for various reasons. Then I don't care how long it takes for the server to do all the processing of the images/uploading, as long as the res.send(200) is executed instantly when there is a new incoming request.
However, when I use the await, even if new requests are coming in, it takes a lot to display the next "OK" if a previous request
is still processing. Usually it displays the OK only when the code within the getPrintFile function is done executing (that is, images are uploaded and everything is done)
It's like the event loop is blocked but I don't understand why.
What is happening here?
Edit:
So it is clearer, I tested it. If I send 5 requests to the "order" endpoint, the "OK" is displayed in the console immediately for all of them, and then the images are processed and uploaded at their own speed for each request. In the second example, if I send 5 requests, the first OK is displayed, and then the remaining 4 are displayed one at a time when the previous request is done executing, or if not exactly in that order, they get logged with tremendous delay, and not consecutively
I'll try to answer based on my understanding of your problem. The first thing missing is the res.sendStatus(200) in your examples to make them actually work. But then, indeed it happens as you describe it: the /order endpoint actually is "blocking" you from making another request as the await is blocking you from reaching the final statement (res.send). Here a full example
const express = require("express");
const app = express();
async function takeTime() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("resolved");
resolve(1);
}, 2000);
});
}
app.get("/order", (req, res) => {
console.log("ok"); // Happens immediately
await takeTime();
return res.sendStatus(200);
});
app.get("/", async (req, res) => {
console.log("ok"); // Happens immediately
takeTime();
return res.sendStatus(200);
});
app.listen(3000, () => {
console.log("server running on port 3000");
});
When testing the API (i.e. from Postman), you won't be able to run immediately another request on the /order endpoint, because you will be locked in waiting for the answer of the request just sent.
In the / endpoint, on the other hand, you will receive immediately the HTTP 200 response (as there is no await) and the code for the takeTime function will keep running asynchronously until it's done.
Here you can find more information, I hope it's useful.
Edit 1
here I add the .html page I'm using to test the await loop requests
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body></body>
<script>
for (let i = 0; i < 10; i++) {
fetch("http://localhost:3000/order");
}
</script>
</html>
The fetch is not working for me in the browser, but having a web page making those request works

Handling delete operation in express + pug template engine

I'm learning express and have created a to-do list app in express with pug template engine. In this app, I can add and view to-dos. Now I want to add a delete functionality.
My pug code to render the to-do list is:
ol
each val in toDoList
li= val.taskName
a(class='button' href='/todos/'+val._id) delete
Here's my express code to handle the delete:
app.delete('/todos/:id', function(req, res){
db.collection('todos').findOneAndDelete({id: req.params.id}, function(err, results) {
if (err) return res.send(500, err);
res.redirect('/');
});
});
I am getting this error Cannot GET /todos/5e570f67ed9efba99c938719.
What am I doing wrong and how can I fix it?
Thanks!
Why you are getting the error:
The link element: a(class='button' href='/todos/'+val._id) delete
is going to render a link element like this: <a class='button' href='/todos/5e570f67ed9efba99c938719'>delete</a>(assuming val._id is 5e570f67ed9efba99c938719).
Clicking this link won't delete the todo but instead, take you to a page whose URL is something like this /todos/5e570f67ed9efba99c938719 and this would cause the client app make a GET request to your server for that route. Since you do not have any route-handler matching the requested route from the client app in the server, you get the error: Cannot GET /todos/5e570f67ed9efba99c938719.
The Fix
To delete the todo, what you need to do is add an event listener to the link such that when you click it, instead of just redirecting you to some page, the handler attached to the event listener would be executed to reach out to your server and delete the todo. A sample implementation:
// Add some more properties to the delete button
a(class='button delete-todo' data-articleid=val._id href='/todos/'+val._id) delete
And in a <script /> tag or a javascript file that would be loaded with the pug template, add this:
// SOURCE: https://github.com/elvc/node_express_pug_mongo/blob/master/public/js/main.js
$(document).ready(function(){
$('.button.delete-todo').on('click', function(e){
e.preventDefault();
$target = $(e.target);
const id = $target.attr('data-articleid');
$.ajax({
type: 'DELETE',
url: '/todos/'+id,
success: function (response){
// Whatever you want to do after successful delete
console.log(response);
alert('Deleting article');
},
error: function(err){
// Whatever you want to do after a failed delete
console.error(err);
}
});
});
});

Send variable from mongoose query to page without reload on click

I have a link on my site. When clicked it'll call a function that does a mongoose query.
I'd like the results of that query to be sent to the same page in a variable without reloading the page. How do I do that? Right now it is just rendering the page again with new query result data.
// List comments for specified chapter id.
commentController.list = function (req, res) {
var chapterId = req.params.chapterId;
var query = { chapterId: chapterId };
Chapter.find({ _id: chapterId }).then(function (chapter) {
var chapter = chapter;
Comment.find(query).then(function (data) {
console.log(chapter);
Chapter.find().then(function(chapters){
return res.render({'chapterlinks', commentList: data, user: req.user, chapterq: chapter, chapters:chapters });
})
});
})
};
You just need to make that request from your browser via AJAX:
https://www.w3schools.com/xml/ajax_intro.asp
This would be in the code for your client (browser), not the code for your server (nodejs).
UPDATE:
Here's a simple example, which uses jQuery to make things easier:
(1) create a function that performs and handles the ajax request
function getChapterLinks(chapterId) {
$.ajax({
url: "/chapterLinks/"+chapterId,
}).done(function(data) {
//here you should do something with data
console.log(data);
});
}
(2) bind that function to a DOM element's click event
$( "a#chapterLinks1" ).click(function() {
getChapterLinks(1);
});
(3) make sure that DOM element is somewhere in you html
<a id="chapterLinks1">Get ChapterLinks 1</a>
Now when this a#chapterLinks1 element is clicked, it will use AJAX to fetch the response of /chaptersLink/1 from your server without reloading the page.
references:
http://api.jquery.com/jquery.ajax/
http://api.jquery.com/jquery.click/

Direct linking to route (e.g., /articles/:articleid) returns plain JSON, skips AngularJS and DOM

I have a multipage blog based on https://github.com/linnovate/mean.
Right now when I go directly to a /articles/:articleid type url, all that I see is plain JSON ({"title":"this is the title","_id":"12345","user":"me"}) returned from my database. If I go to /articles/:articleid from my main page / -> clicking a link, the page parses fine since Angular and the DOM have already loaded from being at the main page, so Angular reads and parses the JSON that's returned.
Ideally, I want to be able to enter a direct URL to an article (e.g., /articles/:articleid) and have the server load the DOM and then have AngularJS return and parse the JSON. Or have some way for my server to load the html/css/etc. if it hasn't been already, before parsing the JSON (and thus avoiding plain json outputs).
Node routes:
var articles = require('../app/controllers/articles');
app.get('/articles', articles.all);
app.get('/articles/:articleId', articles.show);
app.param('articleId', articles.article); //This is called when I directly link to an article
Articles model code:
exports.article = function(req, res, next, id) {
Article.load(id, function(err, article) {
if (err) return next(err);
if (!article) return next(new Error('Failed to load article ' + id));
console.log(req.headers['x-requested-with']);
if ( req.headers["x-requested-with"] === "XMLHttpRequest" ) {
console.log('sending jsonp' + req.article.title);
res.jsonp(req.article);
} else {
console.log("sending page");
res.render('index', { //my default JADE skeleton page
user: req.user ? JSON.stringify(req.user) : "null"
});
}
//req.article = article;
next();
});
};
exports.show = function(req, res) {
console.log(req.headers['x-requested-with']);
if ( req.headers["x-requested-with"] === "XMLHttpRequest" ) {
res.jsonp(req.article);
} else {
res.render('index', { //default page
user: req.user ? JSON.stringify(req.user) : "null"
});
}
};
Angular route:
when('/articles/:articleId', {
templateUrl: 'views/articles/view.html'
}).
The controller that handles the individual article is:
$scope.findOne = function() {
Articles.get({
articleId: $routeParams.articleId
}, function(article) {
$scope.article = article;
});
//$scope.htmlReady();
};
Thanks in advance!
EDIT:
I did some checking for the content type in the headers. I'm now able to discern whether it's a direct link or if it's coming from another page in the app, but I don't know how both render the template page (jade), start Angular, and supply JSON to it all in one go if it's a direct link.
My folder structure:
public
-- css
-- img
-- js
-- lib
-- views
My log output when I direct link (it seems to be using /articles as a base):
GET /articles/72119103c2e3a932b51e000201 304 619ms
GET /articles/css/blogstyle.css 404 134ms
GET /articles/lib/angular-resource/angular-resource.js 404 126ms
GET /articles/lib/jquery/jquery.js 404 136ms
GET /articles/lib/angular/angular.js 404 134ms
GET /articles/lib/angular-cookies/angular-cookies.js 404 136ms
GET /articles/lib/bootstrap/js/bootstrap.js 404 148ms
GET /articles/lib/angular-bootstrap/ui-bootstrap-tpls.js 404 58ms
GET /articles/lib/angular-ui-utils/modules/route.js 404 67ms
You need to look at the request content-type and return the complete angular page when it is content/html and JSON when it is jsonp request. This is for the route /articles/:articleId, of course.

Resources