Categories
JavaScript Nodejs

Using the Node.js OS Module — Part 2

The Node.js OS module has many useful utility functions for getting information about the computer system that the OS module’s program it is running on. It can provide information about hardware like CPUs, endianness, the home directory, IP address, hostname, the platform the program is running on, system uptime, information about the currently logged in user and more.

We can use the OS module by writing const os = require('os'); at the top of our code. There are many useful properties in the OS module. Below are more useful properties in the OS module:

os.cpus

The os.cpus() function returns an array of objects that has various information about each logical core of the host computer’s CPU. Each entry has a few properties. There’s the model property, which is a string that indicates the model of the computer’s CPU. The speed property is a numeric property that is measured in megahertz. The times property is an object with the following properties:

  • user — a numeric property that indicates the number of milliseconds the CPU has spent in user mode.
  • nice — a numeric property that indicates the number of milliseconds the CPU has spent in nice mode. Nice mode is where the CPU is running processes that have a positive nice value, which means a process that’s a lower priority.
  • sys — a numeric property that indicates the number of milliseconds the CPU has spent in sys mode.
  • idle — a numeric property that indicates the number of milliseconds the CPU has spent in idle mode. A CPU is idle when it’s not used by any program.
  • irq— a number property the number of milliseconds the CPU has spent in IRQ mode. IRQ is a hardware signal that makes the CPU temporarily stop a running program and allow an interrupt handler program to run instead.

For example, we can use the function like the following:

console.log(os.cpus());

We can get the output that resembles the following:

[ { model: 'Intel(R) Xeon(R) CPU @ 2.30GHz',  
    speed: 2300,  
    times:  
     { user: 3367100, nice: 0, sys: 757800, idle: 9833900, irq: 0 } },  
  { model: 'Intel(R) Xeon(R) CPU @ 2.30GHz',  
    speed: 2300,  
    times:  
     { user: 3387000, nice: 0, sys: 730100, idle: 10054300, irq: 0 } },  
  { model: 'Intel(R) Xeon(R) CPU @ 2.30GHz',  
    speed: 2300,  
    times:  
     { user: 3259600, nice: 0, sys: 748300, idle: 10168800, irq: 0 } },  
  { model: 'Intel(R) Xeon(R) CPU @ 2.30GHz',  
    speed: 2300,  
    times:  
     { user: 3229700, nice: 0, sys: 755800, idle: 10195600, irq: 0 } } ]

os.endianness

The os.endianness() function returns a string identifying the endianness of the CPU for which the Node.js run-time binary is compiled from. The 2 possible values are:

  • 'BE' — big-endian. A big-endian CPU order places with the most significant byte first and the least significant byte last.
  • 'LE' — little-endian, the opposite of big-endian.

If we run console.log on the return value of os.endianness() we may get something like the following:

'LE'

os.freemem

The os.freemem() function returns an integer that indicates the amount of free memory in the number of bytes. If we run console.log of the output of os.freemem() , we get something like:

15338930176

os.getPriority

The os.getPriority function returns an integer indicating the scheduling priority of the process which is specified by the PID. It takes one argument, which is the process ID passed in as an integer. If the PID isn’t provided or it’s 0, then the priority of the current process is returned. For example, if we write:

os.getPriority()

Then we get a number. 19 indicates the lowest CPU priority and -20 indicates the highest. Processor priority levels in Node.js are defined by the following constants:

  • PRIORITY_LOW — The lowest process scheduling priority. This corresponds to IDLE_PRIORITY_CLASS on Windows and a nice value of 19 on all other platforms.
  • PRIORITY_BELOW_NORMAL — This corresponds to BELOW_NORMAL_PRIORITY_CLASS on Windows and a nice value of 10 on all other platforms.
  • PRIORITY_NORMAL — The default process scheduling priority. This corresponds to NORMAL_PRIORITY_CLASS on Windows and a nice value of 0 on all other platforms.
  • PRIORITY_ABOVE_NORMAL — This corresponds to ABOVE_NORMAL_PRIORITY_CLASS on Windows and a nice value of -7 on all other platforms.
  • PRIORITY_HIGH — . This corresponds to HIGH_PRIORITY_CLASS on Windows and a nice value of -14 on all other platforms.
  • PRIORITY_HIGHEST — The highest process scheduling priority. This corresponds to REALTIME_PRIORITY_CLASS on Windows and a nice value of -20 on all other platforms.

os.homedir

