Search code examples
node.jsexpresshandlebars.js

Express.js Reload Template Engine


Does anyone know how (or if it's even possible) to reload the template engine views for Express.js without restarting the server and not disabling view cache?

Something like...

app.set('view engine', 'hbs');

...

app.post('/reload-views', async (req, res) => {
    await app.get('view engine').invalidate();
    return res.status(203).end()
});

Update

Apparently, this question needs more context. I have an Express.js application that requires, somewhat frequent, view template updates. With view cache enabled, the res.render does not reflect the changes and afaik requires the application to be restarted to pick up the changes.

This was fine until I realize, restarting causes some longer running processes/requests to end prematurely, resulting in data inconsistencies. I am asking if anyone knows of a solution to "refresh" or "invalidate" the view cache during runtime.

Here's a Minimal, Reproducible Example. If you run the application, update index.hbs, refresh /page, you don't see the update, someone calls /api-endpoint, restart app (to refresh the views) within 3 secs, now you see the view update but you don't see done! in the console, (aka, "the problem").

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');

app.get('/page', async (req, res) => {
    res.render('index');
});

app.post('/api-endpoint', async (req, res) => {
    // longer running process 

    // this could be an upload endpoint, or a slower downstream api,
    // or a slow embedding function, or a multi-step state machine,
    // or anything that needs to be completed.

    // yes, there are a million other ways to implement this,
    // queues, separate batch process, microservice, etc
    // but if there is a quick simple way to just refresh the views,
    // it'll save me a bunch of time and effort

    setTimeout(() => {
        console.log('done!');
    }, 3000);    
});

// some secure internal endpoint called by CD
app.post('/refresh-views', async (req, res) => {
    await app.get('view engine').invalidate();
    return res.status(203).end()
});

Solution

  • Not sure why I didn't try this soon. I instantiated a new engine and re-set the app.engine()

    const getViewEngine = () => hbs.express4({
        beautify: true,
        layoutsDir: path.resolve(import.meta.dirname, '../views/layouts'),
        partialsDir: path.resolve(import.meta.dirname, '../views/partials'),
        defaultLayout: path.resolve(import.meta.dirname, '../views/layouts/default.hbs'),
    });
    
    app.post('/refresh-views', async (req, res) => {
        app.engine('hbs', getViewEngine());
        return res.status(203).end()
    });
    

    Or if you want it to automatically check for updates, this worked for me:

    chokidar.watch(path.resolve(import.meta.dirname, './views')).on('change', () => {
        console.log('views:change');
        app.engine('hbs', getViewEngine());
    });