skip to Main Content

I am trying to understand line by line how the Express library works. So what I understand is when we import and call ‘express()’ function in our codebase, execution control would go to the ExpressJS liberary and look for the default export (in its Index.js).

Index.js

/*!
 * express
 * Copyright(c) 2009-2013 TJ Holowaychuk
 * Copyright(c) 2013 Roman Shtylman
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict';

module.exports = require('./lib/express');

Here, the execution control would go to ‘./lib/express’ and look for its default export.

./lib/express.js

/*!
 * express
 * Copyright(c) 2009-2013 TJ Holowaychuk
 * Copyright(c) 2013 Roman Shtylman
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict';

/**
 * Module dependencies.
 */

var bodyParser = require('body-parser')
var EventEmitter = require('events').EventEmitter;
var mixin = require('merge-descriptors');
var proto = require('./application');
var Route = require('./router/route');
var Router = require('./router');
var req = require('./request');
var res = require('./response');

/**
 * Expose `createApplication()`.
 */

exports = module.exports = createApplication;

/**
 * Create an express application.
 *
 * @return {Function}
 * @api public
 */

function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // expose the prototype that will get set on requests
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init();
  return app;
}

/**
 * Expose the prototypes.
 */

exports.application = proto;
exports.request = req;
exports.response = res;

/**
 * Expose constructors.
 */

exports.Route = Route;
exports.Router = Router;

/**
 * Expose middleware
 */

exports.json = bodyParser.json
exports.query = require('./middleware/query');
exports.raw = bodyParser.raw
exports.static = require('serve-static');
exports.text = bodyParser.text
exports.urlencoded = bodyParser.urlencoded

/**
 * Replace removed middleware with an appropriate error message.
 */

var removedMiddlewares = [
  'bodyParser',
  'compress',
  'cookieSession',
  'session',
  'logger',
  'cookieParser',
  'favicon',
  'responseTime',
  'errorHandler',
  'timeout',
  'methodOverride',
  'vhost',
  'csrf',
  'directory',
  'limit',
  'multipart',
  'staticCache'
]

removedMiddlewares.forEach(function (name) {
  Object.defineProperty(exports, name, {
    get: function () {
      throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
    },
    configurable: true
  });
});

The default export here seems to be ‘createApplication()’ factory function.

var app = function(req, res, next) {
    app.handle(req, res, next);
  };

But what I do not understand is how we are calling the ‘app.handle()’ method when we have just freshly declared ‘app’ a line above. It should have no properties/methods right? How does it then work?

I tried to debug it line by line using VS Code, but it just skips all the breakpoints I put in the library.

2

Answers


  1. But what I do not understand is how we are calling the ‘app.handle()’
    method when we have just freshly declared ‘app’ a line above

    This works because the code within the app function, specifically the app.handle() line of code, is not being executed when the app function is created. The code within the function is evaluated at some point in the future when app() is invoked in some way, but it’s not evaluated at the time the app function is created:

    //  v--- Gets created and set to a function
    var app = function(req, res, next) {
        app.handle(req, res, next); // This `app` is used at some point in the future when this function is called, by that time `app` has been updated to have the `.handle()` method.
    };
    

    The app variable within the function refers to the app variable in the scope outside of the function (ie the one we just created), and so changes made to the app variable outside of the function before the function is called will also apply to the app variable within the function, allowing methods such as handle() to be used.

    It should have no properties/methods right?

    No, that would only be the case if that function was being executed right away without app being modified or updated in any way, but we can see that is not the case, as app() is not being called, and we can see below the var app = function(...) {...} line that app is being modified.

    Specifically, the .handle method is being added by this line:

    mixin(app, proto, false);
    

    This merges the properties from the object proto into the app function object using the merge-descriptors package (ie: the mixin function). We can also see that proto is imported from the ./application.js file in express which exports its own object that has its own handle method:

    app.handle = function handle(req, res, callback) {
      var router = this._router;
    
      // final handler
      var done = callback || finalhandler(req, res, {
        env: this.get('env'),
        onerror: logerror.bind(this)
      });
    
      // no routes
      if (!router) {
        debug('no routes defined on app');
        done();
        return;
      }
    
      router.handle(req, res, done);
    };
    

    For a simpler example on what’s going on, the same principles apply in this example with different variable names for the object and the function. The foo function created forms what’s called a closure over the obj variable, and so changes to obj will be visible to the function once it’s eventually executed:

    const obj = {};
    
    const foo = function() { // When `foo` is created, `obj` doesn't have any properties 
      console.log(obj.x); // When this function is executed, `obj` in the surrounding scope has been updated to have an `x` property which can be printed 
    }
    obj.x = 1;
    foo();
    Login or Signup to reply.
  2. While the code assigns a function to the app that would call method on the app, the function is never actually called until the method exists.

    I have created a simplified example below. As you can see I assign the

    var app = function() {
      app.handle();
    }
    
    try {app();} catch (error) {console.error(error.name);} // fails with TypeError
    
    app.handle = () => console.log("Hello World");
    app(); // logs "Hello World"

    In express 2 lines after the function you got confused about we have a line

      mixin(app, proto, false);
    

    Which assigns the handle property to the app.

    app.handle = function handle(req, res, callback) {
      var router = this._router;
    
      // final handler
      var done = callback || finalhandler(req, res, {
        env: this.get('env'),
        onerror: logerror.bind(this)
      });
    
      // no routes
      if (!router) {
        debug('no routes defined on app');
        done();
        return;
      }
    
      router.handle(req, res, done);
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search