The os.homedir function returns a string of the path o the home directory of the user. On Unix and Linux systems, it will use the $HOME variable if it’s defined. Otherwise, it will look up the home directory path by the effective UID, which is the user ID of the user identity that you’re currently assuming. For example, if you used sudo to use the computer as a root user then the effective user ID would be the user ID of the root user. On Windows, the value of the USERPROFILE will be used if it’s defined. Otherwise, it will be the path of the profile directory of the current user. For example, if we run console.log on the output of os.homedir() , then we may get something like:

'/home/runner'

if you’re on a Linux system.

os.hostname

The os.hostname function returns the hostname of the operating system as a string. For example, if we call console.log on the return value of os.hostname() , then we may get something like ‘5b84600c80eb’ .

os.loadavg

The os.loadavg function returns a number array containing 1, 5, and 15-minute load averages. The load average measures system activity, which is calculated by the operating system and expressed as a decimal number. The ideal load average should be less than the number of logical CPUs in the system. This function only works on Unix and Linux system. On Windows, it always returns [0,0,0] . For example, if we run this function on the Linux system, like in the following code:

console.log(os.loadavg())

We may get something like:

[ 12.60791015625, 13.3916015625, 9.8798828125 ]

os.networkInterfaces

The os.networkInterfaces function returns an object that has the network interfaces that have been assigned a network address. The keys on the returned object identify a network interface. The corresponding value of the keys is an array of objects that describe an assigned network address. Properties of an assigned network address object include:

  • address — a string that has the assigned IPv4 or IPv6 address
  • netmask — a string that has the IPv4 or IPv6 network mask
  • family — a string that has 2 possible values: IPv4 or IPv6
  • mac — a string that has the MAC address of the network interface
  • internal — a boolean value that is true if the network interface is a loopback or similar interface that is not remotely accessible. Otherwise, it’s false
  • scopeid — a number that has the IPv6 scope ID, applicable only when family is IPv6
  • cidr — a string that has the assigned IPv4 or IPv6 address with the routing prefix in CIDR notation. CIDR notation has 2 groups of numbers. The first is the group of bits, which is the network address. The first group is followed by a slash. The second group is the number of bits that are considered significant for network routing. For example, if we have an IP address 192.168.0.15/24, then 192.168.0.15 is the network address and 24 is the number of significant bits for network routing purposes. If the netmask is invalid, this property is set to null.

For example, if we run:

console.log(os.networkInterfaces());

Then we may get something like:

{ lo:  
   [ { address: '127.0.0.1',  
       netmask: '255.0.0.0',  
       family: 'IPv4',  
       mac: '00:00:00:00:00:00',  
       internal: true,  
       cidr: '127.0.0.1/8' } ],  
  eth0:  
   [ { address: '172.18.0.103',  
       netmask: '255.255.0.0',  
       family: 'IPv4',  
       mac: '02:42:ac:12:00:67',  
       internal: false,  
       cidr: '172.18.0.103/16' } ] }

We have lo for the loopback address and eth0 for the Ethernet interface.

os.platform

The os.platform function returns a string that identifies that operating system platform of the computer that compiled the Node.js binary. The current possible values are 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', or 'win32' . It’s the same as the process.platform property. The value 'android' maybe returned if the Node.js binary is built on an Android device. However, Android support in Node.js is in the experimental phase. For example, if we run:

console.log(os.platform());

Then we may get something like:

'linux'

The Node.js OS module has many useful utility functions for getting information about the computer system that the OS module’s program is running on. The modules have many more properties containing useful information like CPUs, endianness, home directory, IP address, hostname, the platform the program is running on, system uptime, information about the currently logged in user and more.

Categories
JavaScript JavaScript Basics

Using the Javascript Conditional Operator

The JavaScript conditional operator is a basic building block of JavaScript programs.

The conditional operator is also called the ternary operator.

It lets us write expression that returns one thing if a given condition it truthy and return something else otherwise.

The conditional operator is denoted by the ? operator.

We can use it as follows:

const foo = condition ? 'foo' : 'bar';

In the code above, if condition is truthy, then 'foo' is returned. Otherwise, 'bar' is returned.

Then the returned value is assigned to the foo variable.

The code is above is short for:

if (condition) {
  return 'foo';
}
else {
  return 'bar';
}

As we can see, the code above is much longer than using the conditional operator.

Since it’s save so much typing and space on the page, we should use the conditional operator instead of using if statements if we want to write code that returns one thing if a given condition is truthy and something else otherwise.

Categories
JavaScript JavaScript Basics

