Features of work and internal device express.js
- Transfer
If you were engaged in development for the node.js platform, then you, for certain, heard about express.js . This is one of the most popular lightweight frameworks used when building web applications for a node.
The author of the material, the translation of which we are publishing today, proposes to study the features of the internal structure of the express framework through the analysis of its source code and the consideration of an example of its use. He believes that studying the mechanisms underlying popular open source libraries contributes to their deeper understanding, removes the veil of "mystery" and helps to create better applications based on them.

You may find it convenient to keep the express source code on hand as you read this material. This version is used here . You can read this article quite well without opening the express code, since here, wherever it is appropriate, fragments of the code of this library are given. In those places where the code is abbreviated, comments of the form are used.
To begin, take a look at the traditional “Hello World!” In the development of new computer technologies - an example. It can be found on the framework’s official website; it will serve as a starting point in our research.
This code starts a new HTTP server on port 3000 and sends a response
The command
The object
Now let's take a look at the code that is responsible for creating the method
It is interesting to note that, in addition to the semantic features, all of the methods that implement HTTP actions, such as
Although the above function has 2 arguments, it is similar to the function
If in a nutshell, it
The router method is
Not surprisingly, the method declaration
Each route can have several processors; on the basis of each processor, a type variable is constructed
And
When creating objects of type,
Each type object
Recall what happens when you create a route using the method
As a result, all handlers are stored inside an instance

Objects of type Layer in the router stack and in the route stack.
Received HTTP requests are processed in accordance with this logic. We will talk about them below.
After setting up the routes, you need to start the server. In our example, we refer to the method
It looks like
After understanding that, ultimately, everything that gives us express.js can be reduced to a very intelligent handler function, the framework does not look as complicated and mysterious as it used to be.
Now, when we know that
First, the request goes to the function
Then it goes to the method
The method is
If we describe what is happening in a nutshell, then the function
Just as in the case of a router, during the processing of each route, the layers that this route has
Here, finally, the HTTP request falls into the code area of our application.

The request path in the express application
Here we covered only the basic mechanisms of the express.js library, those that are responsible for the operation of the web server, but this library has many other features. We did not dwell on the checks that the requests pass before they are received by the handlers, we did not talk about the helper methods that are available when working with variables
We hope this material has helped you understand the main features of the express device, and now you, if necessary, can understand everything else by independently analyzing the parts of the source code of this library that you are interested in.
Dear readers! Do you use express.js?


