Node.js / Express.js - How to override/intercept res.render function?

我怕爱的太早我们不能终老 提交于 2019-11-27 19:44:57

Old question, but found myself asking the same thing. How to intercept res render? Using express 4.0x something now.

You can use/write middleware. The concept was a bit daunting to me at first, but after some reading it made a little more sense. And just for some context for anyone else reading this, the motivation for overriding res.render was to provide global view variables. I want session to be available in all my templates without me having to type it in every res object.

The basic middleware format is.

app.use( function( req, res, next ) {
    //....
    next();
} );

The next param and function call are crucial to execution. next is the callback function, to allow multiple middleware to do their thing without blocking. For a better explanation read here

This can then be used to override render logic

app.use( function( req, res, next ) {
    // grab reference of render
    var _render = res.render;
    // override logic
    res.render = function( view, options, fn ) {
        // do some custom logic
        _.extend( options, {session: true} );
        // continue with original render
        _render.call( this, view, options, fn );
    }
    next();
} );

I've tested this code, using express 3.0.6. It should work with 4.x without issue. You can also override specific URLs combos with

app.use( '/myspcificurl', function( req, res, next ) {...} );

It's not a good idea to use a middleware to override a response or request method for each instance of them because the middleware is get executed for each and every request and each time it get called you use cpu as well as memory because you are creating a new function.

As you may know javascript is a Prototype-based language and every object has a prototype, like response and request objects. By looking at code (express 4.13.4) you can find their prototypes:

req => express.request
res => express.response

So when you want to override a method for every single instance of a response it's much much better to override it in it's prototype as it's done once is available in every instance of response:

var app = (global.express = require('express'))();
var render = express.response.render;
express.response.render = function(view, options, callback) {
    // desired code
    /** here this refer to the current res instance and you can even access req for this res: **/
    console.log(this.req);
    render.apply(this, arguments);
};

The response object doesn't have a prototype. This should work (taking Ryan's idea of putting it in a middleware):

var wrapRender = function(req, res, next) {
  var _render = res.render;
  res.render = function(view, options, callback) {
    _render.call(res, "testViews/" + view, options, callback);
  };
};

However, it might be better to hack the ServerResponse.prototype:

var express = require("express")
  , http = require("http")
  , response = http.ServerResponse.prototype
  , _render = response.render;

response.render = function(view, options, callback) {
  _render.call(this, "testViews/" + view, options, callback);
};

I recently found myself needing to do the same thing, to provide a configuration-specific Google Analytics property id and cookie domain to each of my templates.

There are a number of great solutions here.

I chose to go with something very close to the solution proposed by Lex, but ran into problems where calls to res.render() did not already include existing options. For instance, the following code was causing an exception in the call to extend(), because options was undefined:

return res.render('admin/refreshes');

I added the following, which accounts for the various combinations of arguments that are possible, including the callback. A similar approach can be used with the solutions proposed by others.

app.use(function(req, res, next) {
  var _render = res.render;
  res.render = function(view, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    } else if (!options) {
      options = {};
    }
    extend(options, {
      gaPropertyID: config.googleAnalytics.propertyID,
      gaCookieDomain: config.googleAnalytics.cookieDomain
    });
    _render.call(this, view, options, callback);
  }
  next();
});

edit: Turns out that while this all might be handy when I need to actually run some code, there's a tremendously simpler way to accomplish what I was trying to do. I looked again at the source and docs for Express, and it turns out that the app.locals are used to render every template. So in my case, I ultimately replaced all of the middleware code above with the following assignments:

app.locals.gaPropertyID = config.googleAnalytics.propertyID;
app.locals.gaCookieDomain = config.googleAnalytics.cookieDomain;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!