rogerxu / rogerxu.github.io

Roger Xu's Blog
3 stars 2 forks source link

Node.js #49

Open rogerxu opened 7 years ago

rogerxu commented 7 years ago

maxogden/art-of-node: a short introduction to node.js

Tutorials

NodeSchool

Best Practices

goldbergyoni/nodebestpractices: The Node.js best practices list (March 2020)

19 things I learnt reading the NodeJS docs – Hacker Noon

rogerxu commented 7 years ago

Basic

How to exit from a Node.js program

process.exit

process.on('exit', function () {
});

process.exit();
process.exit(1);

You need to send the command a SIGTERM singal, and handle that with the process signal hander.

const express = require('express');

const app = express();

app.get('/', (req, res) => {
  res.send('hi');
});

const server = app.listen(30000, () => console.log('Server ready'));

process.on('SIGTERM', () => {
  server.close(() => {
    console.log('Process terminated');
  });
});

SIGTERM is the signals that tells a process to gracefully terminate. It is the signal that's sent from process managers like upstart or supervisord and many others.

You can send this signal from inside the program, in another function:

process.kill(process.pid, 'SIGTERM');

How to read environment variables

The process core module of Node provides the env property which hosts all the environment variables that were set at the moment the process was started.

process.env.NODE_ENV // 'production'

Command Line

Pass arguments from the command line

node app.js name=roger

process.argv - Array

The first argument is the full path of the node command. The second element is the full path of the file being executed. All the additional argumetns are present from the third position going forward.

const args = process.argv.slice(2);
args[0] // name=roger

The best way to parse it is by using the minimist package.

const minimist = require('minimist');
const args = minimist(process.argv.slice(2));
args['name'] // roger

Output to the command line

console.log()

console.log('My %s has %d years', 'cat', 2);

console.count()

console.count(msg);

Print the stack trace

const foo = () => console.trace();
const bar = () => foo();

bar();

Calculate the time spent

const doSomething = () => console.log('test');
const measure = () => {
  console.time('doSomething');
  doSomething();
  console.timeEnd('doSomething');
};

measure();

Standard input and output

process.stdout

process.stdout.write('hello world');

process.stdin

process.stdin.on('data', function (data) {
  console.log(data.toString().trim());
});

Color the output

You can color the output of your text in the console by using escape sequences.

console.log('\x1b[33m%s\x1b[0m', 'hi!');

chalk package

const chalk = require('chalk');
console.log(chalk.yellow('hi!'));

Create a progress bar

progress package

const ProgressBar = require('progress');

const bar = new ProgressBar(':bar', {
  total: 10,
});
const timer = setInterval(() => {
  bar.tick();

  if (bar.complete) {
    clearInterval(timer);
  }
}, 100);

Accept input from the command line

Node readline module.

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.question('What is your name?', (name) => {
  console.log(`Hi ${name}!`);
  rl.close();
});
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.question('What is your name? ', (answer) => {
  person.name = answer;
  rl.setPrompt(`What would ${person.name} say? `);
  rl.prompt(); // write a new line

  rl.on('line', (saying) => {
    person.sayings.push(saying.trim());

    if (saying.toLowerCase().trim() === 'exit') {
      rl.close();
    } else {
      rl.setPrompt(`What else would ${person.name} say? ('exit' to leave) `);
      rl.prompt();
    }
  });
});

rl.on('close', () => {
  process.exit();
});

inquirer package

cosnt inquirer = require('inquirer');

const questions = [{
  type: 'input',
  name: 'name',
  message: 'What is your name?',
}];

inquirer.prompt(questions).then(answers => {
  console.log(`Hi ${answers['name']}!`);
});

Node Core

The global object

const path = require('path');
path.basename(__filename); // abc.js

Global timing functions

function writeWaitingPercent(percent) {
  process.stdout.clearLine();
  process.stdout.cursorTo(0);
  process.stdout.write(`waiting ... ${percent}%`);
}

var interval = setInterval(function () {
  currentTime += waitInterval;
  percentWaited = Math.floor((currentTime / waitTime) * 100);
  writeWaitingPercent(percentWaited);
}, waitInterval);

setTimeout(function () {
  clearInterval(interval);
  writeWaitingPercent(100);
  console.log('\n\ndone');
}, waitTime);

exec

const exec = require('child_process').exec;

exec('git version', function(err, stdout) {
  if (err) {
    throw err;
  }

  console.log('Git Version Executed');
  console.log(stdout);
});

spawn

const spawn = require('child_process').spawn;

const cp = spawn('node', ['--version']);

cp.stdout.on('data', function (data) {
  console.log(`STDOUT: ${data.toString()}`);
});