Introduction to JavaScript Strict Mode

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at the benefits of using the strict mode directive.

Add Use Strict Mode Directive in Our Scripts

If we’re using old-school JavaScript script files, then we should add the 'use strict' directive into the top of our script.

Strict mode eliminates lots of silent errors by changing them to throw errors.

It also fixes mistakes that make optimizing our JavaScript code hard to increase their performance.

Also, it bans syntax that may be used in future JavaScript versions. Keywords like class can’t be used to declare variables, for example.

Strict mode can be applied to the whole script by putting the directive at the top or just in the function level by putting it in functions.

However, we should just apply it everywhere since it brings lots of benefits.

For instance, we can’t declare variables accidentally with 'use strict':

'use strict';
x = 1;

Since we have the 'use strict' directive on top of our script, we’ll get an error with the x = 1 expression since we haven’t declared x yet.

Without strict mode, this would create a global variable called x and assign 1 to it and pollute the global scope.

That isn’t good because it may overwrite another global variable with the same name.

Also, we can assign values to another global value like undefined or Infinity:

With strict mode on, code like the following will throw a TypeError:

var undefined = 1;
var Infinity = 1;

Assigning values to a non-writable property will also fail with a TypeError thrown:

var obj = {};
Object.defineProperty(obj, 'foo', { value: 42, writable: false });
obj.foo = 9;

We also wouldn’t be able to add properties that has preventExtensions called on it to prevent that:

var obj = {};
Object.preventExtensions(obj);
obj.foo = 'a';

The code above would throw a TypeError.

Attempting to delete undeletable properties will also throw an error. If we write something like:

'use strict';
delete Object.prototype;

Then we’ll get a TypeError.

Strict mode also requires function parameters to be unique. For instance, if we write:

'use strict';
function add(a, a, c) {  
  return a + a + c; 
}

Then we’ll get an ‘Uncaught SyntaxError: Duplicate parameter name not allowed in this context’ error since we have JavaScript strict mode on.

Also, it prevents numbers with a leading 0 from being used. The leading zero for octal numbers is rarely used and developers often assume that it’s still a decimal number.

Therefore if we have the following the strict mode throws an error:

'use strict';
var sum = 015 + 222;

If we run that, we get ‘Uncaught SyntaxError: Octal literals are not allowed in strict mode.’

The with statement is prohibited in strict mode. With strict mode, confusing code like this is prohibited:

'use strict';
var x = 17;
with(obj) {
  console.log(x);
}

The code above is confusing because x can either be obj.x or just x. It’s hard to tell what it’s referring to.

With strict mode on as we have above, we get ‘Uncaught SyntaxError: Strict mode code may not include a with statement’.

Doing anything to arguments or eval will also return syntax errors, so code like the following:

'use strict';
eval = 'foo';
arguments++;

Then we get ‘Uncaught SyntaxError: Unexpected eval or arguments in strict mode’ when we run the code above since we have JavaScript strict mode on.

We shouldn’t use these entities regardless of whether JavaScript strict mode is on, so it’s good that strict mode prevents expressions like these from running at least.

With strict mode on, the this value passed into call, apply, or bind aren’t boxed into an object.

Therefore, the original value is preserved if we pass in a primitive value. For instance, if we have:

'use strict';
function fn() {  
  return this;
}
console.log(fn.call(1) === 1);

Then we get 1 returned from fn instead of new Number(1), so we get that the console log output returns true.

Strict mode is enabled automatically for modules so we don’t need the directive on top of our code.

Conclusion

With so many benefits to strict mode, we should always enable it in old-school scripts.

If we use JavaScript modules, then it’s enabled by default, so we don’t have to worry about it.

We just have to put 'use strict' on top of all our scripts.

Categories
JavaScript Nodejs

Using the Node.js OS Module (Part 1)

The Node.js OS module has many useful utility functions for getting information about the computer system that the OS module’s program is running on. It can provide information about hardware such as CPUs, endianness, the home directory, IP address, hostname, the platform the program is running on, system uptime, information about the currently logged in user and more.

We can use the OS module by writing const os = require('os'); at the top of a file. There are many useful properties in the OS module. Below are some of the useful properties in the OS module:

os.EOL

The os.EOL property is a string constant that has the operating system specific end of line marker. For POSIX operating system, it’s \n and for Windows, it’s \r\n . We can use it like the following code as an example:

console.log(`End of line market is ${os.EOL}`)

Then we get:

'End of line market is \n'

os.arch()

