NodeJS #2

Basic concepts

$ whoami

Inna Ivashchuk

Senior Software Engineer

JS developer, music fan, movie-dependent and Star Wars fan 🤓

May the Force be with you!

4+ years with GlobalLogic

6+ years in web-development

        GitHub page

Agenda

  • Global objects

  • NodeJS modules
  • Core modules
  • REPL. File execution

Global objects

NodeJS Globals

Global object in a browser

What will happen when you type the following code in a browser console?

// console.log(this) or just
 this

And the correct answer is - we will get the full global object Window

Global object in a browser

And some examples:

// standard way to use console.log
console.log('hello');

// equal to
window.console.log('hello');

// custorm variable created like that 
window.myVar = 'JavaScript';

// we can access like that
console.log(myVar);

Global object in NodeJS

There is no global object window, but we have - global namespace Object

// standard way to use console.log
console.log('hello');

// equal to
global.console.log('hello');

// custorm variable created like that 
global.myVar = 'JavaScript';

// we will get 'undefined'
console.log(myVar);

// this is pointing to the global
console.log(this);


// common functions
global.require('http'); // equal to - require('http');

global.process; // equal to - process; 

global.setImmediate(cb); // equal to - setImmediate(cb);

Globals in NodeJS

Commonly used:

  • __dirname
  • __filename
  • module
  • exports
  • Buffer (class)
  • URL (class)
  • URLSearchParams (class)
  • and etc

NodeJS modules

Modules: CommonJS modules

In the Node.js module system, each file is treated as a separate module

const circle = require('./circle.js');

console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

Here are the contents of circle.js:

const { PI } = Math;

exports.area = (r) => PI * r ** 2;

exports.circumference = (r) => 2 * PI * r;

Modules

Module 1

Module 2

Module 3

Variables local to the module will be private because the module is wrapped in a function by Node.js (IIFE).

Modules

app.js

Every file is a module.
Every variable and function defined in the file are in the scope of this module and not available outside.

Modules: create a new one

logger.js

const loggerUrl = 'http://mylogger.io/log';

function logger(message) {
  // Send an HTTP request
  console.log(message);
}

module.exports.log = logger;

// we can export URL as well
// mostly should not be done,
// as it's implementation details
module.exports.LOGGER_URL = loggerUrl;

Modules: loading a module

app.js

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

function app() {
  	// some logic can be there
  	
    logger.log('log my message');
}

app();

Modules: another way to do the same

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

function app() {
  	// some logic can be there
  	
    logger('log my message');
}

app();
const loggerUrl = 'http://mylogger.io/log';

function logger(message) {
  // Send an HTTP request
  console.log(message);
}

module.exports = logger;

Modules: CommonJS modules

The module.exports property can be assigned a new value (such as a function or object).

const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`The area of mySquare is ${mySquare.area()}`);

The square module is defined in square.js:

module.exports = class Square {
  constructor(width) {
    this.width = width;
  }

  area() {
    return this.width ** 2;
  }
};

Modules: require(X)

Modules: Caching

  1. Modules are cached after the first time they are loaded
  2. Modules are cached based on their resolved filename
  3. On case-insensitive file systems or operating systems, different resolved filenames can point to the same file, but the cache will still treat them as different modules and will reload the file multiple times

a.js

b.js

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

main.js

Modules: Cycles

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);

     When main.js loads a.js, then a.js, in turn, loads b.js. At that point, b.js tries to load a.js. In order to prevent an infinite loop, an unfinished copy of the a.js exports object is returned to the b.js module. b.js then finishes loading, and its exports object is provided to the a.js module.

$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true

Modules: Cycles

addTwo.mjs

app.mjs

Modules: ECMAScript modules

// addTwo.mjs
function addTwo(num) {
  return num + 2;
}

export { addTwo };
// app.mjs
import { addTwo } from './addTwo.mjs';

// Prints: 6
console.log(addTwo(4));

Available from NodeJS v.12.12.0

Core modules

Common information

  • Core modules are described in NodeJS documentation
  • Core modules are always preferentially loaded if their identifier is passed to require()
  • Core modules can also be identified using the node: prefix(Added v16.0.0)

Assert

The assert module provides a set of assertion functions for verifying invariants.

const assert = require('assert/strict');

// This fails because 1 !== '1'.
assert.deepStrictEqual({ a: 1 }, { a: '1' });
// AssertionError: Expected inputs to be strictly deep-equal:
// + actual - expected
//
//   {
// +   a: 1
// -   a: '1'
//   }

assert.ok(typeof 123 === 'string');
// AssertionError: The expression evaluated to a falsy value:
//
//   assert.ok(typeof 123 === 'string')

