Categories
Node.js Tips

Node.js Tips — Environment Variables, Downloads, DNS, and More

Like any kind of apps, there are difficult issues to solve when we write Node apps.

In this article, we’ll look at some solutions to common problems when writing Node apps.

Node.js Require All Files in a Folder

We can export all modules in a folder by creating a module that exports all the modules.

Then we can import them all at once.

For instance, we can write:

index.js

exports.foo = require("./routes/foo.js");
exports.bar= require("./routes/bar.js");

app.js

const routes = require("./routes");

We can also write:

const fs = require("fs");
const normalizedPath = require("path").join(__dirname, "routes");

fs.readdirSync(normalizedPath).forEach((file) => {
  require(`./routes/${file}`);
});

Stringify an Error

We can log an error by looping through the properties of an object.

For instance, we can write:

const error = new Error('error');
const propertyNames = Object.getOwnPropertyNames(error);

for (const prop of propertyNames) {
  const descriptor = Object.getOwnPropertyDescriptor(error, prop);
  console.log(prop, descriptor);
}

We use the Object.getOwnPropertyNames method to get the properties of the error object.

Then we get the descriptors of each property.

Difference Between “process.stdout.write” and “console.log” in Node.js

console.log calls process.stdout.write with formatted output.

The code for console.log is just:

console.log = function (d) {
  process.stdout.write(d + 'n');
};

They are pretty much equivalent except for the extra line break.

Set NODE_ENV to production/development in Mac OS or Windows

We can use the export command to set environment variables in Mac OS.

For instance, we can set the NODE_ENV by running:

export NODE_ENV=production

In Windows, we can use the SET command:

SET NODE_ENV=production

We can also set process.env.NODE_ENV directly:

process.env.NODE_ENV = 'production';

We can set the environment when we run the file by running:

NODE_ENV=production node app.js

It also works in the package.json scripts :

{
  ...
  "scripts": {
    "start": "NODE_ENV=production node ./app"
  }
  ...
}

Using require in Express Templates

require is included with CommonJS.

It’s not a part of browser-side JavaScript natively.

To include JavaScript files, we can use the script tag.

Also, we can use CommonJS module.

Or we can use async modules.

Browserify, Webpack, and Rollup can bundle modules into build artifacts usable by the browser.

We can also use ES6 modules natively in browsers with the type attribute set to module .

For instance, we can write:

script.js

export const hello = () => {
  return "Hello World";
}

We can then include script.js by writing:

<script type="module" src="script.js"></script>

Measure the Execution Time of JavaScript Code with Callbacks

We can use the console.time and console.timeEnd methods to measure the time of a script.

For instance, if we have:

for (let i = 1; i < 10; i++) {
  const user = {
    id: i,
    name: "name"
  };
  db.users.save(user, (err, saved) => {
    if(err || !saved) {
      console.log("error");
    } else {
      console.log("saved");
    }
  });
}

We can call the methods by writing:

console.time("save");

for (let i = 1; i < 10; i++) {
  const user = {
    id: i,
    name: "name"
  };
  db.users.save(user, (err, saved) => {
    if(err || !saved) {
      console.log("error");
    } else {
      console.log("saved");
    }
    console.timeEnd("save");
  });
}

We’ve to pass in the same string to time and timeEnd .

Download a File from NodeJS Server Using Express

We can download a file with the res.download method in an Express route.

For instance, we can write:

const path = reqyire('path');