The os.arch() function returns a string that tells us the operating system CPU architecture that the Node.js binary is compiled on. Possible values are 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', 'x32', and 'x64'. It’s the same as the process.arch function. For example, we can use it like in the following code:

console.log(`Node.js is built in ${os.arch()}`)

The we get:

'Node.js is built in x64'

if Node.js was compiled on an x64 system

os.constants

The os.constants property has a collection of constants for error codes, process signals, etc. The following are the constants from os.constants :

Signal Constants

  • SIGHUP — signal sent to indicate when a controlling terminal is closed or a parent process exits.
  • SIGINT — signal sent to indicate when a user wishes to interrupt a process ((Ctrl+C)).
  • SIGQUIT — signal sent to indicate when a user wishes to terminate a process and perform a core dump.
  • SIGILL — signal sent to a process to notify that it has attempted to perform an illegal, malformed, unknown, or privileged instruction.
  • SIGTRAP — signal sent to a process when an exception has occurred.
  • SIGABRT — signal sent to a process to request that it abort.
  • SIGIOT — same as SIGABRT
  • SIGABRTSIGBUS — signal sent to a process to notify that it has caused a bus error.
  • SIGFPE — signal sent to a process to notify that it has performed an illegal arithmetic operation.
  • SIGKILL — signal sent to a process to terminate it immediately.
  • SIGUSR1 SIGUSR2 — signal sent to a process to identify user-defined conditions.
  • SIGSEGV — signal sent to a process to notify of a segmentation fault.
  • SIGPIPE — signal sent to a process when it has attempted to write to a disconnected pipe.
  • SIGALRM — signal sent to a process when a system timer elapses.
  • SIGTERM — signal sent to a process to request termination.
  • SIGCHLD — signal sent to a process when a child process terminates.
  • SIGSTKFLT — signal sent to a process to indicate a stack fault on a co-processor.
  • SIGCONT — signal sent to instruct the operating system to continue a paused process.
  • SIGSTOP — signal sent to instruct the operating system to halt a process.
  • SIGTSTP — signal sent to a process to request it to stop.
  • SIGBREAK — signal sent to indicate when a user wishes to interrupt a process.
  • SIGTTIN — signal sent to a process when it reads from the teletypewriter while in the background.
  • SIGTTOU — signal sent to a process when it writes to the teletypewriter while in the background.
  • SIGURG — signal sent to a process when a socket has urgent data to read.
  • SIGXCPU — signal sent to a process when it has exceeded its limit on CPU usage.
  • SIGXFSZ — signal sent to a process when it grows a file larger than the maximum allowed.
  • SIGVTALRM — signal sent to a process when a virtual timer has elapsed.
  • SIGPROF — signal sent to a process when a system timer has elapsed.
  • SIGWINCH — signal sent to a process when the controlling terminal has changed its size.
  • SIGIO — signal sent to a process when I/O is available.
  • SIGPOLL — same as SIGIO
  • SIGIOSIGLOST — signal sent to a process when a file lock has been lost.
  • SIGPWR — signal sentto a process to notify of a power failure.
  • SIGINFO — same as SIGPWR
  • SIGPWRSIGSYS — signal sent to a process to notify of a bad argument.
  • SIGUNUSED — same as SIGSYS

Error Constants for POSIX Systems