You may find it convenient to keep the express source code on hand as you read this material. This version is used here . You can read this article quite well without opening the express code, since here, wherever it is appropriate, fragments of the code of this library are given. In those places where the code is abbreviated, comments of the form are used.
// ...Basic example of using express
To begin, take a look at the traditional “Hello World!” In the development of new computer technologies - an example. It can be found on the framework’s official website; it will serve as a starting point in our research.
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3000, () =>console.log('Example app listening on port 3000!'))This code starts a new HTTP server on port 3000 and sends a response
Hello World!to requests coming along the route GET /. If you do not go into details, you can select four stages of what is happening, which we can analyze:- Create a new express application.
- Creating a new route.
- Start the HTTP server on the specified port number.
- Processing incoming requests to the server.
Creating a new express application
The command
var app = express()allows you to create a new express application. The function createApplicationfrom the lib / express.js file is a function exported by default, and we are accessing it by executing a function call express(). Here are some important things you should pay attention to here:// ...var mixin = require('merge-descriptors');
var proto = require('./application');
// ...functioncreateApplication() {
// Это возвращаемая переменная приложения, о которой мы поговорим позже.
// Обратите внимание на сигнатуру функции: `function(req, res, next)`
var app = function(req, res, next) {
app.handle(req, res, next);
};
// ...
// Функция `mixin` назначает все методы `proto` методам `app`
// Один из этих методов - метод `get`, который был использован в примере.
mixin(app, proto, false);
// ...
return app;
}The object
appreturned from this function is one of the objects used in the code of our application. A method is app.getadded using the merge-descriptorsmixin library function , which is responsible for assigning the methods declared in . The object itself is imported from lib / application.js .appprotoprotoCreating a new route
Now let's take a look at the code that is responsible for creating the method
app.getfrom our example.var slice = Array.prototype.slice;
// .../**
* Делегирование вызовов `.VERB(...)` `router.VERB(...)`.
*/// `methods` это массив методов HTTP, (нечто вроде ['get','post',...])
methods.forEach(function(method){
// Это сигнатура метода app.get
app[method] = function(path){
// код инициализации
// создание маршрута для пути внутри маршрутизатора приложения
var route = this._router.route(path);
// вызов обработчика со вторым аргументом
route[method].apply(route, slice.call(arguments, 1));
// возврат экземпляра `app`, что позволяет объединять вызовы методов в цепочки
returnthis;
};
});It is interesting to note that, in addition to the semantic features, all of the methods that implement HTTP actions, such as
app.get, app.post, app.putand the like, in terms of functionality, can be considered the same. If you simplify the above code, reducing it to the implementation of only one method get, you get something like this:app.get = function(path, handler){
// ...
var route = this._router.route(path);
route.get(handler)
returnthis
}Although the above function has 2 arguments, it is similar to the function
app[method] = function(path){...}. The second argument,, is handlerobtained by calling slice.call(arguments, 1). If in a nutshell, it
app.<method>simply saves the route in the application's router using its method route, and then sends it handlerto route.<method>. The router method is
route()declared in lib / router / index.js :// proto - это прототип объявления объекта `_router`
proto.route = functionroute(path){
var route = new Route(path);
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
return route;
};Not surprisingly, the method declaration
route.getin lib / router / route.js looks like the declaration app.get:methods.forEach(function (method) {
Route.prototype[method] = function () {
// `flatten` конвертирует вложенные массивы, вроде [1,[2,3]], в одномерные массивы
var handles = flatten(slice.call(arguments));
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
// ...
// Для каждого обработчика, переданного маршруту, создаётся переменная типа Layer,
// после чего её помещают в стек маршрутов
var layer = Layer('/', {}, handle);
// ...
this.stack.push(layer);
}
returnthis;
};
});Each route can have several processors; on the basis of each processor, a type variable is constructed
Layer, which is a data processing layer, which then goes on the stack.Layer Objects
And
_router, and routeuse objects of type Layer. In order to understand the essence of such an object, let's look at its constructor :functionLayer(path, options, fn){
// ...
this.handle = fn;
this.regexp = pathRegexp(path, this.keys = [], opts);
// ...
}When creating objects of type,
Layerthey are given a path, certain parameters, and a function. In the case of our router, this function is route.dispatch(in more detail we will talk about it below, in general terms, it is intended to send a request to a separate route). In the case of the route itself, this function is a handler function declared in the code of our example. Each type object
Layerhas a method handle_request , which is responsible for executing the function passed during object initialization. Recall what happens when you create a route using the method
app.get:- A route
this._routeris created in the application router ( ). - The route method
dispatchis assigned as the handler method of the corresponding objectLayer, and this object is pushed onto the router's stack. - The request handler is passed to the object
Layeras a handler method, and this object is pushed onto the route stack.
As a result, all handlers are stored inside an instance
appas objects of type Layerthat are inside the route stack, whose methods dispatchare assigned to objects Layerthat are on the router's stack:
Objects of type Layer in the router stack and in the route stack.
Received HTTP requests are processed in accordance with this logic. We will talk about them below.
Start HTTP server
After setting up the routes, you need to start the server. In our example, we refer to the method
app.listen, passing it the port number and the callback function as arguments. To understand the features of this method, we can refer to the lib / application.js file :app.listen = functionlisten() {
var server = http.createServer(this);
returnserver.listen.apply(server, arguments);
};It looks like
app.listen- this is just a wrapper around http.createServer. This point of view makes sense, because if we recall what we said at the very beginning, appit is just a function with a signature function(req, res, next) {...}that is compatible with the arguments necessary for http.createServer(the signature of this method is function (req, res) {...}). After understanding that, ultimately, everything that gives us express.js can be reduced to a very intelligent handler function, the framework does not look as complicated and mysterious as it used to be.
HTTP request processing
Now, when we know that
app- this is just a request handler , let's follow the path that an HTTP request passes through the express application. This path leads it to the handler declared by us. First, the request goes to the function
createApplication( lib / express.js ):var app = function(req, res, next){
app.handle(req, res, next);
};Then it goes to the method
app.handle( lib / application.js ):app.handle = functionhandle(req, res, callback){
// `this._router` - это место, где мы объявили маршрут, используя `app.get`
var router = this._router;
// ...
// Запрос попадает в метод `handle`
router.handle(req, res, done);
};The method is
router.handledeclared in lib / router / index.js :proto.handle = functionhandle(req, res, out){
varself = this;
//...
// self.stack - это стек, в который были помещены все
//объекты Layer (слои обработки данных)
var stack = self.stack;
// ...
next();
functionnext(err){
// ...
// Получение имени пути из запроса
var path = getPathname(req);
// ...
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
// ...
if (match !== true) {
continue;
}
// ... ещё некоторые проверки для методов HTTP, заголовков и так далее
}
// ... ещё проверки
// process_params выполняет разбор параметров запросов, в данный момент это не особенно важно
self.process_params(layer, paramcalled, req, res, function(err){
// ...
if (route) {
// после окончания разбора параметров вызывается метод `layer.handle_request`
// он вызывается с передачей ему запроса и функции `next`
// это означает, что функция `next` будет вызвана снова после того, как завершится обработка данных в текущем слое
// в результате, когда функция `next` будет вызвана снова, запрос перейдёт к следующему слою
return layer.handle_request(req, res, next);
}
// ...
});
}
};If we describe what is happening in a nutshell, then the function
router.handlegoes through all the layers in the stack, until it finds one that matches the path specified in the query. Then the layer method will be called handle_request, which will execute the predefined handler function. This handler function is a route method dispatchthat is declared in lib / route / route.js :Route.prototype.dispatch = functiondispatch(req, res, done){
var stack = this.stack;
// ...
next();
functionnext(err){
// ...
var layer = stack[idx++];
// ... проверки
layer.handle_request(req, res, next);
// ...
}
};Just as in the case of a router, during the processing of each route, the layers that this route has
handle_requestare searched for and the methods that the layer handler methods perform are called. In our case, this is the request handler, which is declared in the application code. Here, finally, the HTTP request falls into the code area of our application.

The request path in the express application
Results
Here we covered only the basic mechanisms of the express.js library, those that are responsible for the operation of the web server, but this library has many other features. We did not dwell on the checks that the requests pass before they are received by the handlers, we did not talk about the helper methods that are available when working with variables
resand req. And finally, we did not touch on one of the most powerful express features. It consists in the use of middleware that can be aimed at solving virtually any task - from parsing requests to implementing a full-fledged authentication system.We hope this material has helped you understand the main features of the express device, and now you, if necessary, can understand everything else by independently analyzing the parts of the source code of this library that you are interested in.
Dear readers! Do you use express.js?
