Mongoose/Node How to access DB in directly in EJS file - node.js

I want to access my DB in my EJS header file, which is a partial that is added to every page.
I have a Schema called Category and I want to get the name for the categories which will be in my header dynamically from the db.
I am tring to run the following commmand:
<% Category.find({}, name, function(err, names) { %>
<% if(err) { console.log(err); } %>
<% console.log("Names: " + names); %>
<% }); %>
But of course the header ejs file doesn't have access to Category.
I know normaly to access my DB in a ejs file I query the DB in the route and then pass the data to the ejs, but here since it is the header that will be added to every page I can't really do this operation in the route unless I do it in every route which does seem like such a good idea.
How can I get this data here?
Thanks

Database requests shouldn't be performed directly in view. This is prescribed by separation of concerns principle that stands behind MV* patterns.
Express route handlers act as MVC controllers, their purpose is to provide data from models to views.
Mongoose supports promises, so using callback-based API just complicates everything. Common data like could be provided as a separate function that returns a promise of data, e.g.:
function getPageData() { ... }
async function routeHandler(req, res, next) {
try {
const pageData = await getPageData();
res.render('index', {
...pageData,
/ * etc */
});
} catch (err) {
next(err);
}
};
routeHandler itself can be refactored to helper function that accepts view, view variables, req, res, and next.
Another approach is to make page data available globally in all or most views with additional middleware, as described in this related question, e.g.:
app.use(async function (req, res, next) {
try {
const pageData = await getPageData();
Object.assign(res.locals, pageData);
next();
} catch (err) {
next(err);
}
});

Related

How to use two responses at once?

I have a working template engine (pug) to fill it's website with content depending from the situation. Acutally that template is rendered for the site '/show'.
Now I also need to change the url of this website depending from the content. That means I need the same template with new content for sites like: '/tree', '/house', '/urban' an do so. '/show' is the starting point, I need to change it's url with the new content.
I'm sure there is an easy answer, but I can't find the fitting question for that. So I can't find the right answer per searchengine. (Express.js res.render() and res.redirect() was my closest success, but it is not helpful for me.
I know, the following code is incorrect, at least because of the two resp.
server.get('/show', (req, resp) => {
loadContent(function(err, content){
if(content){
resp.location('/tree');
resp.render('myTemplate', content);
} else{
console.log(err);
}
})
});
How can I send my content to the template and replace the url to see both on the browser?
to send data to your pug template with express js use this syntax
const router = require('express').Router();
server.get('/show', (req, res, next) => {
loadContent(function(err, content){
if(content){
res.render('myTemplate', { content: content });
} else{
console.log(err);
}
})
and you will get it
script.
var content = !{content};
Well, I've found my problem. My approach was incorrect.
With
server.get('/:kindOfSite', function(req, resp){...});
I'm able to load the same template for different sites.
Learning can get hard sometimes...
Remember that your express route handlers are just functions. There's nothing that forces you to use an anonymous function. You can just use a regular function:
function handleRequest (req, resp) {
loadContent(function(err, content){
if(content){
resp.location('/tree');
resp.render('myTemplate', content);
} else{
console.log(err);
}
})
}
server.get('/show', handleRequest);
server.get('/tree', handleRequest);
server.get('/house', handleRequest);
server.get('/urban', handleRequest);
Indeed, you can do a bit of metaprogramming and call server.get() in a loop:
['/show','/tree','/house','/urban].forEach(route => {
server.get(route,handleRequest)
});
In fact, Express accepts regular expressions as route paths:
server.get(/\/(show|tree|house|urban)/, (req, resp) => {
loadContent(function(err, content){
if(content){
resp.location('/tree');
resp.render('myTemplate', content);
} else{
console.log(err);
}
})
});

Can app.render() render an array of views?

It does not seem to be documented, I was wondering if it is possible to render multiple views or an array of views in Expressjs like so:
const data = 'some data passed by a DB';
const app = express();
const arrayViews = ['layout','email', 'web'];
app.render(arrayViews, data, (err, html) => {
if (err) throw err;
})
or do I have to do it in multiple instances
app.render('email', data, (err, html) => {
if (err) throw err;
})
app.render('web', data, (err, html) => {
if (err) throw err;
})
No. You cannot pass an array of views to app.render(). The point of app.render() is to create ONE piece of rendered HTML that can be sent as a response to a particular http request. You can't send multiple responses to one http request. So, you only want to call res.render() once for any given request.
If you want to have two different types of responses for different situations, then you should either make a separate route for each response and call res.render() once in each response with the appropriate template for that request.
Or, you can pass in a query parameter in the URL and use an if statement in the route handler to decide which template to render. But, the point is that you send exactly one response for each request.
For example, here's looking at a query parameter to decide which type of render to do:
app.get('/mypage', function(req, res) {
if (req.query.page === "email") {
res.render('email', data);
} else {
res.render('web', data);
}
});
Otherwise, you'd have two separate routes:
app.get('/mypage/email', function(req, res) {
res.render('email', data);
});
app.get('/mypage/web', function(req, res) {
res.render('web', data);
});
I may have originally been confused about the point of your question (since you don't show the overall context of your render or what you're using it for).
If you're using app.render() to collect the HTML from rendering operations, you can call that multiple times and wait for all to be done and then you have multiple rendered templates which you can do whatever you want with. But, a single app.render() doesn't accept multiple templates. You'd have to call it multiple times and wait for all the requests to be done.
Promise.all([appRender('email'), appRender('web')]).then([email, web] => {
// can access both rendered templates here
// to do with them whatever you want to do
}).catch(err => {
// error here
});
Or, you could use this to make a version of app.render() that would take an array:
const util = require('util');
const appRender = util.promisify(app.render.bind(app));
app.renderAll = function(arrayOfTemplates) {
return Promise.all(arrayOfTemplates.map(template => {
return appRender(template);
}));
});
app.renderAll(['email', ['web']]).then([email, web] => {
// can access both rendered templates here
// to do with them whatever you want to do
}).catch(err => {
// error here
});