The following are constants for error indicators that are raised in POSIX systems.

  • E2BIG — list of arguments is longer than expected.
  • EACCES — the operation did not have sufficient permissions.
  • EADDRINUSE — the network address is already in use.
  • EADDRNOTAVAIL — the network address is currently unavailable for use.
  • EAFNOSUPPORT — he network address family is not supported.
  • EAGAIN — there is currently no data available and to try the operation again later.
  • EALREADY — the socket already has a pending connection in progress.
  • EBADF — file descriptor is not valid.
  • EBADMSG — invalid data message.
  • EBUSY — device or resource is busy.
  • ECANCELED — an operation was canceled.
  • ECHILD — there are no child processes.
  • ECONNABORTED — network connection has been aborted.
  • ECONNREFUSED — network connection has been refused.
  • ECONNRESET — network connection has been reset.
  • EDEADLK — resource deadlock has been avoided.
  • EDESTADDRREQ — a destination address is required.
  • EDOM — an argument is out of the domain of the function.
  • EDQUOT — the disk quota has been exceeded.
  • EEXIST — the file already exists.
  • EFAULT — invalid pointer address.
  • EFBIG — the file is too large.
  • EHOSTUNREACH — the host is unreachable.
  • EIDRM — the identifier has been removed.
  • EILSEQ — an illegal byte sequence encountered.
  • EINPROGRESS — an operation is already in progress.
  • EINTR — a function call was interrupted.
  • EINVAL — an invalid argument was provided.
  • EIO — unspecified I/O error.
  • EISCONN — the socket is connected.
  • EISDIR — the path is a directory.
  • ELOOP — too many levels of symbolic links in a path.
  • EMFILE — there are too many open files.
  • EMLINK — there are too many hard links to a file.
  • EMSGSIZE — the provided message is too long.
  • EMULTIHOP — a multihop was attempted.
  • ENAMETOOLONG — he filename is too long.
  • ENETDOWN — the network is down.
  • ENETRESET — the connection has been aborted by the network.
  • ENETUNREACH — the network is unreachable.
  • ENFILE — too many open files in the system.
  • ENOBUFS — no buffer space is available.
  • ENODATA — no message is available on the stream head read queue.
  • ENODEV — that there is no such device.
  • ENOENT — there is no such file or directory.
  • ENOEXEC — an exec format error.
  • ENOLCK — there are no locks available.
  • ENOLINK — a link has been severed.
  • ENOMEM — there is not enough space.
  • ENOMSG — there is no message of the desired type.
  • ENOPROTOOPT — a given protocol is not available.
  • ENOSPC — there is no space available on the device.
  • ENOSR — there are no stream resources available.
  • ENOSTR — a given resource is not a stream.
  • ENOSYS — a function has not been implemented.
  • ENOTCONN — the socket is not connected.
  • ENOTDIR — the path is not a directory.
  • ENOTEMPTY — the directory is not empty.
  • ENOTSOCK — the given item is not a socket.
  • ENOTSUP — a given operation is not supported.
  • ENOTTY — inappropriate I/O control operation.
  • ENXIO — no such device or address.
  • EOPNOTSUPP — an operation is not supported on the socket. While ENOTSUP and EOPNOTSUPP have the same value on Linux, according to POSIX.1 these error values should be distinct.)
  • EOVERFLOW — a value is too large to be stored in a given data type.
  • EPERM — the operation is not permitted.
  • EPIPE — a pipe is broken.
  • EPROTO — a protocol error.
  • EPROTONOSUPPORT — a protocol is not supported.
  • EPROTOTYPE — wrong type of protocol for a socket.
  • ERANGE — the results are too large.
  • EROFS — the file system is read only.
  • ESPIPE — invalid seek operation.
  • ESRCH — there is no such process.
  • ESTALE — the file handle is stale.
  • ETIME — timer expired
  • ETIMEDOUT — the connection timed out.
  • ETXTBSY — a text file is busy.
  • EWOULDBLOCK — the operation would block.
  • EXDEV — improper link.

Error Constants for Windows Systems

