Node.js: data types exist and they may hurt

Node.js: data types exist and they may hurt

JavaScript is loosely typed. This is fine in 90% of the cases. Let's see what may happen in the remaining 10%.

JavaScript is loosely typed. This is fine in 90% of the cases. Let's see what may happen in the remaining 10%.

Think of numeric conversions, i.e. converting strings to numbers. parseInt() returns NaN (Not A Number) when the conversion to an integer fails.

This method has been designed to convert numerical strings to integers. Since JavaScript is a loosely typed language, there is no formal restriction to the possible values that can be passed to parseInt(). This implies the fact that if we don't perform a pre-check and data validation, we can get unwanted results that may negatively affect our code.

We can test the default behaviour with the following test:


'use strict';

const values = ['10', '5.2', 10, 5.2, true, false, null, '', {a: 1}, {}, [1], []];

values.forEach(value => {
    console.log(value + ': ' + parseInt(value, 10));
});

Results are as follows:

Input Output
'10' 10
'5.2' 5
10 10
5.2 5
true NaN
false NaN
null NaN
'' NaN
[object Object] NaN
[object Object] NaN
[1] 1
[] NaN

We can see that boolean values evaluate to NaN, whereas the array evaluates to 1. The behaviour seen above follows the ECMAScript specification, but on a production environment may lead to several problems if we omit the data validation step.

Suppose for a moment that you need to get a string which represents a Unix timestamp. You need to convert this value into a valid date that, in turn, will be used in a MongoDB document.


'use strict';

const app = require('expresss')();
const posts = require('./models/posts');
const bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.post('/api/post', (req, res) => {
    let timestamp = req.body.time; // 1) May be empty '' 
    let pubDate = new Date(parseInt(timestamp, 10)); // 2) Invalid Date {}
    //...
});

app.listen(8000);

In step (1) the passed value may be an empty string, thus the conversion to an integer will produce NaN in this case which will raise an error (2) during the conversion to a valid Date object.

So, by simply omitting:


app.post('/api/post', (req, res) => {
    let timestamp = req.body.time;
    if(/^\d+$/.test(timestamp)) {
        // OK
    } else {
        // Not OK
    }
});

app.listen(8000);

we are actually exposing our entire application to several side effects, not only to mere errors.

To summarise: data types actually exist in JavaScript and if we don't validate data before performing a conversion, we're compromising all data and application integrity.