app.get('/download', (req, res) => {
  const file = path.join(`${__dirname}, 'folder', 'img.jpg');
  res.download(file);
});

We just get the path and pass it to res.download .

URl Encode a String in Node.js

We can URI encode a string in Node.jhs with the querystring module.

For instance, we can write:

const querystring = require("querystring");
const result = querystring.stringify({ text: "hello world"});

Then we get 'text=hello%20world’ for result .

Get Local IP Address in Node.js

We can get a local IP address in a Node app with the dns module.

For instance, we can write:

const dns = require('dns');
const os = require('os');

dns.lookup(os.hostname(), (err, addr, fam) => {
  console.log(addr);
})

We call lookup with the hostname to get the IP address from the hostname.

Then addr has the IP address.

Conclusion

We can get the IP address with the dns module.

console has methods to let us measure the timing of code.

We can require files dynamically.

Environment variables can be set dynamically.

Categories
Node.js Tips

Node.js Tips — File Operations, Command Line Arguments, and More

Like any kind of apps, there are difficult issues to solve when we write Node apps.

In this article, we’ll look at some solutions to common problems when writing Node apps.

Pass Command-Line Arguments to a Node.js Program

We get command line arguments by using the process.argv property.

It’s an array with all the arguments including node and the script name.

'node' is always the first entry.

The script name is the 2nd entry.

The rest of the arguments are the remaining entries.

For instance, if we have:

node foo.js one two=three four

Then node is the first entry of process.argv , foo.js is the 2nd, one is the 3rd, and so on.

Writing files in Node.js

We can use the writeFile method to write a file in a Node app.

For instance, we can write:

const fs = require('fs');

fs.writeFile("/foo.txt", "hello world!", (err) => {
  if (err) {
    return console.log(err);
  }
  console.log("file saved");
});

We call writeFile to write the file.

The first argument is the path to the file.

The 2nd is the content of the file.

The callback is called when the file writing process ends.

err is the error object and it’s defined when an error is encountered.

There’s also a synchronous version call writeFileSync .

We can use it by writing:

fs.writeFileSync('/foo.txt', 'hello world');

The first argument is the file path.

The 2nd is the content.

How to Debug Node.js Applications

We can use the node-inspector package to debug Node apps.

We can install it by running:

npm install -g node-inspector

to install it globally.

Then we run node-debug app.js to run it with node-inspector which lets us debug it with breakpoints, profiling, etc.

We can also run node inspect app.js to run app.js with the debugger.

Read Environment Variables in Node.js

We can read environment variables in Node apps by using the process.env object.

For instance, if we have the environment variable FOO , we can write:

process.env.FOO

Remove Empty Elements from an Array in Javascript

To remove empty from a JavaScript array, we can use the filter method.

For example, we can write:

const arr = [0, 1, null, undefined, 2,3,,,,6];
const filtered = arr.filter((el) => (el !== null || typeof el !== 'undefined'));

This will return an array created from arr with all the null and undefined removed.

Using async/await with a forEach loop

We can use async and await with the for-of loop.

For instance, we can write:

const printFiles = async (filePaths) => {
  for (const file of filePaths) {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }
}

We use the promisified version of readFile to read the file inside each iteration of the for-of loop.

To run promises in parallel, we can use the Promise.all method.

For instance, we can write:

const printFiles = async (filePaths) => {
  await Promise.all(filePaths.map(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  }));
}

We call map to filePaths to map the paths to promises.

Then we call Promise.all on the array of promises.

Get a List of the Names of All Files Present in a Directory in a Node App

We can get a list of names of all files in a directory by using the readdir or readdirSync method.

For example, we can write:

const fs = require('fs');

fs.readdir('/tesrDir', (err, files) => {
  files.forEach(file => {
    console.log(file);
  });
});

files have the array of file strings, buffer, or directory entries.

To read a directory synchronously, we can write:

const fs = require('fs');

fs.readdirSync('/testDir').forEach(file => {
  console.log(file);
});

We read a directory synchronously.

Parse JSON with Node.js

We can require JSON files or parse them with JSON.parse .

For example, we can write:

const config = require('./config.json');

or:

const config = require('./config');

The extension is optional.

Also, we can use JSON.parse to parse JSON strings.

Conclusion

We can get command-line arguments with process.argv .

Environment variables are read into the process.env object.

require can be used to import JSON files.

The fs module has methods to read and write files.

The for-of loop can run promises sequentially.

Categories
JavaScript Best Practices

Maintainable JavaScript — Browser Detection

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at how to detect browsers.

Browser Detection

Browser detection is a tricky topic.

There’s no perfect way to detect the actual browser.

User-Agent Detection

One way to detect the browser is user-agent detection.

The client or server can look at the user-agent string to determine the browser that made the request.

The browser’s user-agent string can be found in the navigation.userAgent property.

So we may think about writing something like:

if (navigator.userAgent.includes("MSIE")) {
  // ...
} else {
  // ...
}

We check for Internet Explorer by using the user-agent string.

The problem is that parsing the user-agent string is hard.

Also, browsers have copied each other to maintain compatibility.

With every new browser release, we need to update the user-agent detection code.

This added to the maintenance burden that we have to do.

User-agent string checking should be the last approach to check for the correct course of action.

If we check for user-agent strings, then we should only check the older browsers.

This is because newer browsers probably have the new features we want.

New features won’t be added to old browsers.

Feature Detection

Instead of checking the user-agent string, we should check whether the feature we want exists in the browser.

To do that, we can check for properties.

For instance, we can write:

if (typeof document.getElementById === 'function') {
  // ...
}

to check that document.getElementById is a function.

This way, we know exactly what we’re looking for exists or not.

It doesn’t rely on knowledge of which browser is used and only which features are available.

Therefore, it’s easy to support new browsers.

We should provide a logical fallback is there’s no feature available in the browser to provide the functionality that we want.

If we want to use newer features, then we’ve to different kinds of checks for different browsers’ implementations.

For instance, we may write:

function setAnimation(callback) {
  if (window.requestAnimationFrame) {
    return requestAnimationFrame(callback);
  } else if (window.mozRequestAnimationFrame) {
    return mozRequestAnimationFrame(callback);
  } else if (window.webkitRequestAnimationFrame) {
    return webkitRequestAnimationFrame(callback);
  } else if (window.oRequestAnimationFrame) {
    return oRequestAnimationFrame(callback);
  } else if (window.msRequestAnimationFrame) {
    return msRequestAnimationFrame(callback);
  } else {
    return setTimeout(callback, 0);
  }
}

to check each browser’s implementation of the requestAnimation method.

If none of the variants available, then we use the setTimeout method.

Avoid Feature Inference

We should assume that if one feature is available, then the related features are available.

For instance, we shouldn’t assume that if document.getElementsByTagName() is present, then document.getElementById() is present.

This also applies to any other kind of object.

Avoid Browser Inference

Also, we shouldn’t infer the browser from a given feature.

So we shouldn’t write something like:

if (document.all) {
  //...
} else {
  //...
}

to infer that is document.all exists, then IE is being used.

The code above isn’t an equivalent to:

const isIE = navigator.userAgent.includes("MSIE");
if (isIE) {
  //...
} else {
  //...
}

We can’t assume that if one feature exists, then our code is running on a given browser.

This is because the feature can be present in other browsers.

Conclusion

Browser detection is a tricky topic.

User-agent string detection isn’t very accurate.

But we can check for features to see if they’re present before we use them.

Categories
JavaScript Best Practices

Maintainable JavaScript — Removing Methods and Inheritance

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at avoiding changing objects we don’t own.

Don’t Remove Methods

Removing methods from objects we didn’t create is also easy.

For instance, we can write:

document.getElementById = null;

Then we made document.getElementById null .

Now getElementById is no longer a function.

So we can’t call it.

Also, we cal delete properties with the delete operator.

delete operator works on regular objects, so we can write:

let person = {
  name: "james"
};
delete person.name;

and we remove the name property, so person.name would be undefined .

However, this operator has no effect on built-in library methods.

So if we write:

delete document.getElementById

that has no effect.

Removing existing methods is definitely a bad practice.

Developers expect the object to have methods described in the library documentation.

And existing code may be using the methods since everyone expected them to be there.

If we want to remove a method, then we should deprecate them so that they won’t be used for new code and will be removed from the existing code.

Then once they’re all gone, then we can remove it.

Removing would be the very last step.

Better Solutions

Modifying objects is definitely a bad idea.

We should adopt some common design patterns to avoid modifying objects we don’t own.

Object-Based Inheritance

One way to extend existing objects is to create a new object with whatever object we want as the prototype.

For instance, we can write:

const person = {
  name: "jane",
  sayName() {
    console.log(this.name);
  }
};

const student = Object.create(person);

We called Object.create with the person object as the prototype of the student object.

So we can call sayName on the student object since it’s inherited from person :

student.sayName();

Then we get 'jane' logged.

We can then define our own sayName method on student by writing:

const person = {
  name: "jane",
  sayName() {
    console.log(this.name);
  }
};

const student = Object.create(person);
student.sayName = function() {
  console.log('joe');
}

student.sayName()

Then we’ll see 'joe' logged instead of 'jane' .

Once the object is created, then we own it.

So we can do whatever we want with it without affecting other pieces of code and confusing people.

Type-Based Inheritance

Type-based inheritance works like object-based inheritance.

We inherit properties from a prototype.

But we create child classes or constructors instead of objects.

With ES6, we can create child classes by using the extends keyword.

For instance, we can create a subclass of Error by writing:

class Person {
  constructor(name) {
    this.name = name;
  }
}

class Author extends Person {}

We created the Author class which is a subclass of Person .

The extends keyword indicates that it’s a subclass.

This lets us create objects flexibly.

We can define any new properties and methods in the subclass to extend the Person class.

Conclusion

We shouldn’t remove methods from objects we don’t own so that no one will be confused.

Also, we don’t want to break existing code.

We can extend objects and classes to create the objects and classes we want.

Categories
JavaScript Best Practices

Maintainable JavaScript — Objects We Don’t Own

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at how to handle objects we don’t own.

Objects We Own

The objects we own are the ones that we create.

The code that creates the object may not be written by us, but the object is created by us.

This means the objects we don’t own our ones like native objects such as Object , Date , etc.

We also don’t own DOM objects, built-in global objects, or library objects.

These are all part of the project’s environment.

These should be treated as read-only.

We shouldn’t override methods to these objects.

And we shouldn’t add or remove existing methods from these objects.

This is because it’s easy for us to do things that no one else expects.

This leads to confusion and waste time tracing unexpected code.

Don’t Override Methods

Overriding methods of objects we don’t own is one of the worst practices in JavaScript.

Within scripts, it’s very easy to override an existing methods.

For instance, we can write:

document.getElementById = () => {
  return null;
}

Then everyone would be confused of why document.getElementById is always returning null .

In a script, there’s nothing preventing us from overwriting DOM methods.

We can also overwrite any other property in any library code.

For instance, someone may write code like:

document._originalGetElementById = document.getElementById;
document.getElementById = (id) => {
  if (id === "window") {
    return window;
  } else {
    return document._originalGetElementById(id);
  }
};

This is also bad since it changes the way that standard library methods work.

This also brings confusion just like anything else.

The new code is called with id is 'window' , but the original is used with anything else.

This is just as bad since getElementById sometimes works as we expect and sometimes does.

Therefore, we should never override any methods so that we always have them work like expect them to.

Don’t Add New Methods

It’s also pretty easy to add new methods to existing objects in JavaScript.

We only need to assign a function onto an existing object to make it a method.

It allows us to modify all kinds of objects.

For instance, we can add methods to document :

document.foo = () => {
  console.log('hello');
};

We can also add methods to the Array ‘s prototytpe :

Array.prototype.foo = () => {
  console.log('hello');
};

They’re both bad.

And there’s nothing stopping us from adding methods to them.

Like with overriding methods, we make a library object behave differently from what we expect.

Also, the method that we added may be added to the standard library.

Then we have 2 methods that do different things and we overwrote it with our version.

Even subtle differences can cause lots of confusion.

Conclusion

We shouldn’t add or override methods of any built-in or library objects.

This is because we’ll be confused on why the code works differently than we expect.