Util

The util module supports the needs of Node.js internal APIs. Many of the utilities are useful for application and module developers as well.

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);

stat('.').then((stats) => {
  // Do something with `stats`
}).catch((error) => {
  // Handle the error.
});

util.format('%s:%s', 'foo', 'bar', 'baz');
// Returns: 'foo:bar baz'

util.types.isDate(new Date());  // Returns true

Path

The path module provides utilities for working with file and directory paths.

const path = require('path');

path.parse('/home/user/dir/file.txt');
// Returns:
// { root: '/',
//   dir: '/home/user/dir',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file' }

path.format({
  root: '/ignored',
  dir: '/home/user/dir',
  base: 'file.txt'
});
// Returns: '/home/user/dir/file.txt'

path.resolve('/foo/bar', './baz');
// Returns: '/foo/bar/baz'

Process

The process object is a global that provides information about, and control over, the current Node.js process.

const process = require('process');

// print process.argv
process.argv.forEach((val, index) => {
  console.log(`${index}: ${val}`);
});

process.on('uncaughtException', (err, origin) => {
  fs.writeSync(
    process.stderr.fd,
    `Caught exception: ${err}\n` +
    `Exception origin: ${origin}`
  );
});

process.on('exit', (code) => {
  console.log('Process exit event with code: ', code);
});

process.exit(1);

File system

The fs module enables interacting with the file system.

const { unlink } = require('fs/promises');

(async function(path) {
  try {
    await unlink(path);
    console.log(`successfully deleted ${path}`);
  } catch (error) {
    console.error('there was an error:', error.message);
  }
})('/tmp/hello');
const { unlinkSync } = require('fs');

try {
  unlinkSync('/tmp/hello');
  console.log('successfully deleted /tmp/hello');
} catch (err) {
  // handle the error
}

Synchronous example:

HTTP

HTTP server

const http = require('http');

// Create an HTTP server
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('okay');
});

server.listen(8000);
const http = require('http');

http.get('http://localhost:3000', { agent }, (res) => {
  res.on('data', (data) => {
  // Do nothing
  });
});

HTTP request

REPL. File execution

What is REPL?

Read-Eval-Print Loop

Why do we need REPL?

Read-Eval-Print Loop is the Node.js interactive shell; any valid JavaScript written in a script can be passed to the REPL.

It can be extremely useful for experimenting with Node.js, debugging code, and figuring out some of JavaScript's more eccentric behaviors.

How to Start the REPL

Starting the REPL is simple - just run node on the command line without a filename.

$ node
> var x = "Hello, World!"
undefined
> x
"Hello, World!"
> .exit

$ node

And now is time to play:

Special Commands and Exiting the REPL

The following special commands are supported by all REPL instances (from Node.js REPL docs:

  • .exit - Close the I/O stream, causing the REPL to exit.
  • .break - When in the process of inputting a multi-line expression, entering the .break command (or pressing the <ctrl>-C key combination) will abort further input or processing of that expression.
  • .clear - Resets the REPL context to an empty object and clears any multi-line expression currently being input.
  • .help - Show this list of special commands.
  • .save - Save the current REPL session to a file: > .save ./file/to/save.js
  • .load - Load a file into the current REPL session. > .load ./file/to/load.js

Special Commands and Exiting the REPL

  • .editor - Enter editor mode (<ctrl>-D to finish, <ctrl>-C to cancel).
> .editor
# Entering editor mode (<ctrl>-D to finish, <ctrl>-C to cancel)
function welcome(name) {
  return `Hello ${name}!`;
}

welcome('Node.js User');

# <ctrl>-D
'Hello Node.js User!'
>

Accessing Modules

  If you need to access any of the built-in modules, or any third-party modules, they can be accessed with require, just like in the rest of Node.

For example:

$ node
> path = require('path')
{ resolve: [Function],
  normalize: [Function],
  join: [Function],
  dirname: [Function],
  basename: [Function],
  extname: [Function],
  exists: [Function],
  existsSync: [Function] }
> path.basename("/a/b/c.txt")
'c.txt'

Q & A

Homework

  1. Create a new directory tutorial2

  2. Create next modules: main.js, file.js, logger.js (or you can turn on your imagination and create something different)

  3. Create 2 text files and read them in file.js using fs

  4. Create a logger module, that should print error or info messages to the terminal

  5. In a main.js create a simple HTTP server, that can read URLs 

  6. Use logger and file modules in main.js

  7. Use functions from global in modules 

NodeJS Core #2

By Inna Ivashchuk

NodeJS Core #2

  • 401