Modularization of nodejs
servers
A server requires the use of network resources, some of which perform expensive read/write operations. Testing servers introduce side effects, some of which expensive, and may cause unintended consequences when not mocked in the testing phase. To limit the chances of breaking something, testing servers have to be done in isolation.
The question to ask at this stage, is How to get there?. This blog article will explore some of the ways to answer this question.
The motivation for modularization is to reduce the complexity associated with large-scale expressjs
applications. In nodejs
servers context, we will shift focus on making sure most of the parts are accessible for tests in isolation.
In this article we will talk about:
- How to modularize
nodejs
server for reusability. - How to modularize
nodejs
server for testability. - How to modularize
nodejs
server for composability.
Even though this blog post was designed to offer complementary materials to those who bought my Testing
nodejs
Applications book, the content can help any software developer to tuneup working environment. You use this link to buy the book.
Show me the code
nodejs
application server comes in two flavors. Using native nodejs
library, or adopting a server provided via a framework, in our case expressjs
.
Using expressjs
framework a classic server code looks as is the following example:
var express = require('express'),
app = express()
/** .. more routes + code for app ... */
app.get('/', function (req, res) {
return res.send('Hello World!')
});
app.listen(port, function () {
console.log('Example app listening on port 3000!')
});
//source: https://expressjs.com/en/starter/hello-world.html
Example:
As the requirement increases, this file becomes exponentially big. The most application runs on top of expressjs
a popular library in nodejs
world. To keep the server.js
small, regardless of requirements and dependent modules, moving most of the code into modules makes a difference.
var http = require('http'),
hostname = 'localhost',
port = process.env.PORT || 3000,
server = http.createServer(function(req, res){
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
//Alternatively
var express = require('express'),
app = express(),
require('app/routes')(app),
server = http.createServer(app);
server.listen(port, hostname, function (){
console.log(['Server running at http://',hostname,':',port].join());
});
//source: https://nodejs.org/api/synopsis.html#synopsis_example
Example:
What can possibly go wrong?
When trying to figure out how to approach modularizing nodejs
servers, the following points may be a challenge:
- Understanding where to start, and where to stop with server modularization
- Understanding key parts that need abstraction, or how/where to inject dependencies
- Making servers testable
The following sections will explore more on making points stated above work.
How to modularize nodejs
server for reusability
How to apply modularization technique in a server context or How to break down larger server file into a smaller granular alternative.
The server reusability becomes an issue when it becomes clear that the server bootstrapping code either needs some refactoring or presents an opportunity to add extra test coverage.
In order to make the server available to the third-party sandboxed testing environment, the server has to be exportable first.
In order to be able to load and mock/stub certain areas of the server code, still the server has to be exportable.
Like any other modularization technique we used, two steps are going to be in play. Since our case concerns multiple players, for instance, expressjs
WebSocket and whatnot, we have to look at the server like an equal of those other possible servers.
How to modularize nodejs
server for testability
Simulations of start/stop while running tests are catalysts of this exercise.
Testability and composability are other real drives to get the server to be modular. A modular server makes it easy to load the server as we load any other object into the testing sandbox, as well as mocking any dependency we deem unnecessary or prevents us to get the job done.
Simulation of Start/Stop while running tests – How to correctly unit test express server – There is a better code structure organization, that make it easy to test, get coverage, etc. Testing
nodejs
with mocha
The previous example shows how simpler becomes server initialization, but that comes with the additional library to install. Modularization of the above two code segments makes it possible to test the server in isolation.
module.exports = server;
Example: Modularization – this line makes server available in our tests ~ source
How to modularize nodejs
server for composability
The challenge is to expose the HTTP server, in a way redis
/websocket
or agenda
can re-use the same server. Making the server injectable.
The composability of the server is rather counter-intuitive. In most cases, the server will be injected into other components, for those components to mount additional server capabilities. The code sample proves this point by making the HTTP server available to a WebSocket component so that the WebSocket can be aware and mounted/attached to the same instance of the HTTP server.
var http = require('http'),
app = require('express')(),
server = http.createServer(app),
sio = require("socket.io")(server);
...
module.exports = server;
Conclusion
Modularization is key in making nodejs
server elegant, serve as a baseline to performance improvements and improved testability. In this article, we revisited how to achieve nodejs
server modularity, with stress on testability and code reusability. There are additional complimentary materials in the “Testing nodejs
applications” book.