Node.js

Node.js, overview

Node.js, configuration

npm is in synch with a package.json file for configuration purposes including file location, nature, purpose… library versioning, licensing, etc. Library versioning:

Other configurations:

Node.js and TypeScript

TypeScript imposes the installation of Node.js types as dev. dependency:

npm i @types/node -S

Library import can then be ruled from Node.js common style, i.e., require or TypeScript pure style (recommended):

const HTTP = require("http"); // 'require' => 'npm i @types/node -S'
import * as HTTP from "http"; // Pure TypeScript style...

Node.js, sandbox

const file_system = require('fs');
const http = require("http");
const port = process.env['PORT'] || 8080; // Env. variable

const server = http.createServer((request, response) => {
    response.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
    file_system.readFile('./index.html', null, function (error, data) {
        if (error) {
            response.writeHead(404);
            respone.write("Whoops! './index.html' not found...");
        } else {
            response.write(data);
        }
        response.end();
    });
}).listen(port);
console.log("Server ready to accept requests on port %d", port);

Express, sandbox

const express = require('express');
const path = require('path');
const port = process.env['PORT'] || 8080;

const my_application = express();
my_application.get('/', (request, response) => {
    response.setHeader("Content-Type", "text/html; charset=utf-8");
    response.statusCode = 200;
    response.sendFile(path.join(__dirname + '/index.html'));
});
const server = my_application.listen(port);
console.log("Server ready to accept requests on port %d", port);

Express, “router”

const express = require('express');
const path = require('path');
const port = process.env['PORT'] || 8080;
const router = express.Router();

const my_application = express();
my_application.use('/', router);
router.get('/', (request, response) => {
    response.setHeader("Content-Type", "text/html; charset=utf-8");
    response.statusCode = 200;
    response.sendFile(path.join(__dirname + '/index.html'));
});
const server = my_application.listen(port);
console.log("Server ready to accept requests on port %d", port);

Express, serving static files

Serving static files in Node.js relies on the non-static library.

const http = require('http');
const ns = require('node-static');
…
const file_server = new ns.Server('./static');
http.createServer(function (request, response) {
    file_server.serve(request, response);
}).listen(port);
// https://expressjs.com/en/starter/static-files.html
// console.log(__dirname + path.sep + 'static');
barbierdarnal.use(express.static('static')); // This serves resources from 'static' folder...
barbierdarnal.use('/', router);
router.get('/', (request, response) => {
    response.sendFile(path.join(__dirname + '/BarbierDarnal.html')); // '__dirname' resolves to your project folder
});
router.get('/Genie_logiciel', (request, response) => {
    response.sendFile(path.join(__dirname + '/static/html/Genie_logiciel.html'));
});
const server = barbierdarnal.listen(port);

Querying the Web, request (third-party) library (simple HTTP client)

const request = require("request"); // 'npm install --save request' or 'sudo npm install --save request'

const options = {
    headers: {accept: 'application/json', 'content-type': 'application/json'},
    method: 'GET',
    url: 'http://www.example.com'
};

request(options, function (error, response, body) {
    if (error)
        return console.error('Failed: %s', error.message);
    console.log('Success: ', body);
});

Querying the Web, https (native) library

const https = require('https');
https.get('https://www.carrefour.fr/s?q=3083681093926', (response) => { // Aubergines Cassegrain
    let data = '';
    response.on('data', chunk => { // A chunk of data has been received...
        data += chunk;
    });
    response.on('end', () => { // The whole response has been received...
        let object;
        try {
            object = JSON.parse(data);
            console.log(Object.getOwnPropertyNames(object).join(" - "));
        } catch (exception) {
            console.log(`${exception.name}: ${exception.message}`);
        }
    });
}).on('error', (error) => {
    console.log("Error: " + error.message);
});

export/import

// 'Save_image_from_URL.js' file
const file_system = require('fs'); // https://nodejs.org/api/fs.html
const https = require('https'); // https://nodejs.org/api/https.html

module.exports.save_image_from_URL = function () {
    https.get('https://i5.walmartimages.ca/images/Large/912/760/6000197912760.jpg', response => {
        response.pipe(file_system.createWriteStream('./Nutella.jpg'));
    });
};
// 'Main.js' file
const Save_image_from_URL = require('./Save_image_from_URL.js');

console.log(typeof Save_image_from_URL.save_image_from_URL); // 'function' is displayed

Save_image_from_URL.save_image_from_URL(); // Simple usage...

App.

Web services, micro-service architecture design (see also: here… and there…)

Express is a natural support for Web service design in the logic of “micro-service architecture”.