Design pattern as alternative to front-end template locals

I am relatively new to web development and am trying to figure out how to publish the server environment (dev, test, prod, etc) to the front-end.
I am using Node.js with Express, but the following code is closer to pseudo-code because is this is more of a design pattern question.
One way to publish the server environment with Express is to add this to the middleware:
app.use(function(req,res,next){
res.locals.env= {}; //we add the property env to locals
});
so now in a front-end template we can access the 'env' locals variable:
<div>
<% app.env = env %> //hopefully 'env' will be available in a front-end template
</div>
I am not sure if the above is standard, but I feel like it certainly isn't ideal.
So I was thinking, perhaps we could do this instead, for either the first HTTP request, first socket request, or all requests(?):
//pseudo-code below
app.get('/', function(req,res,next){
if(req.isAuthenticated()){
socket.on('received-env-info', function(err,msg){
res.render('index',{auth:true});
}
socket.emit('send-env-info', env);
}
else{
res.render('index',{auth:false});
}
});
in this way we can be assured that the client knows what the environment is (or any other server variables), before any html is sent to the server. (We assume there is some socket.io handler on the client that subsequently sets the global env on the client to the variable that was sent from the server).
is this a good design pattern, or a bad one?
extra credit: we could take the same idea, and use it for authentication too:
app.get('/', function(req,res,next){
var infoForClient = {auth:null,env:env}; //should probably set auth to false for security but using 'null' for illustration purposes
if(req.isAuthenticated()){
infoForClient.auth = true;
}
else{
infoForClient.auth = false;
}
socket.on('received-info-from-client', function(msg){
if(msg === 'success'{ //some success message or another
res.render('index',infoForClient);
}
else{
infoForClient.auth = false;
res.render('index',infoForClient);
}
}
socket.emit('send-info-to-client', infoForClient);
}
});
I just need to know if this is a sane approach or not
This would work, but what's the gain vs
// B
app.get('/', function(req, res) {
res.json({
info: info,
html: template.render(info)
});
});
or
// C
socket.on('get-index', function () {
socket.emit('index', {
info: info,
html: template.render(info)
});
});
or with the template moved client side, simply return the info.
Once an intelligent client is in the picture, rendering the template server-side is no longer needed in the first place.
So looking at the client code
// A
socket.on('send-info-to-client', function (infoForClient) {
handleInfo(infoForClient);
socket.emit('recieved-info-from-client', true);
});
request('/', function (err, res, body) {
if (err) { return handleErr(err) }
handleHtml(body);
});
handle info and handle html end up split in two.
// B
request('/', function (err, res, body) {
if (err) { return handleErr(err) }
handleInfo(body.info);
handleHtml(body.html);
});
// C
socket.emit('get-index');
socket.on('index', function (msg) {
handleInfo(msg.info);
handleHtml(msg.html);
});
the handlers can be unified
and in the final case
// D
request('/', function (err, res, body) {
if (err) { return handleErr(err) }
handleInfo(body.info);
handleHtml(template.render(body.info));
});

Change the order of a routes variable in the front end NodeJs/Express/Jade

Novice to NodeJS and Express but lets say I have this route for mywebsite.com/tournaments.
router.get('/tournaments', function (req, res) {
TournamentController.getAllTournaments(function (err, docs) {
if (err) {
//render error
} else {
res.render('tournaments', {
data: {
title: 'mysite',
command: 'tournaments',
user: req.session.user,
tournaments: docs
}
});
}
});
});
data.tournaments is an array of tournaments in order of their date. Lets say in the front end I have a select/option form where the user can choose date/prize/players as the order to sort the tournaments by. How can I sort the data.tournaments without having to call another route or refresh the page? I'm using Jade on the front end.
You can always sort them directly in the Browser via Javascript, either do it yourself or use a plugin like datatables.
If you don't wanna do that but do it on the server, you'll need an ajax call for that and a route that handles the sorting (based on the parameters you pass), and afterwards change the DOM according to the response. This goes without refreshing the page, but you'll need a route for that, or change the existing route and extend your controller to take optional parameters, something like
router.get('/tournaments/:sort?', function (req, res) {
TournamentController.getAllTournaments(req.param('sort'), function (err, docs) {
/* ... */
});
});

Node.js Express Jade: Is there a way to define a JSON view for all URLs that (say) end with ".json"?

Most of my handlers look as follows:
function(req, res) {
var data = ...;
res.render('my_view', data);
}
I know that If I want to return JSON I need to change res.render to res.json, as follows:
function(req, res) {
var data = ...;
res.json(data);
}
when debugging I often want to see the raw data (in JSON format that was computed by the handler). To do that, I (manually) go to the handler callback and change res.render('...', to res.json(.
I am wondering whether there is a way to tell express that if the URL meets a certain condition token (say, ends with .json, or, alternatively, has a ?format=json query param) then res.view will seamlessly delegate to res.json ?
If it is just for debugging purpose then you could make a middleware that would override render method to json.
I will not recommend to use this in production.
In your app.configure add this:
app.use(function(req, res, next) {
if (req.query.json !== undefined) {
res.render = function(name, data) {
res.json(data);
}
}
return next();
});
So what it does: if request has json in query, then it will override render method and will call json instead.
So test it with: http://example.com/test?json

Resources