3. INTRO TO NODE.JS
WHAT IS NODE.JS?
It’s fast because it’s mostly C code
Allows you to build scalable network
applications using JavaScript on the server-side.
V8 JavaScript Runtime
Node.js
4. INTRO TO NODE.JS
WHAT COULD YOU BUILD?
• Websocket Server
• Fast File Upload Client
• Ad Server
• Any Real-Time Data Apps
Like a chat server
5. INTRO TO NODE.JS
WHAT IS NODE.JS NOT?
• A Web Framework
• For Beginners It’s very low level
• Multi-threaded
You can think of it as a single threaded server
6. INTRO TO NODE.JS
OBJECTIVE: PRINT FILE CONTENTS
This is a “Callback”
Read file from Filesystem, set equal to “contents”
Print contents
• Blocking Code
• Non-Blocking Code
Do something else
Read file from Filesystem
whenever you’re complete, print the contents
Do Something else
7. console.log(contents);
INTRO TO NODE.JS
BLOCKING VS NON-BLOCKING
var contents = fs.readFileSync('/etc/hosts');
console.log(contents);
console.log('Doing something else');
• Blocking Code
• Non-Blocking Code
console.log('Doing something else');
Stop process until complete
fs.readFile('/etc/hosts', function(err, contents) {
});
8. fs.readFile('/etc/hosts', function(err, contents) {
console.log(contents);
});
INTRO TO NODE.JS
CALLBACK ALTERNATE SYNTAX
var callback = function(err, contents) {
console.log(contents);
}
fs.readFile('/etc/hosts', callback);
Same as
9. INTRO TO NODE.JS
BLOCKING VS NON-BLOCKING
blocking
0s
non-blocking
10s5s
0s 10s5s
fs.readFile('/etc/hosts', callback);
fs.readFile('/etc/inetcfg', callback);
var callback = function(err, contents) {
console.log(contents);
}
10. hello.js
NODE.JS HELLO DOG
$ curl http://localhost:8080
Hello, this is dog.
How we require modules
Status code in header
Response body
Close the connection
Listen for connections on this port
$ node hello.js Run the server
var http = require('http');
http.createServer(function(request, response) {
response.writeHead(200);
response.write("Hello, this is dog.");
response.end();
}).listen(8080);
console.log('Listening on port 8080...');
Listening on port 8080...
11. THE EVENT LOOP
var http = require('http');
http.createServer(function(request, response) {
}).listen(8080);
console.log('Listening on port 8080...');
Starts the Event Loop when finished
...
Known Events
request
Checking
for
Events
Run the Callback
12. INTRO TO NODE.JS
WHY JAVASCRIPT?
“JavaScript has certain characteristics that make it very
different than other dynamic languages, namely that it has
no concept of threads. Its model of concurrency is
completely based around events.” - Ryan Dahl
13. THE EVENT LOOP
Known Events
requestChecking
for
Events
connection
close
Event Queue
close
request
Events processed one at a time
14. INTRO TO NODE.JS
WITH LONG RUNNING PROCESS
Represent long running process
var http = require('http');
http.createServer(function(request, response) {
}).listen(8080);
response.writeHead(200);
response.write("Dog is done.");
response.end();
setTimeout(function(){
}, 5000); 5000ms = 5 seconds
response.write("Dog is running.");
15. INTRO TO NODE.JS
TWO CALLBACKS HERE
var http = require('http');
http.createServer(function(request, response) {
response.writeHead(200);
request
timeout
}).listen(8080);
response.write("Dog is done.");
response.end();
setTimeout(function(){
}, 5000);
response.write("Dog is running.");
16. TWO CALLBACKS TIMELINE
0s 10s5s
Request comes in, triggers request event
Request Callback executes
setTimeout registered
Request comes in, triggers request event
Request Callback executes
setTimeout registered
triggers setTimeout event
setTimeout Callback executes
triggers setTimeout event
setTimeout Callback
request
timeout
17. WITH BLOCKING TIMELINE
0s 10s5s
Request comes in, triggers request event
Request Callback executes
setTimeout executed
Request comes in, waits for server
Request comes in
triggers setTimeout event
setTimeout Callback executed
Wasted Time
Request Callback executes
18. INTRO TO NODE.JS
• Calls out to web services
TYPICAL BLOCKING THINGS
• Reads/Writes on the Database
• Calls to extensions
20. EVENTS
EVENTS IN THE DOM
The DOM
The DOM triggers Events
click
events
you can listen for those events
submit
hover
When ‘click’ event is triggered
attach
$("p").on("click", function(){ ... });
26. http.createServer(function(request, response){ ... });
EVENTS
ALTERNATE SYNTAX
var server = http.createServer();
function(request, response){ ... });server.on('request',
This is how we add
Same as
function(){ ... });server.on('close',
add event listeners
28. S T R E A M S
WHAT ARE STREAMS?
Start Processing
Immediately
Streams can be readable, writeable, or both
29. S T R E A M S
STREAMING RESPONSE
Our clients receive "Dog is running."
"Dog is done."
(5 seconds later)
http.createServer(function(request, response) {
}).listen(8080);
response.writeHead(200);
response.write("Dog is done.");
response.end();
setTimeout(function(){
}, 5000);
response.write("Dog is running.");
readable stream writable stream
30. S T R E A M S
HOW TO READ FROM THE REQUEST?
EventEmitter
Readable Stream data
events
emit
Lets print what we receive from the request.
http.createServer(function(request, response) {
request.on('data', function(chunk) {
console.log(chunk.toString());
});
response.end();
}).listen(8080)
request.on('end', function() {
});
response.writeHead(200);
end
31. S T R E A M S
LETS CREATE AN ECHO SERVER
http.createServer(function(request, response) {
request.on('data', function(chunk) {
});
response.end();
}).listen(8080)
request.on('end', function() {
});
response.writeHead(200);
response.write(chunk); request.pipe(response);
32. S T R E A M S
LETS CREATE AN ECHO SERVER!
http.createServer(function(request, response) {
}).listen(8080)
request.pipe(response);
$ curl -d 'hello' http://localhost:8080
Hello on client
response.writeHead(200);
cat 'bleh.txt' | grep 'something'
Kinda like on the command line
33. S T R E A M S
READING AND WRITING A FILE
var fs = require('fs');
var file = fs.createReadStream("readme.md");
var newFile = fs.createWriteStream("readme_copy.md");
require filesystem module
file.pipe(newFile);
34. S T R E A M S
UPLOAD A FILE
var fs = require('fs');
var newFile = fs.createWriteStream("readme_copy.md");
var http = require('http');
http.createServer(function(request, response) {
request.pipe(newFile);
}).listen(8080);
$ curl --upload-file readme.md http://localhost:8080
uploaded!
response.end('uploaded!');
request.on('end', function() {
});
37. THINK OF A MILK JUG
milkStream.pause();
milkStream.resume();
});
Once milk jug is drained
38. PIPE SOLVES BACKPRESSURE
readStream.resume();
});
Pause when writeStream is full
writeStream.on('drain', function(){
readStream.on('data', function(chunk) {
writeStream.write(chunk);
});
var buffer_good =
if (!buffer_good) readStream.pause(); returns false
if kernel buffer full
Resume when ready to write again
readStream.pipe(writeStream);
All encapsulated in
40. S T R E A M S
FILE UPLOADING PROGRESS
$ curl --upload-file file.jpg http://localhost:8080
progress: 3%
progress: 6%
progress: 9%
progress: 12%
progress: 13%
...
progress: 99%
progress: 100%
Outputs:
• HTTP Server
• File System
We’re going to need:
41. S T R E A M S
DOCUMENTATION http://nodejs.org/api/
Stability Scores
42. S T R E A M S
REMEMBER THIS CODE?
var fs = require('fs');
var newFile = fs.createWriteStream("readme_copy.md");
var http = require('http');
http.createServer(function(request, response) {
request.pipe(newFile);
}).listen(8080);
response.end('uploaded!');
request.on('end', function() {
});
43. S T R E A M S
REMEMBER THIS CODE?
var newFile = fs.createWriteStream("readme_copy.md");
http.createServer(function(request, response) {
request.pipe(newFile);
}).listen(8080);
...
request.on('data', function(chunk) {
uploadedBytes += chunk.length;
var progress = (uploadedBytes / fileBytes) * 100;
response.write("progress: " + parseInt(progress, 10) + "%n");
});
var uploadedBytes = 0;
var fileBytes = request.headers['content-length'];
47. MODULES
LETS CREATE OUR OWN MODULE
custom_hello.js custom_goodbye.js
app.js
exports = hello;
var hello = require('./custom_hello');
hello();
exports defines what require returns
var hello = function() {
console.log("hello!");
}
exports.goodbye = function() {
console.log("bye!");
}
var gb = require('./custom_goodbye');
gb.goodbye();
require('./custom_goodbye').goodbye(); If we only need to call once
48. MODULES
EXPORT MULTIPLE FUNCTIONS
my_module.js
app.js
var foo = function() { ... }
var bar = function() { ... }
exports.foo = foo
exports.bar = bar
var myMod = require('./my_module');
myMod.foo();
myMod.bar();
my_module.js
foo
bar
var baz = function() { ... }
baz
“private”
49. MODULES
MAKING HTTP REQUESTS
app.js
logs response body
begins request
var http = require('http');
var options = {
host: 'localhost', port: 8080, path: '/', method: 'POST'
}
var request = http.request(options, function(response){
response.on('data', function(data){
console.log(data);
});
});
request.end();
request.write(message);
finishes request
var message = "Here's looking at you, kid.";
50. MODULES
ENCAPSULATING THE FUNCTION
app.jsvar http = require('http');
var makeRequest = function(message) {
var options = {
host: 'localhost', port: 8080, path: '/', method: 'POST'
}
var request = http.request(options, function(response){
response.on('data', function(data){
console.log(data);
});
});
request.end();
}
makeRequest("Here's looking at you, kid.");
request.write(message);
Text
51. MODULES
CREATING & USING A MODULE
make_request.jsvar http = require('http');
var makeRequest = function(message) {
}
exports = makeRequest;
...
app.jsvar makeRequest = require('./make_request');
makeRequest("Here's looking at you, kid");
makeRequest("Hello, this is dog");
Where does require look for modules?
52. MODULES
REQUIRE SEARCH
var make_request = require('make_request')
/Home/eric/my_app/app.js
• /Home/eric/my_app/node_modules/
var make_request = require('./make_request')
var make_request = require('../make_request')
var make_request = require('/Users/eric/nodes/make_request')
• /Home/eric/node_modules/make_request.js
• /Home/node_modules/make_request.js
• /node_modules/make_request.js
look in same directory
look in parent directory
Search in node_modules directories
53. MODULES
NPM: THE USERLAND SEA
Package manager for node
• Comes with node
• Module Repository
• Dependency Management
• Easily publish modules
• “Local Only”
“Core” is small. “Userland” is large.
54. MODULES
INSTALLING A NPM MODULE
$ npm install request https://github.com/mikeal/request
In /Home/my_app
Home my_app requestnode_modules
Installs into local node_modules directory
var request = require('request');
In /Home/my_app/app.js
Loads from local node_modules directory
55. MODULES
LOCAL VS GLOBAL
Global npm modules can’t be required
$ npm install coffee-script -g
Install modules with executables globally
$ coffee app.coffee
var coffee = require('coffee-script');
$ npm install coffee-script
var coffee = require('coffee-script');
global
Install them locally
77. SOCKET.IO
SENDING MESSAGES TO SERVER
io.sockets.on('connection', function(client) {
});
var server = io.connect('http://localhost:8080');
<script>
</script>
app.js
index.html
client.on('messages', function (data) {
});
console.log(data);
$('#chat_form').submit(function(e){
var message = $('#chat_input').val();
socket.emit('messages', message);
});
listen for ‘messages’ events
emit the ‘messages’ event on the server
82. SOCKET.IO
SAVING DATA ON THE SOCKET
io.sockets.on('connection', function(client) {
});
var server = io.connect('http://localhost:8080');
<script>
</script>
app.js
index.html
client.on('join', function(name) {
client.set('nickname', name);
});
set the nickname associated
with this client
server.on('connect', function(data) {
$('#status').html('Connected to chattr');
nickname = prompt("What is your nickname?");
server.emit('join', nickname);
});
notify the server of the
users nickname
83. SOCKET.IO
SAVING DATA ON THE CLIENT
io.sockets.on('connection', function(client) {
});
app.js
client.on('join', function(name) {
client.set('nickname', name);
});
set the nickname associated
with this client
client.on('messages', function(data){
});
client.broadcast.emit("chat", name + ": " + message);
client.get('nickname', function(err, name) {
});
get the nickname of this client before broadcasting message
broadcast with the name and message
87. });
});
});
P E R S I S T I N G D A T A
RECENT MESSAGES
io.sockets.on('connection', function(client) {
client.on('join', function(name) {
client.set('nickname', name);
client.broadcast.emit("chat", name + " joined the chat");
});
client.on("messages", function(message){
client.get("nickname", function(error, name) {
client.broadcast.emit("messages", name + ": " + message);
app.js
88. });
});
});
P E R S I S T I N G D A T A
STORING MESSAGES
io.sockets.on('connection', function(client) {
client.on("messages", function(message){
client.get("nickname", function(error, name) {
app.js
storeMessage(name, message);
var messages = []; store messages in array
var storeMessage = function(name, data){
messages.push({name: name, data: data});
if (messages.length > 10) {
messages.shift();
}
}
add message to end of array
if more than 10 messages long,
remove the last one
when client sends a message
call storeMessage
89. P E R S I S T I N G D A T A
EMITTING MESSAGES
io.sockets.on('connection', function(client) {
});
app.js
client.on('join', function(name) {
});
messages.forEach(function(message) {
client.emit("messages", message.name + ": " + message.data);
}); iterate through messages array
and emit a message on the connecting
client for each one
...
91. P E R S I S T I N G D A T A
PERSISTING STORES
• MongoDB
• CouchDB
• PostgreSQL
• Memcached
• Riak
All non-blocking!
Redis is a key-value store
92. P E R S I S T I N G D A T A
REDIS DATA STRUCTURES
Strings SET, GET, APPEND, DECR, INCR...
Hashes HSET, HGET, HDEL, HGETALL...
Lists LPUSH, LREM, LTRIM, RPOP, LINSERT...
Sets SADD, SREM, SMOVE, SMEMBERS...
Sorted Sets ZADD, ZREM, ZSCORE, ZRANK...
data structure commands
93. P E R S I S T I N G D A T A
REDIS COMMAND DOCUMENTATION
95. client.get("message1", function(err, reply){
console.log(reply);
});
P E R S I S T I N G D A T A
REDIS
key value
"hello, yes this is dog"
var redis = require('redis');
var client = redis.createClient();
client.set("message1", "hello, yes this is dog");
client.set("message2", "hello, no this is spider");
commands are non-blocking
$ npm install redis
96. P E R S I S T I N G D A T A
REDIS LISTS: PUSHING
Add a string to the “messages” list
client.lpush("messages", message, function(err, reply){
console.log(reply);
}); "1”
var message = "Hello, no this is spider";
client.lpush("messages", message, function(err, reply){
console.log(reply);
}); "2”
replies with list length
Add another string to “messages”
var message = "Hello, this is dog";
97. P E R S I S T I N G D A T A
REDIS LISTS: RETRIEVING
Using LPUSH & LTRIM
trim keeps first two strings
and removes the rest
Retrieving from list
client.lrange("messages", 0, -1, function(err, messages){
console.log(messages);
})
["Hello, no this is spider", "Oh sorry, wrong number"]
replies with all strings in list
var message = "Oh sorry, wrong number";
client.lpush("messages", message, function(err, reply){
client.ltrim("messages", 0, 1);
});
98. P E R S I S T I N G D A T A
CONVERTING MESSAGES TO REDIS
var storeMessage = function(name, data){
messages.push({name: name, data: data});
if (messages.length > 10) {
messages.shift();
}
}
Let’s use the List data-structure
app.js
99. P E R S I S T I N G D A T A
CONVERTING STOREMESSAGE
var storeMessage = function(name, data){
}
var redisClient = redis.createClient();
var message = JSON.stringify({name: name, data: data});
redisClient.lpush("messages", message, function(err, response) {
redisClient.ltrim("messages", 0, 10);
});
keeps newest 10 items
app.js
need to turn object into string to store in redis
100. P E R S I S T I N G D A T A
OUTPUT FROM LIST
client.on('join', function(name) {
});
messages.forEach(function(message) {
client.emit("messages", message.name + ": " + message.data);
});
app.js
101. P E R S I S T I N G D A T A
OUTPUT FROM LIST
client.on('join', function(name) {
});
messages.forEach(function(message) {
client.emit("messages", message.name + ": " + message.data);
});
redisClient.lrange("messages", 0, -1, function(err, messages){
messages = messages.reverse();
message = JSON.parse(message);
});
app.js
reverse so they are emitted
in correct order
parse into JSON object
103. P E R S I S T I N G D A T A
CURRENT CHATTER LIST
Sets are lists of unique data
client.sadd("names", "Dog");
client.sadd("names", "Spider");
client.sadd("names", "Gregg");
client.srem("names", "Spider");
client.smembers("names", function(err, names){
console.log(names);
});
["Dog", "Gregg"]
reply with all members of set
add & remove members of the names set
104. P E R S I S T I N G D A T A
ADDING CHATTERS
client.on('join', function(name){
client.broadcast.emit("add chatter", name);
redisClient.sadd("chatters", name);
});
app.js
notify other clients a chatter has joined
add name to chatters set
index.htmlserver.on('add chatter', insertChatter);
var insertChatter = function(name) {
var chatter = $('<li>'+name+'</li>').data('name', name);
$('#chatters').append(chatter);
}
105. P E R S I S T I N G D A T A
ADDING CHATTERS (CONT)
client.on('join', function(name){
client.broadcast.emit("add chatter", name);
redisClient.sadd("chatters", name);
});
app.js
notify other clients a chatter has joined
add name to chatters set
emit all the currently logged in chatters
to the newly connected client
redisClient.smembers('names', function(err, names) {
names.forEach(function(name){
client.emit('add chatter', name);
});
});
106. P E R S I S T I N G D A T A
REMOVING CHATTERS
client.on('disconnect', function(name){ app.js
index.html
client.get('nickname', function(err, name){
client.broadcast.emit("remove chatter", name);
redisClient.srem("chatters", name);
});
});
remove chatter when they disconnect from server
server.on('remove chatter', removeChatter);
var removeChatter = function(name) {
$('#chatters li[data-name=' + name + ']').remove();
}
107. P E R S I S T I N G D A T A
WELCOME TO CHATTR