cp.on('close', function () {
  console.log('Child process has ended');
  process.exit();
});
rogerxu commented 7 years ago

Node Modules

Core modules

path

var dirUploads = path.join(__dirname, 'www', 'files', 'uploads');

v8

v8.getHeapStatistics()

events

const EventEmitter = require('events').EventEmitter;

const emitter = new EventEmitter();
emitter.on('customEvent', function (message, status) {
  console.log(`${status}: ${message}`);
});

emitter.emit('customEvent', 'Hello World', 200);
rogerxu commented 7 years ago

The File System

File

Reading files

fs.readFile('./files.js', 'UTF-8', function (err, contents) {
  if (err) {
    throw err;
  }

  console.log(contents);
});

Writing files

fs.readFile('sample.md', `# Sample Title`, function (err) {
  if (err) {
    throw err;
  }

  console.log('File Created');
});

Removing files

fs.unlink('sample.md', function (err) {
  if (err) {
    throw err;
  }

  console.log('File removed');
});

fs-extra

const fs = require('fs-extra');

(async () => {
  const exists = await fs.pathExists(file);
  await fs.ensureFile(file);
  await fs.ensureDir(path);
})();

Directory

Listing directory files

var files = fs.readdir('./lib', function (err, files) {
  if (err) {
    throw err;
  }

  console.log(files);
});

Creating directory

fs.mkdir('temp');

Removing directory

fs.rmdir('temp');

Stream

Read stream

const stream = fs.createReadStream('temp.log', 'UTF-8');
let data = '';

stream.once('data', function () {
  console.log('Started reading file');
});

stream.on('data', function (chunk) {
  process.stdout.write(`chunk: ${chunk.length} | `);
  data += chunk;
});

stream.on('end', function () {
  console.log(`Finished reading file ${data.length}`);
});

Write stream

const stream = fs.createWriteStream('temp.log');
stream.write((new Date()).toString());
stream.close();

How to avoid nested callback in asynchronous fs functions?

rogerxu commented 7 years ago

The HTTP Module

Making a request

4 + 1 ways for making HTTP requests with Node.js: async/await edition

const options = {
  hostname: 'en.wikipedia.org',
  port: 443,
  path: '/wiki/Git',
  method: 'GET',
};

const req = https.request(options, function (res) {
  let responseBody = '';

  console.log('Response from server started');
  console.log(`Server Status: ${res.statusCode}`);
  console.log('Response Headers: %j', res.headers);

  res.setEncoding('UTF-8');

  res.on('data', function (chunk) {
    console.log(`--chunk-- ${chunk.length}`);
    responseBody += chunk;
  });

  res.on('end', function () {
    fs.writeFile('Git.html', responseBody, function (err) {
      if (err) {
        throw err;
      }

      console.log('File downloaded');
    });
  });
});

req.on('error', function (err) {
  console.log(`problem with request: ${err.message}`);
});

req.end();

node-fetch

const fetch = require('fetch');
const fs = require('fs-extra');

(async () => {
  const url = 'https://en.wikipedia.org/wiki/Git';
  const res = await fetch(url);
  const dest = fs.createWritesStream(file);
  res.body.pipe(dest);
})();

Building a web server

