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
This works because the code within the
app
function, specifically theapp.handle()
line of code, is not being executed when theapp
function is created. The code within the function is evaluated at some point in the future whenapp()
is invoked in some way, but it’s not evaluated at the time theapp
function is created:The
app
variable within thefunction
refers to theapp
variable in the scope outside of the function (ie the one we just created), and so changes made to theapp
variable outside of the function before the function is called will also apply to theapp
variable within the function, allowing methods such ashandle()
to be used.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, asapp()
is not being called, and we can see below thevar app = function(...) {...}
line thatapp
is being modified.Specifically, the
.handle
method is being added by this line:This merges the properties from the object
proto
into theapp
function object using the merge-descriptors package (ie: themixin
function). We can also see thatproto
is imported from the./application.js
file in express which exports its own object that has its ownhandle
method: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 theobj
variable, and so changes toobj
will be visible to the function once it’s eventually executed: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
In express 2 lines after the function you got confused about we have a line
Which assigns the handle property to the app.