Node JS
Node JS
Node JS
JS
SERVER SIDE JAVASCRIPT PLATFORM
AGENDA
Overview : async programming, event loop
Core : REPL,HTTP, Events, Streams, File System
Modules, npm, semver
REST Web app with Express
Socket.io
Data access : MySQL, MongoDB, ORM
Tools : debuging, testing, monitoring, frontend tools,
deploying
Before starting (1/2)
REQUIREMENT
1. Node JS, npm
2. MySQL + MySQL Workbench
3. Mongodb + Mongohub (Mac) or
http://robomongo.org/
4. POSTMAN REST client for chrome
5. chrome, as mentioned above
EXERCICES OVERVIEW
1. Play with REPL
2. Starters : HTTP server, sync/async, shell
3. A simple Webapp
4. A simple module
5. A REST web app
6. A chat with WebSocket
7. MySQL + REST + Express
8. Mongo : bulk loading, aggregation
9. Tests unit in Node.js
READY ?
1. Ensure that everything is installed :
$ node --version
v0.12.0
$ npm --version
2.5.1
$ node hello.js
OVERVIEW
JS REMINDERS
5 SHADES OF 'THIS'
The value of this depends on how a function is
invoked.
INVOKE AS A FUNCTION
function foo() {};
foo(); //=> this === window
var bar = function() {};
bar(); //=> this === window
INVOKE AS A METHOD
function foo() {
return this;
}
// invoke as a function
foo(); //=> this === window
var bar = {
'foo': foo
};
// invoke as a method of 'bar'
bar.foo(); //=> this === bar
INVOKE AS A CONSTRUCTOR
function Foo() {
this.bar = function() {
return this;
}
}
// exemple 1
var foo = new Foo();
console.log(foo); //=> Foo
console.log(foo.bar() instanceof Foo); //=> true
// exemple 2
var foo1 = Foo();
console.log(typeof foo1); //=> undefined
console.log(typeof window.bar); //=> function
INVOKE BY PASSING THIS
function foo() {
console.log(this); //=> this === element
console.log(arguments); //=> 'a', 'b', 'c'
}
var element = document.querySelector('#foo');
element.addEventListener('click', function(e){
console.log(this); //=> element
console.log(arguments); //=> e === Event
foo.call(element, 'a', 'b', 'c');
});
or
foo.apply(element, ['a', 'b', 'c']);
// #1
element.addEventListener('click', foo.inc); //=> this === element
// #2
element.addEventListener('click', function(){ foo.inc(); }); //=> this === foot
// #3
element.addEventListener('click', foo.inc.bind(foo)); //=> this === foo
BIND EXAMPLE 2
ES3
var obj = {
doIt: function() {},
handle: function() {
var that = this;
document.addEventListener('click', function(e) {
that.doIt();
});
}
}
ES5
var obj = {
doIt: function() {},
handle: function() {
document.addEventListener('click', function(e) {
this.doIt();
}.bind(this)
);
}
}
ES6
var obj = {
doIt: function() {},
handle: function() {
document.addEventListener('click', (e) => this.doIt());
}
}
THE 3 LIVES OF JAVASCRIPT
mid 1990's : DHTML
2000's : jQuery, prototype and RIA
2010's : Node.js and Angular
SERVER-SIDE JAVASCRIPT
PHP in Apache
Java in Tomcat
Javascript in Node.js
EVENT LOOP
An event loop is an entity that handles and processes
external events and converts them into callback
invocations.
SYNC / ASYNC
Single-thread
No preemptive multi-task
⇾ Non-blocking model (remember AJAX)
Need uninterrupted CPU time ? (or the I/O cannot
be made asynchronously)
⇾ fork out a separate process
HTTP
(hello world from the web)
helloWeb.js
var http = require('http');
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(1337);
console.log('Server running at http://localhost:1337/');
$ node helloWeb.js
Go ! ⇾ http://localhost:1337/
EXERCICE
Return an HTML response with HTML content.
(I know it's somewhat simple but we'll need that code base later)
var http = require('http');
REPL
read-eval-print-loop
Interact with Node in Javascript
$ node
> 1 + 5
6
> var add = function (a, b) { return a + b; }
undefined
> add(1, 5)
6
> add
[Function]
GLOBAL OBJECTS
Try this in the REPL :
> console.log("Hello World !");
> global.console.log("Hello World !");
> window.console.log("Hello World !");
global.console and console are identical the same way window.document and document are
identical in the browser.
$ node sync.js
EXERCICE : SYNC / ASYNC
Repair the previous code with async
$ mkdir async
$ cd async
$ npm init
$ npm install async --save
async.js
var async = require('async');
https://www.npmjs.com/package/async#parallel
EVENTS
A single thread but no blocking operations.
Long tasks (read a file) are launched in background and
a callback function is called once completed.
LISTENING EVENTS
Remember jQuery :
$("canvas").on("mouseleave", function() { ... });
Now in Node :
server.on("close", function() { ... })
server.on('close', function() {
console.log('Bye bye !');
})
server.listen(8080);
server.close();
EMITTING EVENTS
Create an EventEmitter :
var EventEmitter = require('events').EventEmitter;
var game = new EventEmitter();
Use emit() :
game.emit('gameover', 'You lost !');
...is an EventEmitter
STREAM
https://nodejs.org/api/stream.html
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write('data: ' + chunk);
}
});
process.stdin.on('end', function() {
process.stdout.write('end');
});
READABLE STREAM
Can read data in "flowing mode" or "paused mode"
Paused mode :
call stream.read() to get chunks of data
Flowing mode can be activated:
by adding a 'data' event handler
by piping (pipe()) the input to a destination
by calling resume()
Flowing mode can be deactivated:
by removing any 'data' event handler and pipe destinations
by calling pause() if there are no pipe
In flow mode, if no data handler is attached and if no pipe destination is set, data may be lost.
READABLE STREAM EVENTS
readable : when a chunk of data can be read
data : in flowing mode (by attaching a 'data' event
listener), data will be passed as soon as it is available
end : when no more data to read
close : for streams that are closeable
error
WRITABLE STREAM
write()
drain() : indicate when it is appropriate to begin
writing more data to the stream.
cork() / uncork() : bufferize / flush data
end() :
Events : finish, pipe, unpipe, error
HTTP STREAM
var http = require('http');
// the end event tells you that you have entire body
req.on('end', function () {
try {
var data = JSON.parse(body);
} catch (er) {
// uh oh! bad json!
res.statusCode = 400;
return res.end('error: ' + er.message);
}
server.listen(1337);
SOCKET & PIPE
A TCP server which listens on port 1337 and echoes
whatever you send it:
var net = require('net');
server.listen(1337, '127.0.0.1');
FILESYSTEM
https://nodejs.org/api/fs.html
Contains all the expected material for naming files,
listing directories (fs.readdir()), reading and writing
files.
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
createWriteStream()
EXERCICE : SHELL
Implement 3 shell commands : pwd, ls, wget
shell.js
// your code here
Steps :
input is a Buffer
foo
<Buffer 61 73 64 0a>
bar
<Buffer 62 61 72 0a>
⇾ need to stringify
1. log stdin input on console
2. stringify the stdin buffer
3. match the first word with a regexp
4. implement the 'pwd' command : use 'process'
5. implement 'ls' with 'fs.readdir()'
6. implement 'wget' with 'http.get()'
HTTP
Low-level Web APIs
var http = require('http');
var url = require('url');
var qs = require('querystring');
https://nodejs.org/api/url.html
https://nodejs.org/api/querystring.html
> var qs = require('querystring')
undefined
> qs.parse('foo=bar&baz=qux&baz=quux&corge')
{ foo: 'bar', baz: [ 'qux', 'quux' ], corge: '' }
URL
> var url = require('url')
undefined
> url.parse('http://localhost:3000/path/to/file?q=1#here')
{ protocol: 'http:',
slashes: true,
auth: null,
host: 'localhost:3000',
port: '3000',
hostname: 'localhost',
hash: '#here',
search: '?q=1',
query: 'q=1',
pathname: '/path/to/file',
path: '/path/to/file?q=1',
href: 'http://localhost:3000/path/to/file?q=1#here' }
POPULATE A TEMPLATE
1. Given the following template :
infos.html
<html>
<head>
<title>My Node.js server</title>
</head>
<body>
<h1>Hello {name}!</h1>
<ul>
<li>Node Version: {node}</li>
<li>V8 Version: {v8}</li>
<li>URL: {url}</li>
<li>Time: {time}</li>
</ul>
</body>
</html>
2. return it populated at
/infos.html?name=Bob
MODULES
MODULES
Node.js features are available in modules
Node.js is shipped with built-in modules
Public modules are available with npm
HTML templates
Database connectors
Dev tools
...
NPM
UNDERSTANDING REQUIRE()
Buit-in modules :
var http = require('http'); // get http.js
var url = require('url'); // get url.js
Third-party modules :
var http = require('module1'); // get [paths]/node_modules/module1.js
var url = require('./module2'); // get module2.js from the current dir
var url = require('../module3'); // get module3.js from the parent dir
Lookup paths :
paths:
[ '/Users/bill/devnode/myproject/node_modules',
'/Users/bill/devnode/node_modules',
'/Users/bill/node_modules',
'/Users/node_modules',
'/node_modules' ]
HOW DO MODULES WORK ?
You have to export public members, others will be kept
private.
var exports = {};
(function() {
})();
console.log(exports.a); // undefined
console.log(exports.foo); // 100
console.log(foo.a); // undefined
console.log(foo.foo); // 100
REPL :
> var config = require('./config')
undefined
> config.urls
[ 'test.com', 'test.org' ]
EXERCICE : DESIGN A MODULE
1. Create a file shapes/circles.js
2. Define the functions area and circumference,
and export them.
3. Create the project descriptor with npm init
4. Create a file circlesTest.js that use the module
for computing the area of a circle with a specific
radius.
MODULE INHERITANCE
You want your module inherit of EventEmitter ?
myStream.js
var util = require("util");
var EventEmitter = require('events').EventEmitter;
// Your events
MyStream.prototype.write = function(data) {
this.emit("data", data);
}
exports.MyStream = MyStream;
stream.on("data", function(data) {
console.log('Received data: "' + data + '"');
})
stream.write("It works!"); // Received data: "It works!"
PUBLISH A MODULE
npm adduser : create a user on the npm repo
Ensure you have :
package.json : at least with name, version and
dependencies
README.md : a more detailed presentation of your
module, the documentation of your API, some
tutorials, etc
npm publish : publish your module
LOCAL VS GLOBAL
Install globally with npm install -g whatever
require('whatever') ? ⇾ install local
use in the shell CLI ? ⇾ install global, binaries will end
up in PATH env var
! Global modules can't be include in your projects
with require()
"version" : "1.2.3",
"SEMVER" RANGES
Used to manage versions of dependencies
Hyphen range : 1.2.3 - 2.3.4 ⟹ 1.2.3 ≤ v ≤ 2.3.4
x range : 1.2.x ⟹ 1.2.0 ≤ v < 1.3.0
Tilde range : ~1.2.3 ⟹ 1.2.3 ≤ v < 1.(2+1).0 ⟹ 1.2.3
≤ v < 1.3.0
Caret range : ^1.2.3 ⟹ 1.2.3 ≤ v < 2.0.0
https://docs.npmjs.com/misc/semver
REST WEB APP WITH
EXPRESS
AFTER
var app = require('express')();
var params = require('express-params');
params.extend(app);
REST
HTTP/REST CRUD SQL
POST Create INSERT
GET Read SELECT
PUT Update UPDATE
DELETE Delete DELETE
ROUTING
ROUTE METHOD
// respond with "Hello World!" on the homepage
app.get('/', function (req, res) {
res.send('Hello World!');
});
// accept POST request on the homepage
app.post('/', function (req, res) {
res.send('Got a POST request');
});
// accept PUT request at /user
app.put('/user', function (req, res) {
res.send('Got a PUT request at /user');
});
// accept DELETE request at /user
app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user');
});
app.all('/secret', function (req, res) {
console.log('Accessing the secret section ...');
});
ROUTE PATHS
Routes are tested one after the other until one
matches.
app.get('/path', function (req, res) {
res.send('Hello World!');
});
ROUTE HANDLERS
app.get('/path', function (req, res) {
res.send('Hello World!');
});
EXPRESS MIDDLEWARES
An Express application is essentially a series of
middleware calls. The next middleware in line in the
request-response cycle is accessible by the next object.
// a middleware sub-stack which handles GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
res.send('User Info');
});
...
http://expressjs.com/resources/middleware.html
TEMPLATING
Template engines can be plugged to Express : Jade,
Mustache, Handlebars, EJS...
$ npm install express-handlebars
Loops :
<ul>
{{#each user}}
<li>{{name}}</li>
{{/each}}
</ul>
USING SOCKET.IO
$ npm install socket.io
var io = require('socket.io').listen(server);
server.listen(1337);
SOCKET.IO CLIENT
<!DOCTYPE html> index.html
<html>
<head>
<meta charset="utf-8" />
<title>Socket.io</title>
</head>
<body>
<h1>Communication avec socket.io !</h1>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
</body>
</html>
USING JQUERY
<script src="http://code.jquery.com/jquery-1.11.1.js"</script>
<form action="">
<input id="text" autocomplete="off" />
<button>Send</button>
</form>
<ul id="discussion"></ul>
$('#discussion').append($('<li>').text(message));
$('form').submit(function(){
// your code here
return false;
});
$('#text').val()
$('#text').val('')
DATA ACCESS
MYSQL
$ npm install mysql
CONNECTION
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
port : 3306,
user : 'bob',
password : 'secret',
database : 'address_book'
});
connection.connect(function(err) {
if (err) {
console.error('error connecting: ' + err.stack);
return;
}
console.log('connected as id ' + connection.threadId);
});
SQL QUERIES
var mysql = require('mysql');
connection.query({
sql: 'SELECT * FROM `books` WHERE `author` = ?',
timeout: 40000, // 40s
values: ['David']
}, function (error, results, fields) {
// fields will contain information about the returned results fields (if any)
});
ESCAPING
var sorter = 'date';
var sql = 'SELECT * FROM posts WHERE id > '
+ connection.escape(userId); // escape values
+ ' ORDER BY '
+ connection.escapeId(sorter); // escape SQL identifiers
OTHER FEATURES
CONNECTION POOL
var mysql = require('mysql');
var connection = mysql.createPool({
connectionLimit : 10,
host : 'localhost',
port : 3306,
user : 'bob',
password : 'secret',
database : 'address_book'
});
Submit you request with the help of POSTMAN REST client on chrome
MONGODB
$ mkdir -p ./data/db
$ mongod --dbpath=./data/db --port 27017
CONNECTION
var mongodb = require('mongodb');
var MongoClient = mongodb.MongoClient;
var url = "mongodb://localhost:27017/myproject";
// Use connect method to connect to the Server
MongoClient.connect(url, function doConnect(err, db) {
var coll = db.collection("myusers");
// do something...
db.close();
});
BULK LOADING
Large number of insertions have to be performed with bulk operations.
INSERTION
EXERCICE : MONGODB BULK LOADING
Data set : stats about cities by département
Load with bulk insertion this data set :
http://public.opendatasoft.com/explore/dataset/code-
insee-postaux-geoflar/
Use Robomongo for browsing data.
COLLECTIONS
db.collection("myusers", function (err,coll) {
coll.stats(function doStats(err, stats) {
console.log(stats);
db.close();
});
});
db.dropCollection("myusers");
QUERYING
coll.find(
{ age: { $gt: 18 } },
{ name: 1, address: 1 }
).limit(5)
.find(
{
$or: [ { qty: { $gt: 100 } }, { price: { $lt: 9.95 } } ]
}
)
UPDATE
Update the data and the schema as well
coll.update(
{ item: "MNO2" },
{
$set: {
category: "apparel",
details: { model: "14Q3", manufacturer: "XYZ Company" }
},
$currentDate: { lastModified: true }
}
)
UPDATE FROM THE CONSOLE
> db.users.insert({name: 'Philippe', country: 'France'});
WriteResult({ "nInserted" : 1 })
> var me = db.users.findOne({name: 'Philippe'});
> me
{
"_id" : ObjectId("556d6b030e9d920f8c6b336d"),
"name" : "Philippe",
"country" : "France"
}
> me.eyesColor = 'Blue'
Blue
> db.users.save(me);
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne();
{
"_id" : ObjectId("556d6b030e9d920f8c6b336d"),
"name" : "Philippe",
"country" : "France",
"eyesColor" : "Blue"
}
DELETE
coll.remove( {} ); // remove all
STAGE OPERATORS
$match
$group : note that grouping with _id : null makes a
single group
$project : reshapes the documents in the stream,
such as by adding new fields or removing existing
fields.
$limit and $skip
$sort
$unwind : flatten an array field
...
ACCUCUMULATOR
$sum, $avg, $min, $max, $first, $last
$push : reverse of $unwind
...
$match
SINGLE PURPOSE AGGREGATION OPERATIONS
coll.count( { a: 1 } ); // count records under condition
coll.group( {
key: { a: 1 },
cond: { a: { $lt: 3 } },
reduce: function(cur, result) { result.count += cur.count },
initial: { count: 0 }
} );
MONGODB MAP-REDUCE
EXERCICE : MONGODB AGGREGATION
With the same data set :
1. return département with population > 1000000
2. return average city population by département
3. return largest and smallest city by département
Note : duplicate fields (such as population) exist for
the same fields.code_commune_insee
MONGO : MONGOOSE
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var Cat = mongoose.model('Cat', { name: String });
var kitty = new Cat({ name: 'Zildjian' });
kitty.save(function (err) { });
TOOLS
DEBUG
Node.js built-in debugger
$ node debug myscript.js
x = 5; myscript.js
setTimeout(function () {
debugger;
console.log("world");
}, 1000);
console.log("hello");
Commands :
cont, c - Continue execution
next, n - Step next
step, s - Step in
out, o - Step out
pause - Pause running code
ISSUES
Logs :
process.nextTick(function() { throw err; })
^
AssertionError: null == { [MongoError: connect ECONNREFUS
ED]
name: 'MongoError', message: 'connect ECONNREFUSED' }
TROUBLESHOOTING
The purpose of uncaughtException is not to catch and
go on, but to allow to free resources and log the error
context. When an error is raised to the event loop, the
program should be considered inconsistent.
process.on('uncaughtException', function(err) {
console.log(JSON.stringify(process.memoryUsage()));
console.error("UncaughtException : the program will end. "
+ err + ", stacktrace: " + err.stack);
return process.exit(1);
});
try {
var bwr = bulk.execute();
} catch(err) {
console.log("Stupid Mongo err : " + err);
}
PREVENT ERRORS
Use Node.js clusters : the Master Cluster will be able
to restart a slave
Use a framework like Express, that manages errors
for you
"Promises" can help to manage errors efficiently :
errors raised by some code inside a promise will be
catched and propagated to the fail or catch
function or in the error callback of the then function
TESTING
Testing with Mocha
var assert = require('assert');
after(function() {
// Some tear-down
});
});
$ npm test
Users management $ npm run testjson
✓ Should count 0 people for new users
1) Should count 1 people after addind a user
1 passing (13ms)
1 failing
Users.prototype.add = function(user) {
// add that user
}
Users.prototype.count =function() {
return this.people.length;
}
module.exports = Users;
ALM
requirements management,
software architecture,
computer programming,
software testing,
software maintenance,
change management,
continuous integration,
project management,
and release management
FRONTEND TOOLS
Yeoman : web's scaffolding tool
Bower : package manager for the Web
Grunt : automate build tasks
Gulp : Grunt, the streaming way
Gruntfile.js
/* global module:false */
module.exports = function(grunt) {
var port = grunt.option('port') || 8000;
// Project configuration
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
meta: {
banner: '/*!\n' +
' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n'
' * http://lab.hakim.se/reveal-js\n' +
' * MIT licensed\n' +
' *\n' +
' * Copyright (C) 2015 Hakim El Hattab, http://hakim.se\n' +
' */'
},
qunit: {
files: [ 'test/*.html' ]
},
uglify: {
options: {
banner: '<%= meta.banner %>\n'
},
build: {
src: 'js/reveal.js',
dest: 'js/reveal.min.js'
}
},
sass: {
core: {
files: {
'css/reveal.css': 'css/reveal.scss',
}
},
themes: {
files: {
'css/theme/black.css': 'css/theme/source/black.scss',
'css/theme/white.css': 'css/theme/source/white.scss',
'css/theme/league.css': 'css/theme/source/league.scss',
'css/theme/beige.css': 'css/theme/source/beige.scss',
'css/theme/beige.css': 'css/theme/source/beige.scss',
'css/theme/night.css': 'css/theme/source/night.scss',
'css/theme/serif.css': 'css/theme/source/serif.scss',
'css/theme/simple.css': 'css/theme/source/simple.scss',
'css/theme/sky.css': 'css/theme/source/sky.scss',
'css/theme/moon.css': 'css/theme/source/moon.scss',
'css/theme/solarized.css': 'css/theme/source/solarized.scss'
'css/theme/blood.css': 'css/theme/source/blood.scss'
}
}
},
autoprefixer: {
dist: {
src: 'css/reveal.css'
}
},
cssmin: {
compress: {
files: {
'css/reveal.min.css': [ 'css/reveal.css' ]
}
}
},
jshint: {
options: {
curly: false,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
eqnull: true,
browser: true,
expr: true,
globals: {
head: false,
module: false,
console: false,
unescape: false,
define: false,
exports: false
}
},
files: [ 'Gruntfile.js', 'js/reveal.js' ]
},
connect: {
server: {
options: {
port: port,
base: '.',
livereload: true,
open: true
}
}
},
zip: {
'reveal-js-presentation.zip': [
'index.html',
'css/**',
'js/**',
'lib/**',
'images/**',
'plugin/**'
]
},
watch: {
options: {
livereload: true
},
js: {
files: [ 'Gruntfile.js', 'js/reveal.js' ],
tasks: 'js'
},
theme: {
files: [ 'css/theme/source/*.scss', 'css/theme/template/*.scss'
tasks: 'css-themes'
},
css: {
files: [ 'css/reveal.scss' ],
tasks: 'css-core'
},
html: {
files: [ 'index.html']
}
}
});
// Dependencies
// Dependencies
grunt.loadNpmTasks( 'grunt-contrib-qunit' );
grunt.loadNpmTasks( 'grunt-contrib-jshint' );
grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
grunt.loadNpmTasks(
grunt.loadNpmTasks(
'grunt-contrib-uglify' );
'grunt-contrib-watch' );
grunt.loadNpmTasks( 'grunt-sass' );
grunt.loadNpmTasks( 'grunt-contrib-connect' );
grunt.loadNpmTasks( 'grunt-autoprefixer' );
grunt.loadNpmTasks( 'grunt-zip' );
// Default task
grunt.registerTask( 'default', [ 'css', 'js' ] );
// JS task
grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] );
// Theme CSS
grunt.registerTask( 'css-themes', [ 'sass:themes' ] );
// All CSS
grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] );
// Run tests
grunt.registerTask( 'test', [ 'jshint', 'qunit' ] );
};
DEPLOYMENT
SYSTEM INTEGRATION
stability, performance, security, maintainability
health check and balance traffic
systemd (Fedora), foreverjs, pm2 : ensure Node.js will stay running in case of a crash
stagecoach : deploy node.js applications to your staging and production servers
n : node version manager
CLUSTERING
Separate processes ⇾ same server port.
A server is down ? ⇾ others will be used.
A peak load of traffic ? ⇾ allocate another worker.
CLUSTERING : THE APP
app.js
var express=require("express");
var app=express();
app.get('/',function(req,res) {
res.end("Hello world !");
});
app.listen(3000,function() {
console.log("Running at PORT 3000");
});
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork(); // clone the process for each core
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
require("./app.js");
}
$ node cluster.js