The following are constants for error indicators that are raised in Windows systems.

  • WSAEINTR — an interrupted function call.
  • WSAEBADF — an invalid file handle.
  • WSAEACCES — insufficient permissions to complete the operation.
  • WSAEFAULT — an invalid pointer address.
  • WSAEINVAL — an invalid argument was passed.
  • WSAEMFILE — there are too many open files.
  • WSAEWOULDBLOCK — a resource is temporarily unavailable.
  • WSAEINPROGRESS — an operation is currently in progress.
  • WSAEALREADY — an operation is already in progress.
  • WSAENOTSOCK — the resource is not a socket.
  • WSAEDESTADDRREQ — a destination address is required.
  • WSAEMSGSIZE — the message size is too long.
  • WSAEPROTOTYPE — the wrong protocol type for the socket.
  • WSAENOPROTOOPT — a bad protocol option.
  • WSAEPROTONOSUPPORT — the protocol is not supported.
  • WSAESOCKTNOSUPPORT — the socket type is not supported.
  • WSAEOPNOTSUPP — the operation is not supported.
  • WSAEPFNOSUPPORT — the protocol family is not supported.
  • WSAEAFNOSUPPORT — the address family is not supported.
  • WSAEADDRINUSE — the network address is already in use.
  • WSAEADDRNOTAVAIL — the network address is not available.
  • WSAENETDOWN — the network is down.
  • WSAENETUNREACH — the network is unreachable.
  • WSAENETRESET — the network connection has been reset.
  • WSAECONNABORTED — the connection has been aborted.
  • WSAECONNRESET — the connection has been reset by the peer.
  • WSAENOBUFS — there is no buffer space available.
  • WSAEISCONN — the socket is already connected.
  • WSAENOTCONN — the socket is not connected.
  • WSAESHUTDOWN — data cannot be sent after the socket has been shut down.
  • WSAETOOMANYREFS — there are too many references.
  • WSAETIMEDOUT — the connection has timed out.
  • WSAECONNREFUSED — the connection has been refused.
  • WSAELOOP — a name cannot be translated.
  • WSAENAMETOOLONG — a name was too long.
  • WSAEHOSTDOWN — a network host is down.
  • WSAEHOSTUNREACH — there is no route to a network host.
  • WSAENOTEMPTY — the directory is not empty.
  • WSAEPROCLIM — there are too many processes.
  • WSAEUSERS — the user quota has been exceeded.
  • WSAEDQUOT — the disk quota has been exceeded.
  • WSAESTALE — a stale file handle reference encountered.
  • WSAEREMOTE — the item is remote.
  • WSASYSNOTREADY — the network subsystem is not ready.
  • WSAVERNOTSUPPORTED — the winsock.dll version is out of range.
  • WSANOTINITIALISED — successful WSAStartup has not yet been performed.
  • WSAEDISCON — graceful shutdown is in progress.
  • WSAENOMORE — there are no more results.
  • WSAECANCELLED — an operation has been canceled.
  • WSAEINVALIDPROCTABLE — the procedure call table is invalid.
  • WSAEINVALIDPROVIDER — invalid service provider.
  • WSAEPROVIDERFAILEDINIT — the service provider failed to initialize.
  • WSASYSCALLFAILURE — a system call failure.
  • WSASERVICE_NOT_FOUND — service was not found.
  • WSATYPE_NOT_FOUND — a class type was not found.
  • WSA_E_NO_MORE — there are no more results.
  • WSA_E_CANCELLED — the call was canceled.
  • WSAEREFUSED — a database query was refused.

dlopen Constants

The result of the dlopen command are also included in the os.constants object. The dlopen command is for dynamically loading libraries into memory.

  • RTLD_LAZY — Perform lazy binding. Node.js sets this flag by default.
  • RTLD_NOW — Resolve all undefined symbols in the library before dlopen(3) returns.
  • RTLD_GLOBAL — Symbols defined by the library will be made available for symbol resolution of subsequently loaded libraries.
  • RTLD_LOCAL — Opposite of RTLD_GLOBAL. This is the default behavior if neither RTLD_GLOBAL or RTLD_LOCAL is specified.
  • RTLD_DEEPBIND — Make a self-contained library use its own symbols in preference to symbols from previously loaded libraries.

Priority Constants

The constants below are for setting the priority of scheduled processes. The nice value refers to the integer value for CPU scheduling priority which are used the nice program in Unix and Linux. However, the some of the values are also the same in Windows. The default value is 0. 19 indicates the lowest CPU priority while -20 indicates the highest.

  • PRIORITY_LOW — The lowest process scheduling priority. This corresponds to IDLE_PRIORITY_CLASS on Windows, and a nice value of 19 on all other platforms.
  • PRIORITY_BELOW_NORMAL — This corresponds to BELOW_NORMAL_PRIORITY_CLASS on Windows and a nice value of 10 on all other platforms.
  • PRIORITY_NORMAL — The default process scheduling priority. This corresponds to NORMAL_PRIORITY_CLASS on Windows and a nice value of 0 on all other platforms.
  • PRIORITY_ABOVE_NORMAL — This corresponds to ABOVE_NORMAL_PRIORITY_CLASS on Windows and a nice value of -7 on all other platforms.
  • PRIORITY_HIGH — . This corresponds to HIGH_PRIORITY_CLASS on Windows and a nice value of -14 on all other platforms.
  • PRIORITY_HIGHEST — The highest process scheduling priority. This corresponds to REALTIME_PRIORITY_CLASS on Windows and a nice value of -20 on all other platforms.

The schedule priorities ordered from lowest to highest are — PRIORITY_LOW, PRIORITY_BELOW_NORMAL, PRIORITY_NORMAL, PRIORITY_ABOVE_NORMAL, PRIORITY_HIGH, PRIORITY_HIGHEST .

The Node.js OS module has many useful utility functions for getting information about the computer system that the OS module’s program is running on. The modules have many more properties containing useful information like CPUs, endianness, home directory, IP address, hostname, the platform the program is running on, system uptime, information about the currently logged in user and more.

Categories
JavaScript Nodejs

How To Make a Simple Back End With User Accounts and Authentication

