- Node.js: Getting Started
- Javascript review
- NPM: Node Package Manager
- Semantic Versioning (SemVer)
- Creating and Publishing an NPM Package
- Next: Modules and Concurrency
- Examples of Module APIs
- Global Object
- Event Loop
- Error vs. Exceptions
- Node Clusters
- Node's Asynchronous Patterns
- Event Emitter
- Working with Web Servers
- Node Web Frameworks
- Working with the Operating System
Table of contents generated with markdown-toc
Node.js Getting Started
- Getting Started with Node
- Modern Javascript ... ...
var foo = 0;
let foo = 0;
const foo = 0;const square = (a) => {
return a * a;
};
// const square = (a) => a * a;
// const square = a => a * a;
[1, 2, 3, 4].map(a => a * a);const obj = {
p1: 10,
p2: 20,
f1() {},
f2: () => {},
};// const PI = Math.PI;
// const E = Math.E;
// const SQRT2 = Math.SQRT2;
const { PI, E, SQRT2 } = Math;
// With require
// const { readFile } = require('fs');
// const circle = {
// label: 'circleX',
// radius: 2,
// };
//
// const circleArea = ({ radius }) =>
// (PI * radius * radius).toFixed(2);
//
// console.log(
// circleArea(circle)
// );
const [first, ...restOfItems] = [10, 20, 30, 40];
const data = {
temp1: '001',
temp2: '002',
firstName: 'John',
lastName: 'Doe',
};
const { temp1, temp2, ...person } = data;
const newArray = [...restOfItems];
const newObject = {
...person,
};const greeting = "Hello World";
const answer = 'Forty Two';
const html = `
<div>
${Math.random()}
</div>
`;class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello ${this.name}!`);
}
}
class Student extends Person {
constructor(name, level) {
super(name);
this.level = level;
}
greet() {
console.log(`Hello ${this.name} from ${this.level}`);
}
}
const o1 = new Person("Max");
const o2 = new Student("Tina", "1st Grade");
const o3 = new Student("Mary", "2nd Grade");
o3.greet = () => console.log('I am special!');
o1.greet();
o2.greet();
o3.greet();const https = require('https');
function fetch (url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = '';
res.on('data', (rd) => data = data + rd);
res.on('end', () => resolve(data));
res.on('error', reject);
});
});
}
fetch('https://www.javascript.com/')
.then(data => {
console.log(data.length);
});
(async function read() {
const data = await fetch('https://www.javascript.com/');
console.log(data.length);
})();Init. Creates package.json etc.
$ npm init
$ npm i -D nodemon
-D means it is a "Dev Dependency" and not needed in production.
Testing frameworks, formatting tools, etc. belong in "dev dependencies".
$ npm install --production
When only install "non dev".
4.2.0
4: Major - Breaking Changes
2: Minor - Backward Compantible
0: Patch - Bug Fixes. No new features. No breaking changes.
~1.2.2
~ Update can install most recent patch version. So is greater than or equal to:
1.2.4
1.2.5
1.2.6
1.2.x
But not:
1.3.0
^1.2.3
^ More relaxed constraint. Get the most recent minor version (middle number).
1.3.0
1.4.5
But not all the way to:
2.0.0
Module exports
//File path: `wenbert-frame-print/index.js`
module.exports = function print(msg) {
console.log('**********');
console.log('msg');
console.log('**********');
};//File path: `index.js`
const print = require('../fromt-print');
print('Hello NPM!');
/*
Expected Output:
**********
Hello NPM!
**********
*/By default, Node looks up node_modules but if it can't find it, it will look-up the relative path.
The goal is to do:
$ npm install wenbert-frame-print
We put a prefix wenbert so that it will be unique in npmjs.com. Create an account in npmjs.com
$ npm login
Username: wenbert
Password:
Email: (this IS public) wenbert@gmail.com
Logged in as wenbert on https://registry.npmjs.org/.
Then create a package.json file.
$ npm init
Enter wenbert-frame-print or some unique string. Follow instructions / keep everything as default.
Then finally:
$ npm publish
Now that you have package published, you can:
$ npm install wenbert-frame-print
It will download the package and place it inside the node_modules directory.
$ npm ls
And
$ npm outdated
This will not update the package.
Current is what you have.
Wanted is what you'll get when you npm update
Latest is the latest version.
Be careful, because even when it is only a patch level update, it might still introduce bugs in your code.
function dynamicArgsFunction() {
console.log(arguments);
}
dynamicArgsFunction(3, 7, 5, 4);
// OUTPUT
// $ node arguments.js
// [Arguments] {'0': 3 ... '3':4}Take this file with 1 line. And we run the file in Node. The output would not be "undefined".
// arguments.js
console.log(arguments);Important: Node internally wraps every file in a function, you would get some output here. As opposed to running this in a browser - you would get some kind of "undefined" message.
Node does something like this:
function (exports, module, require, __filename, __dirname) {
// Your code in arguments.js
console.log(arguments);
// So if you use exports.a = 42
// You are just using the param inside the wrapping function.
// That is why if you do:
let foo = 2;
// That does not become a global variable. It is only in the
// scope of the wrapping function.
// If that was done in a browser, that `foo` will be a global one.
exports.a = 42;
module.exports.b = 37;
exports = () => {} // NOT OK
module.exports = () => {}; // OK
// Returns for every file in the "wrapper". Node will always return
// this function.
return module.exports;
}So if we do:
const moduleApi = require('./arguments.js');
// The result of the "require" call is really the "module.exports"
// of "arguments.js" - which is what Node will always return in the background.
console.log(moduleApi);// object.js
// Top-level API is a simple object (no need to use module.exports)
exports.language = 'English';
exports.direction = 'RTL';
exports.encoding = 'UTF-8';Then we use it like:
// use-objects.js
const api = require('./object.');
console.log(api.language, api.direction, api.encoding);We run it, output is:
$ node use-object.js
English RTL UTF-8
// array.js
// Top-level API is an array
module.exports = [2, 3, 5, 7];Then we use it:
// use-array.js
console.log(require('./array.js'));Run it:
$ node use-array.js
[2, 3, 5, 7]
We can also return a string.
// string.js
module.exports = `
<html>
<head>A title</head>
<body>Foo bar!</code>
</html>
`;We use it like so:
// use-string.js
const myTemplate = require('./string.js');
console.log(myTemplate);Output:
$ node use-string.js
<html>
<head>A title</head>
<body>Foo bar!</code>
</html>
Let say you want an HTML template but you want to pass in variables. You do it like this - by exporting a function.
// string2.js
module.exports = title = `
<html>
<head>${title}</head>
<body>Foo bar!</code>
</html>
`;We use it like so:
// use-string2.js
const templateGenerator = require('./string.js');
const myTemplate = templateGenerator('Hello World!');
console.log(myTemplate);Output:
$ node use-string2.js
<html>
<head>Hello World!</head>
<body>Foo bar!</code>
</html>
// dir.js
// console.dir() is like console.log() but only shows top-level items
// We use console.dir() because `global` is a big object
console.dir(global, {depth: 0});Output is:
$ node dir.js
Object [global] {
DTRACE_NET_SERVER_CONNECTION: [Function],
DTRACE_NET_STREAM_END: [Function],
DTRACE_HTTP_SERVER_REQUEST: [Function],
DTRACE_HTTP_SERVER_RESPONSE: [Function],
DTRACE_HTTP_CLIENT_REQUEST: [Function],
DTRACE_HTTP_CLIENT_RESPONSE: [Function],
global: [Circular],
process: [process],
Buffer: [Function],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function] }
You can add/set something like:
global.answer = 42;LOL These are bad. Avoid global objects.
...
const path = require('path');
const fs = require('fs');
const files = ['.bash_profile', 'kjkjhh', '.npmrc'];
files.forEach(file => {
try {
const filePath = path.resolve(process.env.HOME, file);
const data = fs.readFileSync(filePath);
console.log('File data is', data);
} catch (err) {
if (err.code === 'ENOENT') {
console.log('File not found ');
} else {
throw err;
}
}
});- Master process can restart other workers when then have problems (?)
- PM2 Tool for Production
const fs = require('fs');
const data = fs.readFileSync(__filename);
console.log('File data is', data);
console.log('TEST');And
const fs = require('fs');
fs.readFile(__filename, function cb(err, data) {
console.log('File data is', data);
});
console.log('TEST');Output:
TEST
File data is: ....
That is first pass is fs.readFile, then console.log(). Then when the callback function is done, it calls the internal console.log() inside.
const fs = require('fs');
fs.readFile(__filename, function cb1(err, data) {
fs.writeFile(__filename + '.copy', data, function cb2(err) {
// Nest more callbacks here...
});
});
console.log('TEST');const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
async function main() {
const data = await readFile(__filename);
console.log('File data is', data);
}
main();
console.log('TEST');Using built-in libraries for some libraries:
const { readFile } = require('fs').promises;
async function main() {
const data = await readFile(__filename);
console.log('File data is', data);
}
main();
console.log('TEST');Promises are better than callbacks. Easier, etc.
const fs = require('fs').promises;
async function main() {
const data = await fs.readFile(__filename);
await fs.writeFile(__filename + '.copy', data);
// More awaits here...
}
main();
console.log('TEST');// example.js
const EventEmitter = require('events');
// Streams are Event Emitters
// process.stdin, process.stdout
const myEmitter = new EventEmitter();
// This will not output anything. We "emit" before we Subscribe
myEmitter.emit('TEST_EVENT');
// If we want to do "emit" before the Subscription we can use
// setImmediate()
setImmediate(() => {
myEmitter.emit('TEST_EVENT');
});
// Subscribe
myEmitter.on('TEST_EVENT', () => {
console.log('TEST_EVENT was fired');
});
// This will "emit" the event so the callback in TEST_EVENT (Subscribe)
// will get called.
myEmitter.emit('TEST_EVENT');Output:
$ node example.js
TEST_EVENT was fired
TEST_EVENT was fired
This is because A does not do anything. We emit before Subscribing.
B outputs the first TEST_EVENT was fired - due to setImmediate()
C is we Subscribe to TEST_EVENT - this gets called when we emit something - in this case TEST_EVENT.
D outpus the second TEST_EVENT was fired - we emit after we Subscribed to TEST_EVENT.
Very simple yet very powerful. Because it allows modules to work together without depending on any APIs!
The built-in HTTP module
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello World\n');
});
server.listen(4242, () => {
console.log('Server is running...');
});Let's make that previous code snippet a bit more readable.
const http = require('http');
const requestListener = (req, res) => {
res.write('Hello World\n');
res.end();
};
// See previous topic about "Event Listeners" - this is exactly it!
const server = http.createServer();
server.on('request', requestListener);
server.listen(4242, () => {
console.log('Server is running...');
});Because the event loop is constantly running, when you change something in the file above, the changes will not reflect unless you reload the script. ie: Stop then start.
To go around this, we use a library called Nodemon.
$ npm -i nodemon
$ nodemon hello-world.js
Now, it will monitor the files for changes and then nodemon will reload the file/script.
console.log() or console.dir() these lol!
Keywords: IncomingMessage and ServerReponse
req and res are streams!
req object is readable stream while the res object is a writable one.
That means we can SUBSCRIBE to them!
First steps:
$ npm init
$ npm install express
// index.js
const express = require('express');
const server = express();
// We don't define a single request listener.
// We define MANY listeners - per URL
server.get('/', (req, res) => {
res.send('Hello Express');
});
server.get('/bar', (req, res) => {
res.send('Hello Express');
});
server.listen(4242, () => {
console.log('Express Server is running...');
});$ nodemon index.js
...
Express Server is running...
Then go to browser port 4242. And also /bar.
Pug, Handlebars, EJS.
$ npm install express
$ npm install ejs
// index.js
const express = require('express');
const server = express();
// THIS!
server.set('view engine', 'ejs');
server.get('/', (req, res) => {
res.render('index');
});
server.get('/about', (req, res) => {
res.render('about');
});
server.listen(4242, () => {
console.log('Express Server is running...');
});Then inside views directory.
// views/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<title>INDEX</title>
</head>
<body>
<h2>Hello EJS</h2>
</body>
</html>And
// views/about.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>INDEX</title>
</head>
<body>
<h2>About...</h2>
</body>
</html>The os module
const os = require('os');
console.log('OS platform:', os.platform());
console.log('OS CPU architecture:', os.arch());
console.log('# of logical CPU cores', os.cpus().length);
console.log('Home directory for current user', os.homedir());
console.log('line 1' + os.EOL + 'line 2' + os.EOL + 'line 3');The fs module
/*
readFile(path[, options])
createReadStream(path[, options])
writeFile(file, data[, options])
createWriteStream(path[, options])
appendFile(path, data[, options])
copyFile(src, dest[, flags])
stat(path[, options])
access(path[, mode]), chmod(path, mode), chown(path, uid, gid)
link(existingPath, newPath), unlink(path)
truncate(path[, len])
mkdir(path[, mode])
readdir(path[, options])
rmdir(path)
rename(oldPath, newPath)
*/The cp module
const { spawn } = require('child_process');
// Print Working Directory
const pwd = spawn('pwd');
pwd.stdout.pipe(process.stdout);
// Read content of a file
const { HOME } = process.env;
const cat = spawn('cat', [`${HOME}/.bash_profile`]);
cat.stdout.pipe(process.stdout);
// List files
const ls = spawn('ls', ['-l', '.']);
ls.stdout.pipe(process.stdout);
// Use Shell Syntax
const shell = spawn('ls -al ~ | wc -l', { shell: true });
shell.stdout.pipe(process.stdout);$ node --inspect-brk file.js
Then go to: chrome://inspect in your Chrome browser.
Then you will see the node process inside "Targets". Then click on the "Inspect" link.
You should see something like:
function convertArrayToObject(arr) {
return arr.reduce((curr, acc) => {
acc[curr[0]] = curr[1];
return acc;
}, {});
}
const obj = convertArrayToObject([
[1, 'One'],
[2, 'Two'],
[3, 'Three'],
[4, 'Four'],
[5, 'Five'],
]);
console.log(obj);
/* Should output:
{
1: 'One',
2: 'Two',
3: 'Three'
4: 'Four',
5: 'Five'
}
*/Put a breakpoint then click on Play button. You can hover on variables, etc.