Node.js has seen an important growth in the past years, with big companies such as Walmart or PayPal adopting it. More and more people are picking up Node and publishing modules to NPM at such a pace that exceeds other languages. However, the Node philosophy can take a bit to get used to, especially if you have switched from another language.
In this article we will talk about the most common mistakes Node developers make and how to avoid them. You can find the source code for the examples on github.
1 Not using development tools
- nodemon or supervisor for automatic restart
- In-browser live reload (reload after static files and/or views change)
1.1 Automating restarts
Most of us are probably used to saving a file in the editor, hit [CTRL+C] to stop the application and then restart it by pressing the [UP] arrow and [Enter]. However you can automate this repetitive task and make your development process easier by using existing tools such as:
- nodemon
- node-supervisor
- forever
- npm i nodemon -g
- # node server.js
- $ nodemon server.js
- 14 Nov 21:23:23 - [nodemon] v1.2.1
- 14 Nov 21:23:23 - [nodemon] to restart at any time, enter `rs`
- 14 Nov 21:23:23 - [nodemon] watching: *.*
- 14 Nov 21:23:23 - [nodemon] starting `node server.js`
- 14 Nov 21:24:14 - [nodemon] restarting due to changes...
- 14 Nov 21:24:14 - [nodemon] starting `node server.js`
1.2 Automatic browser refresh
Besides reloading the Node application when the source code changes, you can also speed up development for web applications. Instead of manually triggering the page refresh in the browser, we can automate this as well using tools such as livereload.
They work similarly to the ones presented before, because they watch for file changes in certain folders and trigger a browser refresh in this case (instead of a server restart). The refresh is done either by a script injected in the page or by a browser plugin.
Instead of showing you how to use livereload, this time we will create a similar tool ourselves with Node. It will do the following:
- Watch for file changes in a folder;
- Send a message to all connected clients using server-sent events; and
- Trigger the page reload.
- express - for creating the sample web application
- watch - to watch for file changes
- sendevent - server-sent events, SSE (an alternative would have been websockets)
- uglify-js - for minifying the client-side JavaScript files
- ejs - view templates
- var express = require('express');
- var app = express();
- var ejs = require('ejs');
- var path = require('path');
- var PORT = process.env.PORT || 1337;
- // view engine setup
- app.engine('html', ejs.renderFile);
- app.set('views', path.join(__dirname, 'views'));
- app.set('view engine', 'html');
- // serve an empty page that just loads the browserify bundle
- app.get('/', function(req, res) {
- res.render('home');
- });
- app.listen(PORT);
- console.log('server started on port %s', PORT);
- var reloadify = require('./lib/reloadify');
- reloadify(app, __dirname + '/views');
- var sendevent = require('sendevent');
- var watch = require('watch');
- var uglify = require('uglify-js');
- var fs = require('fs');
- var ENV = process.env.NODE_ENV || 'development';
- // create && minify static JS code to be included in the page
- var polyfill = fs.readFileSync(__dirname + '/assets/eventsource-polyfill.js', 'utf8');
- var clientScript = fs.readFileSync(__dirname + '/assets/client-script.js', 'utf8');
- var script = uglify.minify(polyfill + clientScript, { fromString: true }).code;
- function reloadify(app, dir) {
- if (ENV !== 'development') {
- app.locals.watchScript = '';
- return;
- }
- // create a middlware that handles requests to `/eventstream`
- var events = sendevent('/eventstream');
- app.use(events);
- watch.watchTree(dir, function (f, curr, prev) {
- events.broadcast({ msg: 'reload' });
- });
- // assign the script to a local var so it's accessible in the view
- app.locals.watchScript = '<script>' + script + '</script>';
- }
- module.exports = reloadify;
The frontend JS file is pretty simple, it will just listen to the SSE messages and reload the page when needed:
- (function() {
- function subscribe(url, callback) {
- var source = new window.EventSource(url);
- source.onmessage = function(e) {
- callback(e.data);
- };
- source.onerror = function(e) {
- if (source.readyState == window.EventSource.CLOSED) return;
- console.log('sse error', e);
- };
- return source.close.bind(source);
- };
- subscribe('/eventstream', function(data) {
- if (data && /reload/.test(data)) {
- window.location.reload();
- }
- });
- }());
- ...
- <%- watchScript %>
- ...
2 Blocking the event loop
Since Node.js runs on a single thread, everything that will block the event loop will block everything. That means that if you have a web server with a thousand connected clients and you happen to block the event loop, every client will just...wait.
Here are some examples on how you might do that (maybe unknowingly):
- Parsing a big json payload with the JSON.parse function;
- Trying to do syntax highlighting on a big file on the backend (with something like Ace or highlight.js); or
- Parsing a big output in one go (such as the output of a git log command from a child process).
Luckily you can monitor the event loop delay to detect anomalies. This can be achieve either via proprietary solutions such as StrongOps or by using open-source modules such as blocked.
The idea behind these tools is to accurately track the time spend between an interval repeatedly and report it. The time difference is calculated by getting the time at moment A and moment B, subtracting the time at moment A from moment B and also subtracting the time interval.
Below there's an example on how to achieve that. It does the following:
- Retrieve the high-resolution time between the current time and the time passed as a param;
- Determines the delay of the event loop at regular intervals;
- Displays the delay in green or red, in case it exceeds the threshold; then
- To see it in action, each 300 miliseconds a heavy computation is executed.
- var getHrDiffTime = function(time) {
- // ts = [seconds, nanoseconds]
- var ts = process.hrtime(time);
- // convert seconds to miliseconds and nanoseconds to miliseconds as well
- return (ts[0] * 1000) + (ts[1] / 1000000);
- };
- var outputDelay = function(interval, maxDelay) {
- maxDelay = maxDelay || 100;
- var before = process.hrtime();
- setTimeout(function() {
- var delay = getHrDiffTime(before) - interval;
- if (delay < maxDelay) {
- console.log('delay is %s', chalk.green(delay));
- } else {
- console.log('delay is %s', chalk.red(delay));
- }
- outputDelay(interval, maxDelay);
- }, interval);
- };
- outputDelay(300);
- // heavy stuff happening every 2 seconds here
- setInterval(function compute() {
- var sum = 0;
- for (var i = 0; i <= 999999999; i++) {
- sum += i * 2 - (i + 1);
- }
- }, 2000);
- https://github.com/hapijs/heavy/blob/bbc98a5d7c4bddaab94d442210ca694c7cd75bde/lib/index.js#L70
- https://github.com/tj/node-blocked/blob/master/index.js#L2-L14
Written by Alexandru Vladutu
If you found this post interesting, follow and support us.
Suggest for you:
Complete Node JS Developer Course Building 5 Real World Apps
Node.js Tutorials: The Web Developer Bootcamp
Learn How To Deploy Node.Js App on Google Compute Engine
Learn and Understand NodeJS
Learn Nodejs by Building 12 Projects
No comments:
Post a Comment