import * as Express from 'express';
…
const GPAO = Express();
GPAO.use(Express.json()); // For POST (and PUT) requests (based on 'body-parser' library in Node.js)...
const port = process.env['PORT'] || 1963; // 'process' => "@types/node"
const server = GPAO.listen(port);
console.log("Server ready to accept requests on port %d", port);
GPAO.get('/', (request, response) => { // Test: 'curl http://localhost:1963/'
    response.send('<h1 style="color: green;">"GPAO.Node.js.ts": Restful Web services, test</h1>');
});
GPAO.get('/api/GPAO/Articles', (request, response) => { // Test: 'curl http://localhost:1963/api/GPAO/Articles'
    response.send(Articles);
});
GPAO.get('/api/GPAO/Articles/:reference', (request, response) => { // Test: 'curl http://localhost:1963/api/GPAO/Articles/CD100'
    const article = Articles.find((a: Article) => a.reference === request.params.reference);
    if (!article) response.status(404).send('<h1 style="color: red;">Not found: ' + request.params.reference + '</h1>');
    response.send(article);
});

Case study (in French): here
App.
Reminder on HTTP status codes: here

Web services, micro-service architecture design, Post

Express is the natural support for Web service design in the logic of “micro-service architecture”. Case study (in French): here… Reminder, HTTP status codes: here

GPAO.post("/api/GPAO/Nouvel_article", (request, response) => {
    const article: Article = request.body;
    const {error, value} = Articles_.validate({
        reference: article.reference, // Unicity required as well...
        designation: article.designation, // Unicity required as well...
        type_fabrication_achat: article.type_fabrication_achat,
        unite_achat_stock: article.unite_achat_stock,
        delai_en_semaine: article.delai_en_semaine,
        lot_de_reapprovisionnement: article.lot_de_reapprovisionnement,
        stock_maxi: article.stock_maxi,
        PF_ou_MP_ou_Piece_ou_SE: article.PF_ou_MP_ou_Piece_ou_SE
    });
    if (error === undefined) {
        response.status(201).json({
            message: "Article validé (succès) : " + value.reference
        });
        Articles.push(article);
    } else {
        response.status(400).json({
            message: "Article invalidé (échec) : " + error.message
        });
    }
});

Server-side data validation, the case of Joi within Hapi

Hapi is an Express competitor. Data validation relies the Joi module.

import * as Joi from '@hapi/joi';
export enum Article_type { MP = 'MP', PF = 'PF', Piece = 'Piece', SE = 'SE' }
export interface Article { reference: string; designation: string; type_fabrication_achat: string; unite_achat_stock: string; delai_en_semaine: number;
    prix_standard?: number; lot_de_reapprovisionnement?: number; stock_mini?: number; stock_maxi?: number; pourcentage_de_perte?: number; inventaire?: number;
    PF_ou_MP_ou_Piece_ou_SE: Article_type
}
export const Articles_ = Joi.object({
    reference: Joi.string().alphanum().required().min(4).max(10),
    designation: Joi.string().pattern(new RegExp('^[a-zA-Z0-9 ]*$')).required().max(30),
    type_fabrication_achat: Joi.string().pattern(new RegExp('^[a-zA-Z0-9. ]*$')).required().max(30),
    unite_achat_stock: Joi.string().pattern(new RegExp('^[a-zA-Z0-9 ]*$')).required().max(30),
    delai_en_semaine: Joi.number().integer().required(),
    prix_standard: Joi.number(),
    lot_de_reapprovisionnement: Joi.number().integer(),
    stock_mini: Joi.number().integer(),
    stock_maxi: Joi.number().integer(),
    pourcentage_de_perte: Joi.number(),
    inventaire: Joi.number().integer(),
    PF_ou_MP_ou_Piece_ou_SE: Joi.string().valid(Article_type.MP, Article_type.PF, Article_type.Piece, Article_type.SE)
});

Server-side data validation, the case of Joi within Hapi cont'd

Hapi is an Express competitor. Data validation relies the Joi module.

const Articles = new Array<Article>();
const {error, value} = Articles_.validate({
    reference: 'CD100', designation: 'camion demenagement bleu', type_fabrication_achat: 'pf fabr. par lot', unite_achat_stock: 'unite', delai_en_semaine: 2,
    lot_de_reapprovisionnement: 200, stock_maxi: 600, PF_ou_MP_ou_Piece_ou_SE: Article_type.PF
});
if (error === undefined) {
    console.log("Article validé (succès) : " + value.reference);
    Articles.push({
        reference: 'CD100', designation: 'Camion demenagement bleu', type_fabrication_achat: 'Fab. par lot', unite_achat_stock: 'unite', delai_en_semaine: 2,
        lot_de_reapprovisionnement: 200, stock_maxi: 600, PF_ou_MP_ou_Piece_ou_SE: Article_type.PF
    });
} else console.log("Article invalidé (échec) : " + error.message);

