richie
richie

Reputation: 197

middleware on res.render() when using a lot of routes

I've created a webpage to use it locally. I have a ton of routes like the ones shown below -- 31 .ejs files and 3 .html files. (They are all in the same "views" folder).

//app.js - using node and express
    app.get('/page1', function(req, res){
        res.render('page1');
    });
    app.get('/page2', function(req, res){
        res.sendFile('views/page2.html', { root: __dirname });
    });

I use an app.get for each and every one of these files. I've had a feeling it wasn't DRY code, and so now I'm trying to figure out a more elegant and optimal way to achieve the same result.


Any help is very much appreciated, thanks!!


[UPDATE]

  • richie
    • node_modules
    • public
      • css files, images, etc
    • views
      • partials
        • all partial files
      • programmingPublic
        • all ejs files from a same topic
      • other files (html & other ejs)
    • appjs
    • packagejson
    • package-lockjson



const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');

const app = express(); 

// Body Parser Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));

// engine
app.set("view engine", "ejs");

// Set static path
app.use(express.static(path.join(__dirname, 'public')));

const fs = require('fs');

function renderStatic(dir) {
    return function(req, res, next) {
        let target = path.join(dir, req.path);
        fs.access(target, fs.constants.R_OK, function(err) {
            if (err) {
                // file not found, just move on
                next();
            } else {
                res.render(target);
            }
        });
    }
}

app.use(renderStatic(path.join(__dirname, "views/programmingPublic")));

Below is the format of my side-menu: (all these files are inside "programmingPublic" folder)

<a href="/programming" class="title">Programming</a>
<li><a href="/c">C</a></li>
<li><a href="/cpp">C++</a></li>
<li><a href="/python">Python</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/php">PHP</a></li>

Upvotes: 0

Views: 283

Answers (1)

jfriend00
jfriend00

Reputation: 707298

If you have a bunch of pages that need to call res.render(), but aren't passing custom options to each render, then you could isolate all those templates in their own directory and then use some middleware like this:

const path = require('path');
const fs = require('fs');

function renderStatic(dir, options) {
    const regex = /^\.|\.\.|\/\.|\\\./;
    options = options || {};

    return function(req, res, next) {
        let target = path.join(dir, req.path);
        if (options.ext && !path.extname(target)) {
           target = target + options.ext;
        }
        // don't allow leading dot or double dot anywhere in the path
        if (regex.test(target)) {
           next();
           return;
        }
        fs.access(target, fs.constants.R_OK, function(err) {
            if (err) {
                // file not found, just move on
                next();
            } else {
                res.render(target);
            }
        });
    }
}

app.use(renderStatic(path.join(__dirname, "renderPublic"), {ext: ".ejs"}));

Note, you must isolate these template files in their own directory so that other files are not found there.

For safety completeness, this code also needs to filter out . and .. items in the path like express.static() does to prevent an attacker from going up your directory hierarchy to get access to other files than those in the render static directory.


Then, for the routes you are using res.sendFile() and no other logic, just isolate those HTML files in their own directory and point express.static() at that directory. Then, the express.static() middleware will find a matching HTML file in that directory and do res.sendFile() for you automatically, exactly the same as it does for your CSS files.

Upvotes: 1

Related Questions