const server = http.createServer(function (req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/html',
  });
  res.end(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>HTML Response</title>
      </head>
      <body>
        <p>${req.url}</p>
        <p>${req.method}</p>
      </body>
    </html>
  `);
});

server.listen(8080);
console.log('Server listening on port 8080');

Serving files

http.createServer(function (req, res) {
  console.log(`${req.method} request for ${req.url}`);

  if (req.url === '/') {
    fs.readFile('./public/index.html', 'UTF-8', function (err, html) {
      res.writeHead(200, {
        'Content-Type': 'text/html',
      });
      res.end(html);
    });
  } else if (req.url.match(/.css$/)) {
    const cssPath = path.join(__dirname, 'public', req.url);
    const cssStream = fs.createReadStream(cssPath, 'UTF-8');
    res.writeHead(200, {
      'Content-Type': 'text/css',
    });
    cssStream.pipe(res);
  } else {
    res.writeHead(404, {
      'Content-Type': 'text/plain',
    });
    res.end('404 Not Found');
  }
}).listen(8080);

console.log('File server running on port 8080');

Serving JSON data

http.createServer(function (req, res) {
  console.log(`${req.method} request for ${req.url}`);

  if (req.url === '/') {
    res.writeHead(200, {
      'Content-Type': 'text/json',
    });

    res.end(JSON.stringify(data));
  } else {
    res.writeHead(404, {
      'Content-Type': 'text/plain',
    });
    res.end('File not found');
  }
}).listen(8080);

console.log('Server listening on port 8080');

Collecting POST data

http.createServer(function (req, res) {
  if (req.method === 'GET') {
    res.writeHead(200, {
      'Content-Type': 'text/html',
    });

    fs.createReadStream('./public/form.html', 'UTF-8').pipe(res);
  } else if (req.method === 'POST') {
    let body = '';

    req.on('data', function (chunk) {
      body += chunk;
    });

    req.on('end', function () {
      res.writeHead(200, {
        'Content-Type': 'text/html',
      });
      res.end(`
        <!DOCTYPE html>
        <html>
          <head>
            <title>Form Results</title>
          </head>
          <body>
            <h1>Your Form Results</h1>
            <p>${body}</p>
          </body
        </html>
      `);
    });
  }
}).listen(8080);

console.log('Form server listening on port 8080');
rogerxu commented 7 years ago

Express

Introduction to Node & Express – JavaScript Scene – Medium

API

Request

Request - API Reference

static files

const app = express();

app.use((req, res, next) => {
  console.log(`${req.method} request for '${req.url}' - ${JSON.stringify(req.body)}`);
  next();
});

app.use('/app', express.static('./public'));

app.listen(8080, () => {
  console.log('Express app running on port 8080');
});

module.exports = app;

routing

app.get('/api', (req, res) => {
  res.json(items);
});

app.post('/api', (req, res) => {
  items.push(req.body);
  res.json(items);
});

Middlewares

Express middleware

cors

app.use(cors());

body-parser

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

app.post('/api', (req, res) => {
  items.push(req.body);
  res.json(items);
});

app.delete('/api/:id', (req, res) => {
  items = items.filter(item => item.id.toLowerCase() !== req.params.id.toLowerCase());
  res.json(items);
});

http-proxy

express-http-proxy - npm

const proxy = require('express-http-proxy');

// '/proxy/lib/test.js' => '/new/lib/test.js'
app.use('/proxy', proxy(servers.remote, {
  proxyReqPathResolver: (req) => '/new' + req.url,
}));

// req.baseUrl => '/proxy'
// req.url => '/lib/test.js', inherit from Node http module
// req.originalUrl => '/proxy/lib/test.js'
rogerxu commented 7 years ago

WebSocket

Server

const WebSocketServer = require('ws').Server;

const wss = new WebSocketServer({
  port: 3000,
});

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    if (message === 'exit') {
      ws.close();
    } else {
      // broadcast
      wss.clients.forEach((client) => {
        client.send(message);
      });
    }
  })

  ws.send('Welcome to chat');
});

Client

const ws = new WebSocket('ws://localhost:3000');

ws.onopen = () => {
  setStatus('CONNECTED');
};

ws.onclose = () => {
  setStatus('DISCONNECTED');
};

ws.onmessage = (payload) => {
  printMessage(payload.data);
};

Socket.io

Server

const express = require('express');
const http = require('http');
const socketIO = require('socket.io');

const app = express();
const server = http.Server(app);
const io = socketIO(server);

app.use(express.static('./public'));

io.on('connection', (socket) => {
  console.log('connected');

  socket.on('chat', (message) => {
    // send to everyone
    io.emit('message', message);

    // send to everyone except this socket
    // socket.broadcast.emit('message', message);
  });

  socket.on('disconnect', () => {
    console.log('disconnected');
  });

  socket.emit('message', 'Welcome to chat');
});

server.listen(3000, () => {
  console.log('Starting Socket App on *:3000');
});

Client

const socket = io('htpp://localhost:3000');

socket.on('connect', () => {
  setStatus('CONNECTED');
});

socket.on('disconnect', () => {
  setStatus('DISCONNECTED');
});

socket.on('message', (message) => {
  printMessage(message);
});
rogerxu commented 7 years ago

Testing

Test a Node RESTful API with Mocha and Chai | Scotch

Mocking a server with Nock

Injecting dependencies with rewire

Advanced testing Sinon spies

Advanced testing Sinon stubs

Code coverage with Istanbul

Testing HTTP endpoints with Supertest

Checking server responses with Cheerio

rogerxu commented 7 years ago

Debugger

Node 调试工具入门教程 - 阮一峰的网络日志

V8 Inspector Integration for Node.js

$ node --inspect index.js

In node v7+, you can use --inspect-brk for --inspect & --debug-brk combo.

$ node --inspect-brk index.js

Open chrome://inspect in Chrome.

Use inspect-process to open a Chrome window automatically.

https://github.com/jaridmargolin/inspect-process

# install inspect-process globally
npm install -g inspect-process

# start the debugger with inspect
inspect index.js --debug-exception=true

devtool

Update: This tool is mostly obsolete as much of the philosophy has been brought into Node/DevTool core, see here for details.

Debugging Node.js in Chrome DevTools

$ npm install -g devtool

Watch:

$ devtool app.js --watch

Debug:

$ devtool app.js --break

node-inspector

$ npm install -g node-inspector
rogerxu commented 7 years ago

Project

Advanced Node.js Project Structure Tutorial | @RisingStack

rogerxu commented 7 years ago

Query String

const querystring = require('querystring');

escape

parse

rogerxu commented 7 years ago

Path

const path = require('path');

path name

resolve

rogerxu commented 7 years ago

Nodist

marcelklehr/nodist: [LOOKING FOR A MAINTAINER :) ] Natural node.js and npm version manager for windows.

Install

$ choco install -y nodist

Settings

Debugging

$ set DEBUG=nodist:*

Mirror Server

$ set NODIST_NODE_MIRROR=http://npm.taobao.org/mirrors/node

Proxy

$ set HTTPS_PROXY=http://127.0.0.1:8080

Node

$ nodist ls
$ nodist ds
$ nodist latest
$ nodist + 8.2
$ nodist global 14

NPM

$ nodist npm ls
$ nodist npm latest
$ nodist npm match
$ nodist npm global latest
$ nodist npm add ^6
$ nodist npm remove 6.x
rogerxu commented 7 years ago

nvm

nvm-sh/nvm

Install

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/<tag>/install.sh | bash

Verify installation

$ command -V nvm                                                                 
nvm is a shell function from /home/user/.nvm/nvm.sh
$ nvm --version
0.38.0

Auto start

Add these lines to your ~/.bashrc, ~/.profile, or ~/.zshrc file to have it automatically sourced upon login.

# nvm
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Node

List

$ nvm ls
$ nvm ls-remote
$ nvm which 10

Install

$ nvm install --lts
$ nvm install node # latest
$ nvm install 10

Uninstall

$ nvm uninstall --lts

Set default node version

$ nvm alias default node

Use

$ nvm use --lts
$ nvm use node
$ nvm use 10
rogerxu commented 6 years ago

Fork and Cluster mode

Cluster | Node.js v9.5.0 Documentation

A single instance of Node.js runs in a single thread. To take advantage of multi-core systems, the user will sometimes want to launch a cluster of Node.js processes to handle the load.

The cluster module allows easy creation of child processes that all share server ports.

rogerxu commented 6 years ago

Environment Variables

cross-env - npm

"scripts": {
  "start": "yarn dev:start",
  "dev:start": "nodemon --ignore lib --exec babel-node src/server",
  "prod:build": "rimraf lib && babel src -d lib --ignore .test.js",
  "prod:start": "cross-env NODE_ENV=production pm2 start lib/server && pm2 logs",
  "prod:stop": "pm2 delete server",
  "test": "eslint src && flow && jest --coverage",
  "precommit": "yarn test",
  "prepush": "yarn test"
}
rogerxu commented 6 years ago

nvs

jasongin/nvs: Node Version Switcher - A cross-platform tool for switching between versions and forks of Node.js

Node

$ nvs ls
$ nvs ls-remote
$ nvs add latest
$ nvs rm latest
$ nvs use latest
rogerxu commented 5 years ago

Events

Events | Node.js v11.3.0 Documentation

const {EventEmitter} = require('events');

const emitter = new EventEmitter();
emitter.on('customEvent', function (message, status) {
  console.log(`${status}: ${message}`);
});

emitter.emit('customEvent', 'Hello World', 200);
rogerxu commented 5 years ago

Linux Package Manager

nodesource/distributions: NodeSource Node.js Binary Distributions

Ubuntu

Add apt source repository

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -

Replace with mirror server

/etc/apt/sources.list.d/nodesource.list

deb https://mirrors.ustc.edu.cn/nodesource/deb/node_10.x bionic main
deb-src https://mirrors.ustc.edu.cn/nodesource/deb/node_10.x bionic main

Install package

sudo apt install -y nodejs

Debian, as root

Add apt source repository

curl -sL https://deb.nodesource.com/setup_10.x | bash -

Replace with mirror server

/etc/apt/sources.list.d/nodesource.list

deb https://mirrors.ustc.edu.cn/nodesource/deb/node_10.x bionic main
deb-src https://mirrors.ustc.edu.cn/nodesource/deb/node_10.x bionic main

Install package

apt install -y nodejs

CentOS

curl -sL https://rpm.nodesource.com/setup_10.x | bash -
rogerxu commented 2 years ago

ES Module

Node.js 如何处理 ES6 模块 - 阮一峰的网络日志 (ruanyifeng.com)

Module

module.mjs

Import ESM in CJS

await import('module.mjs');

Import CJS in ESM

import package from 'cjs-module';
import { method } from 'cjs-module';