With single-page front-end apps and mobile apps being more popular than ever, the front end is decoupled from the back end. Since almost all web apps need authentication, there needs to be a way for front-end or mobile apps to store user identity data in a secure fashion.

JSON Web Tokens (JWT) is one of the most common ways to store authentication data on front-end apps. With Node.js, there are popular libraries that can generate and verify the JWT by checking for its authenticity. They do this by checking against a secret key stored in the back end and also by checking for an expiry date.

The token is encoded in a standard format that’s understood by most apps. It usually contains user identity data like user ID, user name, etc. It’s given to the user when the user successfully completes authentication.

In this piece, we will build an app that uses JWT to store authentication data.


Overview

For the back end, we’ll use the Express framework, which runs on Node.js, and for the front end, we’ll use the Angular framework. Both have their own JWT add-ons. On the back end, we have the jsonwebtoken package for generating and verify the token.

On the front end, we have the @auth0/angular-jwt module for Angular. In our app, when the user enters user name and password and they are in our database, then a JWT will be generated from our secret key, returned to the user, and stored on the front-end app in local storage. Whenever the user needs to access authenticated routes on the back end, they’ll need the token.

There will be a function in the back-end app called middleware to check for a valid token. A valid token is one that is not expired and verifies as valid against our secret key. There will also be a sign-up and user credential settings pages, in addition to a login page.


Building the App

With this plan, we can begin.

First, we create the front- and back-end app folders. Make one for each.

Then we start writing the back-end app. First, we install some packages and generate our Express skeleton code. We run npx express-generator to generate the code. Then we have to install some packages. We do that by running npm i @babel/register express-jwt sequelize bcrypt sequelize-cli dotenv jsonwebtoken body-parser cors . @babel/register allows us to use the latest JavaScript features.

express-jwt generates the JWT and verifies it against a secret key.bcrypt does the hashing and salting of our passwords. sequelize is our ORM for doing CRUD. cors allows our Angular app to communicate with our back end by allowing cross-domain communication. dotenv allows us to store environment variables in an .env file. body-parser is needed for Express to parse JSON requests.

Then we make our database migrations. First, we run npx sequelize-cli init to generate skeleton code for our database-to-object mapping. Then we run:

npx sequelize-cli model:generate --name User --attributes username:string, password:string, email:string

We make another migration and put:

'use strict';module.exports = {  
  up: (queryInterface, Sequelize) => {  
    return Promise.all([  
      queryInterface.addConstraint(  
        "Users",  
        ["email"],  
        {  
          type: "unique",  
          name: 'emailUnique'  
        }),

      queryInterface.addConstraint(  
        "Users",  
        ["userName"],  
        {  
          type: "unique",  
          name: 'userNameUnique'  
        }),  
  },

  down: (queryInterface, Sequelize) => {  
    return Promise.all([  
      queryInterface.removeConstraint(  
        "Users",  
        'emailUnique'  
      ),

      queryInterface.removeConstraint(  
        "Users",  
        'userNameUnique'  
      ),  
    ])  
  }  
};

This makes sure we don’t have two entries with the same username or email.

This creates the User model and will create the Users table once we run npx sequelize-cli db:migrate .

Then we write some code. First, we put the following in app.js :

require("[@babel/register](http://twitter.com/babel/register)");  
require("babel-polyfill");  
require('dotenv').config();  
const express = require('express');  
const bodyParser = require('body-parser');  
const cors = require('cors');  
const user = require('./controllers/userController');  
const app = express();app.use(cors())  
app.use(bodyParser.urlencoded({ extended: true }));  
app.use(bodyParser.json());

app.use((req, res, next) => {  
  res.locals.session = req.session;  
  next();  
});

app.use('/user', user);app.get('*', (req, res) => {  
  res.redirect('/home');  
});

app.listen((process.env.PORT || 8080), () => {  
  console.log('App running on port 8080!');  
});

We need:

require("@babel/register");  
require("babel-polyfill");

to use the latest features in JavaScript.

And we need:

require('dotenv').config();

to read our config in an .env file.

This is the entry point. We will create userController in the controllers folder shortly.

app.use(‘/user’, user); routes any URL beginning with user to the userController file.

Next, we add the userController.js file:

const express = require('express');  
const bcrypt = require('bcrypt');  
const router = express.Router();  
const models = require('../models');  
const jwt = require('jsonwebtoken');  
import { saltRounds } from '../exports';  
import { authCheck } from '../middlewares/authCheck';

router.post('/login', async (req, res) => {  
    const secret = process.env.JWT_SECRET;  
    const userName = req.body.userName;  
    const password = req.body.password;  
    if (!userName || !password) {  
        return res.send({  
            error: 'User name and password required'  
        })  
    }  
    const users = await models.User.findAll({  
        where: {  
            userName  
        }  
    }) 

    const user = users[0];  
    if (!user) {  
        res.status(401);  
        return res.send({  
            error: 'Invalid username or password'  
        });  
    } 

    try {  
        const compareRes = await bcrypt.compare(password, user.hashedPassword);  
        if (compareRes) {  
            const token = jwt.sign(  
                {  
                    data: {  
                        userName,  
                        userId: user.id  
                    }  
                },  
                secret,  
                { expiresIn: 60 * 60 }  
            );  
            return res.send({ token });  
        }  
        else {  
            res.status(401);  
            return res.send({  
                error: 'Invalid username or password'  
            });  
        }  
    }  
    catch (ex) {  
        logger.error(ex);  
        res.status(401);  
        return res.send({  
            error: 'Invalid username or password'  
        });  
    }});

router.post('/signup', async (req, res) => {  
    const userName = req.body.userName;  
    const email = req.body.email;  
    const password = req.body.password;  
    try {  
        const hashedPassword = await bcrypt.hash(password, saltRounds)  
        await models.User.create({  
            userName,  
            email,  
            hashedPassword  
        })  
        return res.send({ message: 'User created' });  
    }  
    catch (ex) {  
        logger.error(ex);  
        res.status(400);  
        return res.send({ error: ex });  
    }  
});

router.put('/updateUser', authCheck, async (req, res) => {  
    const userName = req.body.userName;  
    const email = req.body.email;  
    const token = req.headers.authorization;  
    const decoded = jwt.verify(token, process.env.JWT_SECRET);  
    const userId = decoded.data.userId;  
    try {  
        await models.User.update({  
            userName,  
            email  
        }, {  
                where: {  
                    id: userId  
                }  
            })  
        return res.send({ message: 'User created' });  
    }  
    catch (ex) {  
        logger.error(ex);  
        res.status(400);  
        return res.send({ error: ex });  
    }});

router.put('/updatePassword', authCheck, async (req, res) => {  
    const token = req.headers.authorization;  
    const password = req.body.password;  
    const decoded = jwt.verify(token, process.env.JWT_SECRET);  
    const userId = decoded.data.userId;  
    try {  
        const hashedPassword = await bcrypt.hash(password, saltRounds)  
        await models.User.update({  
            hashedPassword  
        }, {  
                where: {  
                    id: userId  
                }  
            })  
        return res.send({ message: 'User created' });  
    }  
    catch (ex) {  
        logger.error(ex);  
        res.status(400);  
        return res.send({ error: ex });  
    }});module.exports = router;

The login route searches for the User entry. If it’s found, it then checks for the hashed password with the compare function of bcrypt. If both are successful, then a JWT is generated. The signup route gets the JSON payload of username and password and saves it.

Note that there is hashing and salting on the password before saving. Passwords should not be stored as plain text.

This first is the plain text password, and the second is a number of salt rounds.

updatePassword route is an authenticated route. It checks for the token, and if it’s valid, it will continue to save the user’s password by searching for the User with the user ID from the decoded token.

We will add the authCheck middleware next. We create a middlewares folder and create authCheck.js inside it.

const jwt = require('jsonwebtoken');  
const secret = process.env.JWT_SECRET;export const authCheck = (req, res, next) => {  
    if (req.headers.authorization) {  
        const token = req.headers.authorization;  
        jwt.verify(token, secret, (err, decoded) => {  
            if (err) {  
                res.send(401);  
            }  
            else {  
                next();  
            }  
        });  
    }  
    else {  
        res.send(401);  
    }  
}

You should use the same process.env.JWT_SECRET for generating and verifying the token. Otherwise, verification will fail. The secret shouldn’t be shared anywhere and shouldn’t be checked in to version control.

This allows us to check for authentication in authenticated routes without repeating code. We place it in between the URL and our main route code in each authenticated route by importing and referencing it.

We make an .env file of the root of the back-end app folder, with the following content. (This shouldn’t be checked in to version control.)

DB_HOST='localhost'  
DB_NAME='login-app'  
DB_USERNAME='db-username'  
DB_PASSWORD='db-password'  
JWT_SECRET='secret'

The back-end app is now complete. Now can we can use a front-end app, mobile app, or any HTTP client to sign in.