assert (core) library

console.assert(Dollar.get('common_symbol') === "$"); // Common style...
const {message} = new assert.AssertionError({
    actual: Dollar.get('common_symbol'),
    expected: "$",
    operator: "strictEqual"
});
try {
    assert.strictEqual(Dollar.get('common_symbol'), "$");
} catch (error) {
    assert(error instanceof assert.AssertionError);
    assert.strictEqual(error.message, message);
    assert.strictEqual(error.name, 'AssertionError');
    assert.strictEqual(error.actual, Dollar.get('common_symbol'));
    assert.strictEqual(error.expected, "$");
    assert.strictEqual(error.code, 'ERR_ASSERTION');
    assert.strictEqual(error.operator, 'strictEqual');
    assert.strictEqual(error.generatedMessage, true);
}

Database issues, the case of MongoDB


App.

MongoDB, sample

import * as MongoDB from "mongodb"; // TypeScript style instead of 'const MongoDB = require("mongodb");'
// MongoDB Atlas:
const MongoDB_URL = "mongodb+srv://FB:Sauvagnon64@web-scraping-98xi5.mongodb.net/Web_scraping?retryWrites=true&w=majority";

const client = new MongoDB.MongoClient(MongoDB_URL, {useNewUrlParser: true, useUnifiedTopology: true});
client.connect((error, client) => {
    // @ts-ignore (direct '.s' access isn't advised!)
    console.log("'client.s.url': " + client.s.url);
    const Web_scraping_database = client.db("Web_scraping");
    Web_scraping_database.admin().listDatabases((error_, dbs) => {
        console.log("'dbs.databases': " + JSON.stringify(dbs.databases));
    });
    const my_data = Web_scraping_database.collection("data");
    my_data.insertOne({forname: "Franck", surname: "Barbier"}).then(() => {
        my_data.countDocuments({}).then(value => {
            console.log("'my_data.countDocuments({})': " + value); // '1'
            my_data.drop().then(() => {
                client.close().then(() => {
                    console.log("See you later!");
                });
            });
        });
    });
});

Mongoose, schema and model (see also: here…)

import * as Mongoose from "mongoose";

const Currency_schema: Mongoose.Schema = new Mongoose.Schema({ // USA dollar:
    common_name: String, // Dollar
    common_symbol: {type: String}, // $
    description: {type: String, default: ""},
    iso_code: {type: Number}, // 840
    iso_symbol: String, // USD
    material_icon: {type: String, default: ""},
    substitute: {type: Mongoose.Schema.Types.Mixed},
    substitution_date: {type: Date, default: undefined}
});
const Currency = Mongoose.model('Currency', Currency_schema);
const Dollar = new Currency({
    common_name: "Dollar",
    common_symbol: "$",
    description: "First leading currency in the world...",
    iso_code: 840,
    iso_symbol: "USD",
    material_icon: "attach_money",
    substitute: null
});
console.assert(Dollar.get('common_symbol') === "$");

Mongoose, sample (see also: here…)

import * as Mongoose from "mongoose";
…
// MongoDB Atlas:
const MongoDB_URL_ = "mongodb+srv://FB:Sauvagnon64@web-scraping-98xi5.mongodb.net/Web_scraping?retryWrites=true&w=majority";
Mongoose.connect(MongoDB_URL_, {useNewUrlParser: true, useUnifiedTopology: true}).then((value: Mongoose.Mongoose) => {
    console.log("Connected database: '" + (value.connections.length > 0 ? value.connections[0].name : "?")
        + "' on '" + (value.connections.length > 0 ? value.connections[0].host : "?") + "'");
});
const connection: Mongoose.Connection = Mongoose.connection;
connection.on('error', console.error.bind(console, 'connection error:'));
connection.once('open', () => {
    // Empty data (for test):
    Mongoose.model('Currency').deleteMany({common_symbol: "$"}, function (error) {
        if (error) return console.error(error.toString());
    });
    Dollar.save().then(() => {
        // Then, check if present:
        Mongoose.model('Currency').find({common_symbol: "$"}).exec().then(currency => {
                console.log(currency);
                connection.close().then(() => {
                    console.log("See you later!");
                });
            }
        );
    });
});

© Franck Barbier