2

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

Jack
  • 491
  • 7
  • 27

1 Answers1

2

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);
  }
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Hi, Thanks for the great answer. I am a begginner so I have been trying to understand how the answers work, the first way I would pass routehandler as middeware into all my routes right? And the second I am trying to understand when it is run, doesn't app.use only run once when the app is loaded??? My data might change on the way so I need it to check each time a req is made. But then I see res.locals which I saw on the other thread has to be assigned to each request, so it seems maybe it is run more? I am not too clear, and also why do we use Object.assign to save pageData to res.locals? – Jack Jul 16 '18 at 09:34
  • routeHandler is supposed to be passed as route handler to a route that uses `index` view, e.g. `app.get('/', routeHandler)` - or more if it's single-page app. As it was said, it can be refactored to be more universal. `app.use()` runs only once, but middleware function runs on each request, that's the point. Object.assign copies all properties from pageData to res.locals. – Estus Flask Jul 16 '18 at 10:11
  • Thanks I definitely get the 2nd one now!, As for the first one, the data will be for my header which will have the navbar for all pages and is therefore included as a partial into all pages, which is what makes it difficult. why do I pass route handler to the index file routes? Do you mean all routes?? – Jack Jul 16 '18 at 11:50
  • I mean that in snippet 1 you need to specify view name. You can't apply it to all routes because res.render needs view name. You could make this piece of code DRYer with regular programming techniques. Any way, snippet 2 fits the case. – Estus Flask Jul 16 '18 at 11:54
  • Got it, Thank you Estus for the answer and for clearing everything up! – Jack Jul 16 '18 at 11:57
  • Hi, if I do res.locals.pageData = pageData; then in my view I can use pageData, but if I do: Object.assign(res.locals, pageData); It tells me pageData undefined, how do I access it if done like this? Also why do we prefer to do it with Object.assign and create a new copy than to use a reference here??? Thanks Allot – Jack Jul 16 '18 at 13:06
  • `res.locals.pageData = ` makes data available in template as `pageData.title`, etc. Object.assign(res.locals, pageData) makes it available as `title`, that's the difference. This will work only if pageData has own enumerable `title` property. Considering that you use Mongoose, you likely may want to use `Category.find(...).lean()` to get plain objects that can be used in view. – Estus Flask Jul 16 '18 at 13:11
  • Hi, I tried doing Object.assign(res.locals, pageData), where pageData was and array of mongoose objects with {_id:xxx, name:xxx}, but I cant get name within my views what am I missing? Thanks for the info on lean I read abit about it still dont really get it but will continue to study about it. Thank You for al this follow up which I could give you some karma or something here. – Jack Jul 16 '18 at 15:02
  • This was suggested as general approach. pageData is an object that can be merged with res.locals at once, like `{ categories: [...] }`. Doing that with array directly will make results as available as `res.locals[0]`, etc. This is likely undesirable, Object.assign isn't needed in this case. – Estus Flask Jul 16 '18 at 15:18
  • Hi estus, I just added teh ability to add categories to my site and I am using the code app.use(async function(req, res, next) { try{ let allCats = await getAllCats(); res.locals.allCats = allCats; next() ...... but it doesn't add the newly added category to res.locals.allCats when I add it to the db and go to another page, it seems app.use and all of this is only being called at the beggining of the app and it is not called again to call the getData() function to update the res.locals values – Jack Jul 18 '18 at 13:49
  • Of course, middlewares run multiple times, that's their purpose. You're doing something wrong, or there are errors you didn't catch. See https://repl.it/repls/BrownUnlinedGraduate . There's new res.locals.random on every request, – Estus Flask Jul 18 '18 at 14:32