├── sessions ├── hour1 │ ├── readme.md │ ├── 05_creating-node-projects │ │ ├── .gitignore │ │ ├── test │ │ │ └── .gitignore │ │ ├── node_modules │ │ │ └── .gitignore │ │ ├── package.json │ │ ├── src │ │ │ ├── readme.md │ │ │ └── server.js │ │ └── readme.md │ ├── 06_working-with-packages │ │ ├── npm-packages │ │ │ ├── package.json │ │ │ ├── server.js │ │ │ └── readme.md │ │ └── user-defined-packages │ │ │ ├── helpers │ │ │ └── timestamp.js │ │ │ ├── server.js │ │ │ └── readme.md │ ├── 00_hello-world-from-azure │ │ ├── server.js │ │ └── readme.md │ ├── 03_javascript-and-node │ │ └── readme.md │ ├── 02_event-loop-explained │ │ └── readme.md │ ├── 01_node-explained │ │ └── readme.md │ └── 04_javascript-primer │ │ └── readme.md ├── hour2 │ ├── 00_node-and-http │ │ ├── server.js │ │ └── readme.md │ ├── 04_socket-io-a-primer │ │ ├── package.json │ │ ├── server.js │ │ ├── index.html │ │ └── readme.md │ ├── 02_working-with-filesystem │ │ ├── test.html │ │ ├── server.js │ │ ├── server_with_caching.js │ │ ├── server_with_watchers.js │ │ └── readme.md │ ├── readme.md │ ├── 01_responding-to-requests │ │ └── readme.md │ └── 03_a-modern-app-framework │ │ └── readme.md ├── hour0 │ ├── readFile.php │ ├── readFile.js │ ├── readFile.py │ └── README.md └── hour3 │ ├── 05_working_with_cookies │ ├── server.js │ └── readme.md │ ├── 02_primer-to-Windows-Azure-Table-Storage │ └── readme.md │ ├── 03_using-a-data-model-with-Azure-tables │ ├── readme.md │ ├── models │ │ └── wine.js │ └── winenotebook_with_model.js │ ├── 04_using-continuation-tokens-for-pagination │ ├── readme.md │ └── table-storage-pagination-sample.js │ ├── 00_saving-files │ ├── server.js │ └── readme.md │ └── 01_working-with-a-database │ ├── winenotebook.js │ └── readme.md ├── package.json ├── .gitignore ├── upcoming-events.md ├── event-description.md ├── readme.md └── lessonplan.md /sessions/hour1/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sessions/hour2/00_node-and-http/server.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sessions/hour1/05_creating-node-projects/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sessions/hour1/05_creating-node-projects/test/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sessions/hour1/05_creating-node-projects/node_modules/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sessions/hour1/05_creating-node-projects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-project-structure-sample" 3 | , "version": "0.0.1" 4 | , "private": true 5 | } -------------------------------------------------------------------------------- /sessions/hour1/06_working-with-packages/npm-packages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-package-example" 3 | , "version": "0.0.1" 4 | , "private": true 5 | , "dependencies": { 6 | "slang":">=0.2.0" 7 | } 8 | } -------------------------------------------------------------------------------- /sessions/hour1/00_hello-world-from-azure/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | http.createServer(function (req, res) { 3 | res.writeHead(200, {'Content-Type': 'text/plain'}); 4 | res.end('Hello World\n'); 5 | }).listen(process.env.PORT); 6 | -------------------------------------------------------------------------------- /sessions/hour0/readFile.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sessions/hour2/04_socket-io-a-primer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mukhin_chat", 3 | "description": "example chat application with socket.io", 4 | "version": "0.0.1", 5 | "dependencies": { 6 | "express": "", 7 | "socket.io": "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sessions/hour0/readFile.js: -------------------------------------------------------------------------------- 1 | 2 | /** NODE.JS **/ 3 | var fs = require("fs"); 4 | var filePath = __dirname + "/README.md"; 5 | fs.readFile(filePath, "utf-8", function(err, data) { 6 | if (err) 7 | return console.log("Could not read file " + filePath); 8 | console.log(data); 9 | }); -------------------------------------------------------------------------------- /sessions/hour2/02_working-with-filesystem/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello! 5 | 8 | 9 | 10 |

Hello!

11 | 12 | -------------------------------------------------------------------------------- /sessions/hour0/readFile.py: -------------------------------------------------------------------------------- 1 | 2 | """ PYTHON CODE FOR READING A SIMPLE FILE TO CONSOLE """ 3 | import os 4 | 5 | filePath = os.path.join(os.getcwd(), "README.md") 6 | 7 | if not (os.path.exists(filePath)): 8 | print "Could not read file %s" % filePath 9 | else: 10 | print open(filePath, "r").readlines() -------------------------------------------------------------------------------- /sessions/hour1/06_working-with-packages/user-defined-packages/helpers/timestamp.js: -------------------------------------------------------------------------------- 1 | /* Exports the current time in the form of a string-based timestamp */ 2 | exports.currentTime = function(){ 3 | return Math.round((new Date()).getTime() / 1000).toString(); //Returns a string 4 | } 5 | 6 | exports.currentDate = function(){ 7 | return new Date(); 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-bootcamp-dependencies" 3 | , "version": "0.0.1" 4 | , "private": true 5 | , "dependencies": { 6 | "cookies":"0.2.1", 7 | "slang":"0.2.0", 8 | "azure":"0.5.1", 9 | "formidable":"1.0.9", 10 | "querystring":"0.1.0", 11 | "socket.io":"0.8.7", 12 | "node-uuid":"1.3.3" 13 | } 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | 17 | #Ignore sublime files 18 | *.sublime-project 19 | *.sublime-workspace 20 | 21 | #Ignore Azure package crap 22 | local_package* 23 | *.cspkg 24 | *.logs 25 | 26 | #Ignore IISNode stuff 27 | *.debug -------------------------------------------------------------------------------- /sessions/hour2/02_working-with-filesystem/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var fs = require('fs'); 3 | 4 | http.createServer(function (req, res) { 5 | fs.readFile(__dirname + req.url, 'utf-8', function(err, data) { 6 | if (err) { 7 | res.writeHead(404, {'Content-Type': 'text/plain'}); 8 | res.end('File ' + req.url + ' not found\n'); 9 | } 10 | else { 11 | res.writeHead(200, {'Content-Type': 'text/plain'}); 12 | res.end(data); 13 | } 14 | }); 15 | }).listen(process.env.PORT || 80, "0.0.0.0"); -------------------------------------------------------------------------------- /upcoming-events.md: -------------------------------------------------------------------------------- 1 | # Upcoming Node Bootcamp Events 2 | Here's our schedule for our upcoming Node Bootcamp events! 3 | 4 | 1. March 17, 2012 RocketSpace, San Francisco, CA **[register](http://nodejsatrocketspace.eventbrite.com/)** 5 | 6 | 2. March 24, 2012 Miller Business Innovation Center, Salt Lake City, UT **[register](http://nodejsatmbic.eventbrite.com/)** 7 | 8 | 3. April 14, 2012 Uncubed, Denver, CO **[register](http://nodejsatuncubed.eventbrite.com/)** 9 | 10 | 4. April 21, 2012 MSFT SVC, Mountain View, CA **[register](http://nodejsatmicrosoftsvc.eventbrite.com/)** 11 | 12 | 5. April 28, 2012 Gangplank, Phoenix, AZ Coming Soon! 13 | -------------------------------------------------------------------------------- /sessions/hour1/05_creating-node-projects/src/readme.md: -------------------------------------------------------------------------------- 1 | How To Use This Example 2 | -------- 3 | 4 | This particular example serves plain text back in response to an HTTP request 5 | connection on a port specified by the process or port 3000 and simply responds "Hello World!" back to the end-user. 6 | 7 | To use this example, start the application: 8 | 9 | c:\introtonode\helloworld-http> node server.js 10 | c:\introtonode\helloworld-http> starting server... 11 | 12 | To test your application, simply visit http://127.0.0.1:{PORT || 3000} in your web browser or use curl if you have it: 13 | 14 | $ curl http://127.0.0.1:3000 15 | $ Hello World! -------------------------------------------------------------------------------- /sessions/hour2/readme.md: -------------------------------------------------------------------------------- 1 | Hour 2: Node and Web Applications 2 | ================================= 3 | 4 | * HTTP and Node 5 | * Responding to requests 6 | * Response object 7 | * Content types 8 | * Working with the filesystem 9 | * Reading files 10 | * Sending files in response objects 11 | * Caching 12 | * File watchers 13 | * A Modern Application Framework 14 | * Recap on previous lessons 15 | * Requirements for modern applications 16 | * Example of a modern app 17 | * Node.js enables concurrency 18 | * Socket.io: A Primer 19 | * Why use Socket.io? 20 | * Creating a connection between client and server 21 | * Sending data to the client -------------------------------------------------------------------------------- /sessions/hour2/02_working-with-filesystem/server_with_caching.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var fs = require('fs'); 3 | 4 | var loadedFiles = {}; 5 | 6 | function returnData(res, code, data) { 7 | res.writeHead(code, {'Content-Type': 'text/plain'}); 8 | res.end(data); 9 | } 10 | 11 | http.createServer(function (req, res) { 12 | if (loadedFiles[req.url]) 13 | return returnData(res, 200, "Cached: " + loadedFiles[req.url]); 14 | 15 | fs.readFile(__dirname + req.url, 'utf-8', function(err, data) { 16 | if (err) { 17 | returnData(res, 404, 'File ' + req.url + ' not found\n'); 18 | } 19 | else { 20 | loadedFiles[req.url] = data; 21 | returnData(res, 200, data); 22 | } 23 | }); 24 | }).listen(process.env.PORT || 80, "0.0.0.0"); -------------------------------------------------------------------------------- /event-description.md: -------------------------------------------------------------------------------- 1 | (From: http://nodejs.eventbrite.com/) 2 | 3 | Ready to learn Node.JS? 4 | 5 | Node Bootcamp is a free event (yep, free, thanks to the awesome Microsoft) for developers and designers who want to learn Node.JS from the ground up with hands-on instruction from Node experts at Microsoft and Cloud9. No prior Node experience is necessary to attend. 6 | 7 | At Node Bootcamp you.ll learn how to build your first Node application from scratch, how to work with popular Node.JS development tools and editors, how to work with popular 3rd party Node frameworks like Express and Socket.IO, and how to deploy your applications to production hosting environments. 8 | 9 | We only have capacity for 50 people and we expect this event to fill up fast. Please only RSVP if you know you will be able to attend. 10 | 11 | Breakfast, lunch, and snacks will be provided. -------------------------------------------------------------------------------- /sessions/hour1/05_creating-node-projects/src/server.js: -------------------------------------------------------------------------------- 1 | /* Basic HTTP-based "Hello World" application for Node.JS */ 2 | 3 | var http = require("http"); //Import the built-in HTTP module 4 | 5 | console.log('Starting server...'); 6 | 7 | var port = process.env.PORT || 3000; //Use a port provided by the process or default over to port 3000 8 | 9 | var server = http.createServer(function(req, res){ 10 | console.log('Receiving request'); 11 | 12 | // Set the HTTP header to 200-OK 13 | // and let the browser know to expect content with MIME type text/plain 14 | res.writeHead(200, {'Content-Type':'text/plain'}); 15 | 16 | //Send the text "Hello World!" back to the client 17 | res.end('Hello World!'); 18 | 19 | console.log('Response written to stream') 20 | }).listen(port); //Listen on a port assigned by the server 21 | 22 | console.log("Server listening on port " + port); -------------------------------------------------------------------------------- /sessions/hour1/06_working-with-packages/npm-packages/server.js: -------------------------------------------------------------------------------- 1 | /* Basic HTTP server that uses a third-party NPM package */ 2 | 3 | var http = require("http") //Import the built-in HTTP module 4 | , slang = require("slang"); /* use npm install in the root/npm-packages directory to install slang */ 5 | 6 | console.log('Starting server...'); 7 | 8 | var port = process.env.PORT || 3000; 9 | 10 | var server = http.createServer(function(req, res){ 11 | console.log('Receiving request'); 12 | 13 | // Set the HTTP header to 200-OK 14 | // and let the browser know to expect content with MIME type text/plain 15 | res.writeHead(200, {'Content-Type':'text/plain'}); 16 | 17 | //Uses slang module to convert the output to read "hello world" in all lower caps 18 | res.end(slang.uncapitalizeWords('Hello World!')); 19 | 20 | console.log('Response written to stream') 21 | }).listen(port); //Listen on a port assigned by the server 22 | 23 | console.log("Server listening on port " + port); -------------------------------------------------------------------------------- /sessions/hour1/06_working-with-packages/user-defined-packages/server.js: -------------------------------------------------------------------------------- 1 | /* Basic HTTP server that just returns up the current time as a UNIX timestamp */ 2 | var http = require("http"); //Import the built-in HTTP module 3 | //, timestamp = require("./helpers/timestamp"); //Import our user-defined timestamp module 4 | 5 | console.log('Starting server...'); 6 | 7 | var port = process.env.PORT || 3000; 8 | 9 | var server = http.createServer(function(req, res){ 10 | var currentTime = require("./helpers/timestamp").currentTime(); 11 | 12 | console.log('Receiving request [%s]', currentTime); 13 | 14 | // Set the HTTP header to 200-OK 15 | // and let the browser know to expect content with MIME type text/plain 16 | res.writeHead(200, {'Content-Type':'text/plain'}); 17 | res.end(currentTime) 18 | console.log('Response written to stream [%s]', currentTime) 19 | }).listen(port); //Listen on a port provided by the system or port 3000 20 | 21 | console.log("Server listening on port " + port); -------------------------------------------------------------------------------- /sessions/hour2/02_working-with-filesystem/server_with_watchers.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var fs = require('fs'); 3 | 4 | var loadedFiles = {}; 5 | 6 | function returnData(res, code, data) { 7 | res.writeHead(code, {'Content-Type': 'text/plain'}); 8 | res.end(data); 9 | } 10 | 11 | http.createServer(function (req, res) { 12 | var fullPath = __dirname + req.url; 13 | 14 | if (loadedFiles[fullPath]) 15 | return returnData(res, 200, "Cached: " + loadedFiles[fullPath]); 16 | 17 | fs.readFile(fullPath, 'utf-8', function(err, data) { 18 | if (err) { 19 | returnData(res, 404, 'File ' + req.url + ' not found\n'); 20 | } 21 | else { 22 | // Start watching the file for changes 23 | fs.watchFile(fullPath, function(curr, prev) { 24 | // We only want to know when the "modified" time was updated 25 | if (curr.mtime.getTime() != prev.mtime.getTime()) { 26 | console.log("Cached file [" + fullPath + "] was updated"); 27 | 28 | // Read in the file again 29 | fs.readFile(fullPath, 'utf-8', function(err, data) { 30 | if (!err) 31 | loadedFiles[fullPath] = data; 32 | }); 33 | } 34 | }); 35 | 36 | loadedFiles[fullPath] = data; 37 | returnData(res, 200, data); 38 | } 39 | }); 40 | }).listen(process.env.PORT || 80, "0.0.0.0"); -------------------------------------------------------------------------------- /sessions/hour3/05_working_with_cookies/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var Cookies = require('cookies'); 3 | var url = require('url'); 4 | 5 | http.createServer(function(req, res) { 6 | var query = url.parse(req.url, true).query; 7 | var cookies = new Cookies(req, res); 8 | 9 | if (query["forget"]) { 10 | var expireDate = new Date(1900, 1, 1); 11 | cookies.set('thing_to_remember', '', { 'expires': expireDate }); 12 | res.writeHeader(302, {'Location':'/'}); 13 | res.end(); 14 | return; 15 | } 16 | 17 | if (query["remember"]) { 18 | cookies.set('thing_to_remember', query["remember"]); 19 | res.writeHeader(302, {'Location':'/'}); 20 | res.end(); 21 | return; 22 | } 23 | 24 | var message = ''; 25 | if (cookies.get('thing_to_remember')) { 26 | message = 'Hello again! You asked me to remember: ' + cookies.get('thing_to_remember'); 27 | } 28 | else { 29 | message = 'You have not asked me to remember anything yet. What shall I remember?'; 30 | } 31 | 32 | res.writeHead(200, {'Content-Type': 'text/html'}); 33 | res.end('' + 34 | message + 35 | '
' + 36 | '' + 37 | '' + 38 | '' + 39 | '
'); 40 | 41 | }).listen(process.env.PORT); 42 | -------------------------------------------------------------------------------- /sessions/hour3/02_primer-to-Windows-Azure-Table-Storage/readme.md: -------------------------------------------------------------------------------- 1 | #Primer to Windows Azure Table Storage 2 | ##Windows Azure Storage 3 | Apart from SQL Azure, there are three storage options in Windows Azure Storage: 4 | 5 | - blobs (pictures, video, audio, etc.) 6 | 7 | - queues (web role-worker role messaging, etc.) 8 | 9 | - tables 10 | 11 | Note: All are replicated three times 12 | 13 | ##Windows Azure Table Storage 14 | We'll focus on tables, but in general, you want to choose to use tables when... 15 | 16 | - if data > 50GB on single instance 17 | 18 | - if you don't need relational store 19 | 20 | Basic facts about tables 21 | 22 | - Accessed via a Uri like 'http://myaccount.table.core.windows.net' 23 | 24 | - Max size 100TB 25 | 26 | When naming a table, the name... 27 | 28 | - can only contain alphanumeric char 29 | 30 | - cannot begin with numeric character 31 | 32 | - case-sensitive 33 | 34 | - between 3 to 63 characters long 35 | 36 | 37 | ##Understanding tables... 38 | 39 | - don't associate tables in SQL with 'table' in Windows Azure Table Storage 40 | 41 | - think of 'table' as a object storage, 42 | 43 | - tables are really a simple hierarchy of entities: 44 | - Account 45 | - Table 46 | - Entity (aka row) 47 | - Columns (aka values) 48 | 49 | ###Entities 50 | - Tables store data as collection of entities 51 | 52 | - Entities are like rows, but BUT each row can contain different number of columns or properties 53 | 54 | 55 | ###Properties 56 | - Properties are name-value pairs 57 | 58 | - Each entity has a primary key + set of up to 255 properties 59 | 60 | - inclusive of three system properties: 61 | - PartitionKey 62 | - RowKey 63 | - Timestamp 64 | 65 | - Naming a property: up to 255 char- 66 | 67 | - conform to subset of data types defined in ADO.NET Data Service 68 | - byte, bool, DateTime, double, Guid, Int32 (int), Int64 (long), string -------------------------------------------------------------------------------- /sessions/hour2/04_socket-io-a-primer/server.js: -------------------------------------------------------------------------------- 1 | // From 'https://github.com/mmukhin/psitsmike_example_1' 2 | var app = require('express').createServer(); 3 | var io = require('socket.io').listen(app); 4 | 5 | app.listen(process.env.PORT || 8080); 6 | 7 | // routing 8 | app.get('/', function (req, res) { 9 | res.sendfile(__dirname + '/index.html'); 10 | }); 11 | 12 | // usernames which are currently connected to the chat 13 | var usernames = {}; 14 | 15 | io.sockets.on('connection', function (socket) { 16 | 17 | // when the client emits 'sendchat', this listens and executes 18 | socket.on('sendchat', function (data) { 19 | // we tell the client to execute 'updatechat' with 2 parameters 20 | io.sockets.emit('updatechat', socket.username, data); 21 | }); 22 | 23 | // when the client emits 'adduser', this listens and executes 24 | socket.on('adduser', function(username){ 25 | // we store the username in the socket session for this client 26 | socket.username = username; 27 | // add the client's username to the global list 28 | usernames[username] = username; 29 | // echo to client they've connected 30 | socket.emit('updatechat', 'SERVER', 'you have connected'); 31 | // echo globally (all clients) that a person has connected 32 | socket.broadcast.emit('updatechat', 'SERVER', username + ' has connected'); 33 | // update the list of users in chat, client-side 34 | io.sockets.emit('updateusers', usernames); 35 | }); 36 | 37 | // when the user disconnects.. perform this 38 | socket.on('disconnect', function(){ 39 | // remove the username from global usernames list 40 | delete usernames[socket.username]; 41 | // update list of users in chat, client-side 42 | io.sockets.emit('updateusers', usernames); 43 | // echo globally that this client has left 44 | socket.broadcast.emit('updatechat', 'SERVER', socket.username + ' has disconnected'); 45 | }); 46 | }); -------------------------------------------------------------------------------- /sessions/hour0/README.md: -------------------------------------------------------------------------------- 1 | Node.JS vs Other Languages 2 | ======================== 3 | 4 | Most developers initially struggle with the asynchronous nature of Node.JS, 5 | ecause we're all trained to think like a procedural programmer where we expect a 6 | function to return a result to us before we move onto the next block of code our 7 | program needs to execute. 8 | 9 | Node.JS is fundamentally different in this model, and we'd like to illustrate it 10 | by way of example using the same program written in a few common programming 11 | languages that are found in a lot of moden web development stacks: 12 | 13 | ````PHP 14 | 15 | 26 | ```` 27 | 28 | ````PYTHON 29 | 30 | """ PYTHON CODE FOR READING A SIMPLE FILE TO CONSOLE """ 31 | import os 32 | 33 | filePath = os.path.join(os.getcwd(), "README.md") 34 | 35 | if not (os.path.exists(filePath)): 36 | print "Could not read file %s" % filePath 37 | else: 38 | print open(filePath, "r").readlines() 39 | 40 | ```` 41 | ````JS 42 | /** NODE.JS **/ 43 | var fs = require("fs"); 44 | var filePath = __dirname + "/README.md"; 45 | fs.readFile(filePath, "utf-8", function(err, data) { 46 | if (err) 47 | return console.log("Could not read file " + filePath); 48 | console.log(data); 49 | }); 50 | 51 | ```` 52 | 53 | So what's so different about Node.JS? 54 | -------------------------------------------- 55 | Notice how we have the results of our fs.readFile command wrapped in an anonymous 56 | function and the rest of the business logic conditionally writing to console is 57 | contained therein? 58 | 59 | This is because *reading to the fileSystem is a non-blocking 60 | asynchronous operation in Node.JS* - the next block of code immediately after that 61 | fs.readFile call would be executed by the Node.JS runtime before the contents of the 62 | file were received by the program. 63 | 64 | So instead of doing a standard procedural call, we wrap everything into a callback 65 | that gets executed by the server once the read operation is finished. -------------------------------------------------------------------------------- /sessions/hour2/04_socket-io-a-primer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 47 |
48 | USERS 49 |
50 |
51 |
52 |
53 | 54 | 55 |
56 | -------------------------------------------------------------------------------- /sessions/hour1/03_javascript-and-node/readme.md: -------------------------------------------------------------------------------- 1 | "Javascript and Node" 2 | ===================== 3 | 4 | A very brief history of JavaScript: 5 | ----------------------------------- 6 | 7 | JavaScript was originally developed by Brendan Eich at Netscape for Netscape Navigator in 1995. 8 | Initially, most people used JavaScript for trivial things: button mouse-overs, pop-ups, menus, etc. 9 | During this period of time, many people dismissed JavaScript as a "toy" language. 10 | 11 | Starting in about 2000, people started to use JavaScript to load additional data onto a webpage, 12 | a technique which Jesse James Garrett named "Ajax" in 2005. 13 | It's around this time that people start to take JavaScript more seriously. 14 | 15 | Then, in 2008, Google releases their Chrome web browser, which is praised for its impressive JavaScript performance. 16 | This is the start of a sort of "arms race" between the major browsers to improve the performace of JavaScript. 17 | Chrome gets the V8 JavaScript Engine, Webkit gets Squirrelfish, Firefox gets TraceMonkey, Internet Explorer gets Chakra, and so on. 18 | 19 | 20 | Okay, why do I care? 21 | -------------------- 22 | 23 | It's important to know that these days, 24 | JavaScript is a powerful and performant language that people use to power large websites. 25 | Since this hasn't always been the case, 26 | expect to encounter people who have misgivings about JavaScript based on poor experices they may have had with JavaScript many years ago. 27 | 28 | Knowing this little bit of history will also help you understand what V8, namely, Chrome's JavaScript engine. 29 | V8 is also the code that powers the core of Node. 30 | 31 | What is Node? 32 | ------------- 33 | 34 | Given what you've learned so far, this definion should make sense: 35 | Node is the V8 JavaScript engine, running on a server with lots of extra code to enable server side JavaScript. 36 | 37 | If you're wondering what we mean by "extra code to enable server side JavaScript", 38 | here are some examples of code that Node adds to JavaScript: 39 | * The "net" module, "an asynchronous network wrapper". 40 | * The "Buffer" module which is for dealing with binary data. 41 | * The "http" module whis is for creating an HTTP server or client. 42 | * The "os" module which is for getting information about the host. 43 | 44 | 45 | See also: 46 | ========= 47 | 48 | * http://en.wikipedia.org/wiki/JavaScript 49 | * http://en.wikipedia.org/wiki/JavaScript_engine 50 | * http://nodejs.org/docs/latest/api/all.html 51 | * https://github.com/joyent/node/tree/master/lib -------------------------------------------------------------------------------- /sessions/hour2/01_responding-to-requests/readme.md: -------------------------------------------------------------------------------- 1 | Responding to HTTP Requests 2 | =========================== 3 | 4 | In the previous lesson we briefly introduced HTTP servers and left off with a 5 | tantalizingly hobbled example: 6 | 7 | ```javascript 8 | var http = require('http'); 9 | var server = http.createServer(); 10 | server.listen(process.env.PORT || 80, "0.0.0.0"); 11 | ``` 12 | 13 | This server is primed for a career in therapy: it listens well, but it doesn't 14 | respond to our requests. So let's give it better ears and a voice: the request 15 | callback and response object. 16 | 17 | ```javascript 18 | var http = require('http'); 19 | 20 | function requestCallback(req, res) { 21 | 22 | } 23 | 24 | var server = http.createServer(requestCallback); 25 | server.listen(process.env.PORT || 80, "0.0.0.0"); 26 | ``` 27 | 28 | What we've added is a function that gets called every time the server receives an 29 | HTTP request. That function is passed two parameters: `req` and `res`. As you 30 | might expect, `req` is an object with details about the request, while `res` 31 | gives us methods to respond to the request. Pretty simple so far. 32 | 33 | Let's take this a step further and respond to each request with "Hello World!" 34 | 35 | ```javascript 36 | var http = require('http'); 37 | 38 | function requestCallback(req, res) { 39 | res.writeHead(200, {'Content-Type': 'text/plain'}); 40 | res.write('Hello World!'); 41 | res.end(); 42 | } 43 | 44 | var server = http.createServer(requestCallback); 45 | server.listen(process.env.PORT || 80, "0.0.0.0"); 46 | ``` 47 | 48 | The first line `res.writeHead` takes two arguments here. The first is the 49 | [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) 50 | (200 meaning "OK" e.g. "success") and the second an object 51 | of [headers](http://en.wikipedia.org/wiki/List_of_HTTP_header_fields) 52 | about the response. In short, the headers tell the requesting browser 53 | what kind of response to expect. In this case we are simply sending plain text. 54 | 55 | The second line `res.write` sends data to the client. 56 | 57 | Finally, `res.end()` signals to the server that all of the data has been sent and that 58 | the server should consider the message complete. `res.end()` **must** be called on 59 | each response. 60 | 61 | Take note that this request handler is fairly "dumb" - it doesn't investigate 62 | what types of requests the user is making. It only knows the user is accessing 63 | the server. For example, try accessing `http://localhost:80/sub/path/access.txt` 64 | and you will still get back "Hello World!" 65 | 66 | Loading files and setting the proper response headers will be the subject of 67 | the next lesson. -------------------------------------------------------------------------------- /sessions/hour1/05_creating-node-projects/readme.md: -------------------------------------------------------------------------------- 1 | Creating Node Projects and How They Look on the Filesystem 2 | -------- 3 | The point of this exercise is to understand what Node.JS projects look like on the filesystem and what the best practices are for utilizing files and folders when working with Node. 4 | 5 | #### Node Projects on the Filesystem 6 | 7 | Simply stated, most production Node applications follow a project structure that looks something like this, where [root] is the name of your project: 8 | 9 | c:\[root]\ 10 | c:\[root]\package.json 11 | c:\[root]\readme.text 12 | c:\[root]\.gitignore 13 | c:\[root]\src 14 | c:\[root]\src\server.js 15 | c:\[root]\src\helpers 16 | c:\[root]\src\helpers\timestamp.js 17 | c:\[root]\test 18 | c:\[root]\test\basic-tests.js 19 | c:\[root]\node_modules 20 | 21 | The root folder contains the following files and directories, as explained 22 | 23 | * The __package.json__ file, which includes dependencies used both for production and development. 24 | * A __"readme"__ file which explains what the project does - you don't necessarily need one to run your application but it's a good habit to get into. 25 | * A __.gitignore__ file - this file tells Git, the source control engine of choice for most Node developers, which files need should not be included your Node projects. Here's what [Github recommends you include in your .gitignore files for Node](https://github.com/github/gitignore/blob/master/Node.gitignore) 26 | * A __/src__ directory, which contains all of the source code necessary to power your application, including sub-folders like __/src/helpers__. 27 | * A __/test__ directory, which includes any unit tests you might use to test and verify the correctness of your code. We won't delve much into unit testing at Node Bootcamp, but writing unit tests is considered to be a must-have if you're going to develop production Node applications. 28 | * And finally, there's the __node_modules__ folder which is used by npm to install any third-party packages your application depends on, as specified in __package.json__. 29 | 30 | #### Why It's Done this Way 31 | There are a lot of different ways you can structure a Node project, but here are the reasons why this is considered to be one of the better ways of doing it: 32 | 33 | * By keeping all of your source files and unit tests in seperate sub-directories of the same project, you can cover both areas with the same package.json. 34 | * It cleanly seperates unit tests from your source control and makes it easier to manage both independently. 35 | * Generally it makes easier for someone new to grok your codebase without having to come through everything. 36 | 37 | You can, of course, structure your projects any way you want but this is a good pattern to follow, generally speaking. -------------------------------------------------------------------------------- /sessions/hour3/03_using-a-data-model-with-Azure-tables/readme.md: -------------------------------------------------------------------------------- 1 | Using a data model with Azure Table Storage 2 | -------- 3 | 4 | The Wine "Node"-book example works fine with one type of entity (a wine) and a few operations. However, things will get substantially more complicated if we add more entity types, more operations, and CSS styles. As a step towards the MVC pattern, let's refactor our code and create a prototype for a wine to use as a data model. 5 | 6 | `models/wine.js` contains a module for working with our wine entities. Since we're just refactoring the old example, I'll just highlight certain parts of the file. 7 | 8 | The constructor supplies our credentials to `createTableService()`: 9 | 10 | Wine = function () { 11 | this.tableClient = azure.createTableService(account, accountKey, tableHost); 12 | } 13 | 14 | The constructor is exported in the last line of the file. 15 | 16 | In JavaScript, one can add methods to an object by adding them to the prototype. For example, here's how we define a `findAll` method to retrieve all entities from a table: 17 | 18 | Wine.prototype.findAll = function (entitiesQueriedCallback) { 19 | var tableQuery = TableQuery.select() 20 | .from(tableName); 21 | this.tableClient.queryEntities(tableQuery, entitiesQueriedCallback); 22 | }; 23 | 24 | Note that we still call `entitiesQueriedCallback` as we did in the previous example. 25 | 26 | The code for `findSingleEntity`, `destroy`, and `save` should be self-explanatory since you've already seen it in the previous example. We had to use `destroy` because `delete` is a reserved JavaScript keyword. 27 | 28 | The `init` method creates the table if it doesn't exist, and then adds sample data using a batch transaction. Batching up transactions is an efficient way to talk to Azure Table Storage. In this example, three wines are added in one batch transaction rather than three separate operations. Note that all entities in a batch transaction must have the same `PartitionKey`. 29 | 30 | Now take a look at `winenotebook_with_model.js`. We've added this line to use the module we just created: 31 | 32 | var Wine = require('./models/wine').Wine; 33 | 34 | We no longer need the call to `createTableService()` or the table name, since that's handled by our module. We also don't need to create the table since our `init` method does this. Inside `createServer()`, note that we no longer use `insertEntity()` or `deleteEntity()` and instead make calls to `wines.save()` and `wines.destroy()`. Lastly, the call to `queryEntities()` has been replaced by `wines.findAll()`. 35 | 36 | Not only has our code been simplified, but our module can now be easily reused by other applications. Simply import the module with `require()` and invoke the constructor with `new`. Methods attached to the entity's prototype will then become available to your application. 37 | 38 | -------------------------------------------------------------------------------- /sessions/hour3/03_using-a-data-model-with-Azure-tables/models/wine.js: -------------------------------------------------------------------------------- 1 | // A module for working with wine entities. 2 | 3 | var azure = require('azure'); 4 | var uuid = require('node-uuid'); 5 | 6 | var TableQuery = azure.TableQuery; 7 | var tableName = 'wines'; // Name your table here. 8 | 9 | var account = azure.ServiceClient.DEVSTORE_STORAGE_ACCOUNT; 10 | var accountKey = azure.ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY; 11 | var tableHost = azure.ServiceClient.DEVSTORE_TABLE_HOST; 12 | 13 | //if (process.env.C9_PORT) { // Test if we're running on Cloud9. Change these to your own credentials. 14 | account = 'azurelognodes'; 15 | accountKey = '1ZgPin+5JNPkFjIdJ1v5KkptB3jjO9I9DZh4k8KJcvr1FHM9KYOaDdq5BtKGrRblkJTuXFja5qVflFekGuzUkQ=='; 16 | tableHost = 'table.core.windows.net'; 17 | //} 18 | 19 | Wine = function () { 20 | this.tableClient = azure.createTableService(account, accountKey, tableHost); 21 | } 22 | 23 | Wine.prototype.findAll = function (entitiesQueriedCallback) { 24 | var tableQuery = TableQuery.select() 25 | .from(tableName); 26 | this.tableClient.queryEntities(tableQuery, entitiesQueriedCallback); 27 | }; 28 | 29 | Wine.prototype.findSingleEntity = function (wine, callback) { 30 | this.tableClient.queryEntity(tableName, wine.PartitionKey, wine.RowKey, callback); 31 | }; 32 | 33 | Wine.prototype.destroy = function (wine, entityDeletedCallback) { 34 | this.tableClient.deleteEntity(tableName, wine, entityDeletedCallback); 35 | }; 36 | 37 | Wine.prototype.save = function (wine, entityInsertedCallback) { 38 | this.tableClient.insertEntity(tableName, wine, entityInsertedCallback); 39 | }; 40 | 41 | Wine.prototype.init = function () { 42 | // Puts sample entities into the table. Note that for batch operations, 43 | // the PartitionKey must be the same for all entities. 44 | var provider = this; 45 | this.tableClient.createTableIfNotExists(tableName, function (err, created) { 46 | if (created) { 47 | provider.tableClient.beginBatch(); 48 | var now = new Date().toString(); 49 | provider.tableClient.insertEntity(tableName, { PartitionKey: 'White', RowKey: uuid(), Winery: 'Azure Vineyards', Variety: 'Chardonnay', Vintage: '2003', Notes: 'Buttery and smooth.', TastedOn: now }); 50 | provider.tableClient.insertEntity(tableName, { PartitionKey: 'White', RowKey: uuid(), Winery: 'Node Estates', Variety: 'Pinot Grigio', Vintage: '2008', Notes: 'Delicious.', TastedOn: now }); 51 | provider.tableClient.insertEntity(tableName, { PartitionKey: 'White', RowKey: uuid(), Winery: 'Chateau C9', Variety: 'Sauvignon Blanc', Vintage: '2009', Notes: 'Hints of apricot.', TastedOn: now }); 52 | provider.tableClient.commitBatch(function () { 53 | console.log('Initialized table "' + tableName + '" with sample data.'); 54 | }); 55 | } 56 | }); 57 | }; 58 | 59 | exports.Wine = Wine; 60 | -------------------------------------------------------------------------------- /sessions/hour2/00_node-and-http/readme.md: -------------------------------------------------------------------------------- 1 | Node and HTTP 2 | ============= 3 | 4 | One of the most enticing reasons to use Node is for its high-level HTTP server 5 | library. In fact, starting an HTTP server is as easy as writing just a few 6 | lines of code: 7 | 8 | ```javascript 9 | var http = require('http'); 10 | var server = http.createServer(); 11 | server.listen(80, "0.0.0.0"); 12 | ``` 13 | 14 | You can request access to this server from your browser, but nothing will happen. 15 | Of course _doing_ something with those requests will be covered later in depth, 16 | but first a brief discourse on ports. 17 | 18 | #### Ports 19 | 20 | If you run the above code on your local machine and port 80 is unused, it will 21 | start without issue. If you start the process twice, you will get an error: 22 | 23 | ``` 24 | Error: EACCES, Permission denied 25 | ``` 26 | 27 | ...along with a stack trace indicating where the problem originated. Although 28 | the error is slightly ambiguous, it is thrown here because the port number is 29 | already in use. Typically with a simple application this is not an issue; you 30 | will only run one process with one entry point. 31 | 32 | In a distributed application or on a hosted environment this gets a little 33 | more complex. Since a distributed architecture is outside the scope of this 34 | introduction, let's focus on a shared hosting platform a la Cloud9 IDE. 35 | 36 | ### Ports in a Shared Environment 37 | 38 | When you start a run or debug process on Cloud9, a node process is spawned 39 | in your name. This process is unique and discrete, and operates in a safe 40 | harbor. This means it has restrictions, most of which are hidden to the 41 | developer. 42 | 43 | With all these processes operating in a shared environment and applications 44 | starting up HTTP servers, the potential for port collisions is implicit. Thus, 45 | Cloud9 uses an alternative method for port designation: `process.env.PORT`. 46 | 47 | Where before the HTTP server was started on port 80, the code now becomes: 48 | 49 | ```javascript 50 | var http = require('http'); 51 | var server = http.createServer(); 52 | server.listen(process.env.PORT || 80, "0.0.0.0"); 53 | ``` 54 | 55 | What's going on here? Cloud9 keeps an internal list of available ports on the 56 | server. When a process is spawned, it is passed the `process.env` object 57 | and then sets the `process.env.PORT` variable to an available port. Your 58 | spawned process is then mapped to that port. 59 | 60 | When access is requested to projectname.username.c9.io, Cloud9 looks at the map 61 | from project/process to the port number and proxies the connection automatically. 62 | Neat huh? 63 | 64 | What's great about the `process.env.PORT || 80` setup is Node will "ignore" 65 | `process.env.PORT` if it is not set. This ensures, for example, that if your 66 | application is hosted at "mygreatnodeapp.com" then users accessing it from a 67 | browser (where the default connection port is 80) will access your server as 68 | intended. -------------------------------------------------------------------------------- /sessions/hour1/02_event-loop-explained/readme.md: -------------------------------------------------------------------------------- 1 | Explaining the Node.JS Event Loop 2 | -------- 3 | 4 | Node is an asynchronous distributed programming platform built on top of [Chrome’s V8 JavaScript Engine](http://code.google.com/p/v8/), the same engine used to parse and execute client-side JavaScript inside Chrome. Node is actually server-side JavaScript, but its syntax and prose will be familiar to any web developer that has written in JavaScript before. 5 | 6 | While many developers are excited at the prospect of server-side JavaScript, Node’s true innovation is its evented + asynchronous I/O model. 7 | 8 | ```JavaScript 9 | var http = require('http'); 10 | http.createServer(function (req, res) { 11 | res.writeHead(200, {'Content-Type': 'text/plain'}); 12 | res.end('Hello World!'); 13 | }).listen(1337, "127.0.0.1"); 14 | ``` 15 | 16 | The primary method of any Node application runs a single-threaded continuous event loop - the *.listen* method of the HTTP server in this instance. This loop listens for events raised by the operating system whenever a HTTP request is received on the specified port, 1337 in this example, and the event loop immediately hands the event off for processing to a request handler funciton which executes on a [green thread](http://en.wikipedia.org/wiki/Green_threads). 17 | 18 | ![The Node Event Loop](http://www.aaronstannard.com/image.axd?picture=nodejs%20for%20dotnet.png "The Node Event Loop") 19 | 20 | Once this hand-off is complete, the event loop goes back to sleep until it's either called back by a worker function with the response from a HTTP request that finished or a new HTTP event is raised by the operating system. 21 | 22 | Any additional I/O (reads / writes to a remote database or local filesystem, etc..) performed by the Node application is non-blocking; a new function is passed to a green thread which will callback the calling thread when the I/O operation is complete. 23 | 24 | #### Advantages to the Node Event Model 25 | 26 | There are three major advantages to this model: 27 | 28 | 1. The main event loop uses a single thread and small allocation of memory on the heap to handle multiple concurrent connections, which makes the overhead of Node.JS grow relatively slowly as the number of requests it has to serve increases as there’s no operating system thread / process per-request initialization and context-switching overhead; 29 | 2. All long-running tasks (network I/O, data access, etc…) are always executed asynchronously on top of worker threads which return the results via callback to the event loop thread, which forces most Node applications to confirm to solid asynchronous development practices by default; and 30 | 3. JavaScript’s language features (functions as objects, closures, etc…) and Node’s programming model make this type of asynchronous / concurrent programming much easier to utilize – there’s no thread management, no synchronization mechanisms, and no message-passing nonsense. This eliminates a lot of pitfalls that most developers fall into when attempting to develop concurrent applications. -------------------------------------------------------------------------------- /sessions/hour1/01_node-explained/readme.md: -------------------------------------------------------------------------------- 1 | What Is Node and How Does It Work? 2 | -------- 3 | 4 | Simply explained, Node.JS is a JavaScript-powered framework for building network applications like web apps, chat servers, real-time games, and so forth. 5 | 6 | There are three aspects to Node that make it really interesting to developers of all levels of experience: 7 | 8 | 1. JavaScript has a long history of mainstream adoption on the client-side of web development, but Node offers a path for developers with JavaScript experience to carry those skills over to server-side development. 9 | 10 | 2. Node's evented model for handling incoming HTTP requests offers an interesting alternative to the traditional thread-per-request models that most web developers have grown accustomed to, and it offers some sizeable performance advantages particularly in scenarios where applications have to service a large volume of concurrent requests. 11 | 12 | 3. Node's non-blocking approach to I/O, which uses common JavaScript idioms like events and callbacks, gives Node one of the most simple, elegant, and effective methodologies for encouring good asynchronous programming habits. Node's taken a lot of nasty problems that used to go into concurrent programming and left developers with a relatively straight-forward model for leveraging the power of concurrent programming. 13 | 14 | 15 | #### How Does Node Work? 16 | 17 | We will cover the details of how the Node event loop works a later in this session, but here's the gist of it: 18 | 19 | * Node runs in a system process and listens for HTTP requests on a port specified by the developer; 20 | * Whenever a HTTP request is received the OS wakes up the Node application's event-loop thread, which decides what to do with the request; 21 | * Any subsequent I/O performed in the process of handling the request, like connecting to a database server or reading a file from disk, is done asynchronously on a worker thread; and 22 | * When the work is finished, the worker calls back the main event loop thread, which returns a completed response object to the original request. 23 | 24 | #### What Powers Node? 25 | 26 | Node can be broken down into three core technologies: 27 | 28 | * The foundation of Node is [Chrome's JavaScript runtime](http://code.google.com/p/v8/), known as the "V8 JavaScript Engine" or just "V8". V8 is the same engine used by Chrome to execute JavaScript on any page you visit while browsing, and it's what allows Node developers to use JavaScript at all in the first place. 29 | 30 | * The next major component of Node is the [CommonJS](http://www.commonjs.org/) module system - it's a standard which allows developers to define modules that can be reused and shared throughout their own applications and others. Without CommonJS' standards we wouldn't have systems like [npm](http://www.npmjs.org/) (Node Package Manager) which allow Node developers to share reusable packages with eachother and provide a standard for handling dependencies in our Node applications. 31 | 32 | * The final major component of Node is a powerful network I/O library developed by [Joyent](http://www.joyent.com/) called [libuv](https://github.com/joyent/libuv) - this is what handles all of the incoming network connections on both Windows and POSIX-based systems like Linux and OS X. 33 | -------------------------------------------------------------------------------- /sessions/hour2/03_a-modern-app-framework/readme.md: -------------------------------------------------------------------------------- 1 | A Modern Application Framework 2 | ============================== 3 | 4 | At this point in the lesson plan, it is essential to take what you've learned 5 | thus far and apply a bit of conceptual thinking to modern web development. 6 | First, let's recap. 7 | 8 | In the previous lessons we discussed the Node programming model; the event loop; 9 | JavaScript in general; Node modules; loading files; and serving requests through a 10 | dead-simple HTTP server implementation. 11 | 12 | This last discussion was useful to demonstrate the ease of implementing HTTP servers 13 | in Node. But let's be honest: HTTP servers are nothing new. Many web developers 14 | are intimately familiar with the LAMP stack. They already know how to serve files. 15 | They already know how to route requests to subdomains. In fact, if the _only 16 | thing_ your application does is read from a database and send that information 17 | back to the client, then PHP may still a better choice than Node.JS. 18 | 19 | So what has all this discussion been leading up to? Why is everyone so excited 20 | about Node.JS? 21 | 22 | ### The Modern Web 23 | 24 | To demonstrate why Node.JS is becoming the platform of choice for modern web 25 | applications, let's take an example application and explain why Node.JS was the 26 | best choice for building its back-end. 27 | 28 | First, some requirements for the app: 29 | 30 | * Enables bi-directional communication between user and server 31 | * Integrates with a number of external services & processes 32 | * Maintains state 33 | * Concurrently manages thousands of requests from thousands of users 34 | * Requests cannot block other users' requests 35 | 36 | Many modern applications fit this bill, but one of the most powerful examples is 37 | [Cloud9 IDE](http://c9.io). 38 | 39 | Some common operations a user engages in on Cloud9: 40 | 41 | * Cloning a project from GitHub: A child process starts to `git clone` and the 42 | user is notified immediately after it's finished (external service, bi-directional 43 | comm.) 44 | * Running a Node.JS project from the IDE (external process) 45 | * As the user is testing the running process, `console.log` outputs from the 46 | process are being sent back to the IDE's console as they happen (bi-directional 47 | comm.) 48 | * Deploying an application to Azure (external service) 49 | * User closes IDE, opens it an hour later and everything is where it left off 50 | (maintaining state) 51 | 52 | Meanwhile, _thousands of other users are making requests on the server_ and no 53 | one is slowed down or blocked by anyone else. How is this possible? 54 | 55 | ### Node Enables Concurrency 56 | 57 | The magical concept, the theme we have been hammering on is 58 | _Node.JS enables concurrency_. 59 | 60 | We have already been demonstrating this through discussions on the event loop 61 | and examples of callbacks. And as you continue to learn about Node you will see 62 | this concept demonstrated over and over again. 63 | 64 | The level of concurrent engagement a modern application demands are enabled by 65 | Node.JS: retrieving tabular data from MySQL is non-blocking; sending a request 66 | to GitHub is non-blocking; deploying to Azure is non-blocking; bi-directional 67 | communication is non-blocking and furthermore, enabled by a fantastic library 68 | called socket.io. 69 | 70 | And that is where we will continue with our next lesson. -------------------------------------------------------------------------------- /sessions/hour3/04_using-continuation-tokens-for-pagination/readme.md: -------------------------------------------------------------------------------- 1 | Using continuation tokens from Azure Table Storage for pagination 2 | -------- 3 | 4 | Imagine you have millions of entities in an Azure table, and you want to page through them displaying 20 entities at a time. Fetching all entities and then dividing them into groups of 20 is hardly the most efficient way to do this. In addition, at the time this document is being written, Azure limits the number of returned entities to 1000 in any given request. Among other things, this keeps developers from accidentally querying for millions of entities. 5 | 6 | Azure Table Storage supports continuation tokens to support paging through a large number of entities. You fetch a group entities, and the result contains a continuation token if there are more entities remaining to be fetched. The continuation token is like a bookmark which indicates where the query left off. For example, if you fetched entities 1-20, a continuation token is included to tell you to begin your next query at entity number 21. This is how you can efficiently page through a large number of entities. 7 | 8 | `table-storage-pagination-sample.js` illustrates how to do pagination in Node with Azure tables. It creates a sample database containing `totalEntities` number of entities. It then queries the results and displays `pageSize` entities per page. 9 | 10 | `createTableIfNotExists()` simply creates a table and populates it with sample data using a batch insertion. These can include at most 100 entities at a time, so the code loops through this operation until the number of entities in `totalEntities` has been created. Make `totalEntities` a multiple of 100 so that `totalEntities / 100` is an integer. This indicates how many batches of 100 insertions should be performed. 11 | 12 | In `http.createServer()` we ignore requests for `/favicon.ico` for the same of simplicity. 13 | 14 | Skip over the `if` block which handles requests to `/nextPage` for now. We'll come back to it. 15 | 16 | This line defines our initial query: 17 | 18 | var query = azure.TableQuery.select().from(tableName).top(pageSize); 19 | 20 | You've probably seen `select()` and `from()` before. `top()` limits the query to `pageSize` number of entities. 21 | 22 | In the parameter list for `entitiesQueriedCallback`, note that we now include `pageContinuation`. This object contains the continuation token. If you're curious, set a breakpoint or use `console.log()` to inspect `pageContinuation`. You'll see that it contains these properties: `tableService`, `tableQuery`, `nextPartitionKey`, and `nextRowKey`. 23 | 24 | `entitiesQueriedCallback` loops through the results and uses `counter` to keep track of how many total results have so far been returned. `counter` is initialized at the top of `http.createServer()` because it's also used by the `if` block which handles requests for `/nextPage`. 25 | 26 | If there are more entities yet to be retrieved, `pageContinuation.hasNextPage()` will return true. If that's the case, then we emit a link to `/nextPage` which includes `nextPartitionKey` and `nextRowKey` as query strings. 27 | 28 | In the `if` block which handles requests for `/nextPage`, we use the `querystring` module to extract `nextPartitionKey` and `nextRowKey` from the requested URL. We then create `nextPageQuery` which contains these keys and pass it to `queryEntities()`. 29 | 30 | When there are no more entities to be retrieved, `pageContinuation.hasNextPage()` returns false and we no longer display a link for the next page. 31 | -------------------------------------------------------------------------------- /sessions/hour1/06_working-with-packages/user-defined-packages/readme.md: -------------------------------------------------------------------------------- 1 | Writing and Including Your Own Modules 2 | -------- 3 | 4 | The code in this particular example uses a _user-defined_ timestamp module to 5 | send the current time back to the caller upon receiving an HTTP request. 6 | 7 | This simple Node.JS application doesn't depend on any external modules, but rather it uses 8 | the `module.exports` command to enable the user to include their own modules inside their `server.js` file. 9 | 10 | #### Creating your own modules 11 | The syntax for writing a module is pretty straightforward - take a look at some of the source from __helpers/timestamp.js__ 12 | pasted below: 13 | 14 | ```JavaScript 15 | function TimeStamp(){ 16 | return Math.round((new Date()).getTime() / 1000).toString(); //Returns a string 17 | } 18 | ``` 19 | When written like this, this is just a stand-alone function and we can't reference externally - we need to export 20 | this file as a module to make it available to other parts of our application, so we're going to use the 'module.exports' to 21 | make the contents of __helpers/timestamp.js__ available elsewhere in our app. 22 | 23 | ```JavaScript 24 | exports.currentTime = function(){ 25 | return Math.round((new Date()).getTime() / 1000).toString(); //Returns a string 26 | } 27 | ``` 28 | 29 | What we've done here is told the modules system to expose a method called `currentTime` to anyone who includes the 30 | module via the `requires` syntax. Let's show you what you need to do to actually use the module you just created. 31 | 32 | #### Using your own modules 33 | Here's what our code looks like for including the __helpers/timestamp.js__ in __server.js__: 34 | 35 | ```JavaScript 36 | var http = require("http") //Import the built-in HTTP module 37 | , timestamp = require("./helpers/timestamp"); //Import our user-defined timestamp module 38 | ``` 39 | 40 | What we did is pretty straightforward: `require` the timestamp module using a relative file path to 41 | __helpers/timestamp.js__ from __server.js__ and drop the .js file extension from the end of the path 42 | (the modules system will automatically look for .js files). 43 | 44 | From this point onward we can refer to all of the exposed methods of the 45 | module via the `timestamp.[METHOD NAME]` sytnax. 46 | 47 | So if we called the module in our code, it would look like this: 48 | 49 | ```JavaScript 50 | res.writeHead(200, {'Content-Type':'text/plain'}); 51 | res.end(timestamp.currentTime()) 52 | console.log('Response written to stream [%s]', timestamp.currentTime()) 53 | ``` 54 | 55 | Because we exposed the `currentTime` method explicitly via `exports.currentTime` in __helpers/timestamp.js__, 56 | we can now call that object as `timestamp.currentTime()` in any module where we `require` the timestamp.js file. 57 | 58 | #### What languages are modules written in? 59 | 60 | Modules are typically written in JavaScript. Some modules are written in C++. 61 | 62 | Supporting cross-platform C++ modules on both Unix and Windows is still a work in progress. 63 | 64 | #### Running the Sample Application 65 | 66 | This particular example serves plain text back in response to an HTTP request 67 | connection on port specified by the host process and simply returns an integer representing the current time via 68 | UNIX timestamp. 69 | 70 | To use this example, start the application: 71 | 72 | c:\introtonode\user-defined-modules> node server.js 73 | c:\introtonode\user-defined-modules> starting server on port {PORT} 74 | 75 | To test your application, simply visit http://127.0.0.1:{PORT} in your web browser or use curl if you have it: 76 | 77 | $ curl http://127.0.0.1:{PORT} 78 | $ 1327794195 -------------------------------------------------------------------------------- /sessions/hour3/00_saving-files/server.js: -------------------------------------------------------------------------------- 1 | var azure = require('azure'); 2 | var http = require('http'); 3 | var port = process.env.PORT; 4 | var blobService = azure.createBlobService('tacowan','XqikJtk/PMLO2FDiT1lvOwOCAFL8mWLhwU8a6Y2Ks1Mi+DKXghNhLIRM7hQffozJ92uKUAmuR1BSWhBLoKVkqw==','tacowan.blob.core.windows.net'); 5 | 6 | http.createServer(function(req, res) { 7 | 8 | var parts = req.url.split('/', 3); 9 | var containerName = parts[1]; 10 | var blobName = parts[2]; 11 | if (req.method == 'PUT') { 12 | if (parts.length < 3) { 13 | blobService.createContainerIfNotExists(containerName, { 14 | publicAccessLevel: 'blob'}, containerCreated); 15 | } 16 | else { 17 | var size = req.headers["content-length"]; 18 | blobService.createBlockBlobFromStream(containerName, blobName, req, size, blobCreated); 19 | } 20 | } 21 | else if (req.method == 'DELETE') { 22 | if (parts.length < 3) 23 | blobService.deleteContainer(containerName, containerDeleted); 24 | else 25 | blobService.deleteBlob(containerName, blobName, blobDeleted); 26 | } 27 | else if (req.method == 'GET') { 28 | if (containerName == "") { 29 | //list containers 30 | blobService.listContainers(function (error, containers) { 31 | if (error) { 32 | console.log(error); 33 | } else { 34 | containers.forEach(function (container) { 35 | res.write(container.name + '\r\n'); 36 | }); 37 | } 38 | res.end(); 39 | }); 40 | } else { 41 | // list blobs 42 | blobService.listBlobs(containerName, function (error, blobs) { 43 | if (error) { 44 | console.log(error); 45 | } else { 46 | blobs.forEach(function (blob) { 47 | res.write(blob.name + " (" + blob.url + ')\r\n'); 48 | }); 49 | } 50 | res.end(); 51 | }); 52 | } 53 | } 54 | 55 | function containerDeleted(error) { 56 | console.log(error); 57 | if (error === null) { 58 | res.writeHead(200, { 59 | 'Content-Type': 'text/plain' 60 | }); 61 | res.end("Successfully deleted container\r\n"); 62 | } 63 | else { 64 | console.log(error); 65 | res.end(error.message); 66 | } 67 | } 68 | 69 | function containerCreated(error) { 70 | if (error === null) { 71 | res.writeHead(200, { 72 | 'Content-Type': 'text/plain' 73 | }); 74 | res.end("Successfully created container\r\n"); 75 | } 76 | else { 77 | console.log(error); 78 | res.end(error.message); 79 | } 80 | 81 | } 82 | function blobCreated(error, serverBlob) { 83 | if (error === null) { 84 | res.writeHead(200, { 85 | 'Content-Type': 'text/plain' 86 | }); 87 | res.end("Successfully uploaded blob " + serverBlob.blob + '\r\n'); 88 | } 89 | else { 90 | console.log(error); 91 | res.end(error.message); 92 | } 93 | } 94 | 95 | function blobDeleted(error) { 96 | if (error === null) { 97 | res.writeHead(200, { 98 | 'Content-Type': 'text/plain' 99 | }); 100 | res.end("Successfully deleted blob\r\n"); 101 | } 102 | else { 103 | console.log(error); 104 | res.end(error.message); 105 | } 106 | } 107 | 108 | }).listen(port); -------------------------------------------------------------------------------- /sessions/hour3/04_using-continuation-tokens-for-pagination/table-storage-pagination-sample.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var url = require('url'); 3 | var querystring = require('querystring'); 4 | var azure = require('azure'); 5 | var uuid = require('node-uuid'); 6 | var port = process.env.PORT || 1337; // for C9, Azure, or when running locally. 7 | 8 | var account = azure.ServiceClient.DEVSTORE_STORAGE_ACCOUNT; 9 | var accountKey = azure.ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY; 10 | var tableHost = azure.ServiceClient.DEVSTORE_TABLE_HOST; 11 | 12 | if (process.env.C9_PORT) { // Test if we're running on Cloud9. Change these to your own credentials. 13 | account = 'c9demo'; 14 | accountKey = ''; 15 | tableHost = 'http://table.core.windows.net'; 16 | } 17 | 18 | var tableService = azure.createTableService(account, accountKey, tableHost); 19 | //tableService.logger = new azure.Logger(azure.Logger.LogLevels.DEBUG); // Uncomment this to enable logging 20 | var totalEntities = 100; // Number of entities to create. Make it a multiple of 100. 21 | var pageSize = 20; 22 | var tableName = 'largetable'; // Name your table here. 23 | 24 | // Create and populate the table. Note that for batch operations, 25 | // the PartitionKey must be the same for all entities. 26 | tableService.createTableIfNotExists(tableName, function (err, created) { 27 | if (created) { 28 | var numberOfBatches = parseInt(totalEntities / 100); 29 | for (var i = 0; i < numberOfBatches; i++) { 30 | tableService.beginBatch(); 31 | var now = new Date().toString(); 32 | for (var j = 0; j < 100; j++) { // Batches can include at most 100 entities. 33 | tableService.insertEntity(tableName, { PartitionKey: 'White', RowKey: (i * 100 + j + 1).toString(), 34 | Winery: 'Azure Vineyards', Variety: 'Chardonnay', Vintage: '2003', Notes: uuid(), TastedOn: now 35 | }); 36 | } 37 | tableService.commitBatch(function () { 38 | console.log('Initialized table "' + tableName + '" with 100 sample entities.'); 39 | }); 40 | } 41 | } 42 | }); 43 | 44 | http.createServer(function (req, res) { 45 | var counter = 0; 46 | if (req.url === '/favicon.ico') return; 47 | 48 | if (url.parse(req.url).pathname === '/nextPage') { 49 | var parsedQuerystring = querystring.parse(url.parse(req.url).query); 50 | var nextPartitionKey = parsedQuerystring.nextPartitionKey; 51 | var nextRowKey = parsedQuerystring.nextRowKey; 52 | var nextPageQuery = azure.TableQuery.select().from(tableName).top(pageSize).whereNextKeys(nextPartitionKey, nextRowKey); 53 | tableService.queryEntities(nextPageQuery, entitiesQueriedCallback); 54 | return; 55 | } 56 | 57 | var query = azure.TableQuery.select().from(tableName).top(pageSize); 58 | tableService.queryEntities(query, entitiesQueriedCallback); 59 | 60 | function entitiesQueriedCallback(error, serverEntities, pageContinuation) { 61 | res.writeHead(200, { 'Content-Type': 'text/html' }); 62 | if (error === null) { 63 | for (var index in serverEntities) { 64 | res.write('RowKey: ' + serverEntities[index].RowKey + ', Winery: ' + 65 | serverEntities[index].Winery + ', Notes: ' + serverEntities[index].Notes + '
'); 66 | counter++; 67 | } 68 | res.write('
Returned ' + counter + ' entities.
'); 69 | if (pageContinuation.hasNextPage()) { 70 | res.write('Next page'); 72 | } 73 | res.end(); 74 | } else { 75 | res.end('Could not query entities: ' + error.code); 76 | console.log('Could not query entities: ' + error.code); 77 | } 78 | } 79 | 80 | }).listen(port); 81 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Node Bootcamp 2 | 3 | Node Bootcamp is a series of free events aimed at helping new Node.JS developers get from 4 | no Node experience whatsoever to being able to produce their first basic Node applications. 5 | 6 | ### Event Format 7 | 8 | The general format for these events is this: 9 | 10 | 1. Spend the morning taking some hands-on training from Node.JS experts; 11 | 2. Spend the afternoon and early evening building your first Node.JS project; and finally 12 | 3. Have your projects submitted for judging and prizes. 13 | 14 | ### Lesson Plan 15 | 16 | Here's what you can expect to learn at Node Bootcamp: 17 | 18 | 1. __Hour 1: Introduction to Node.JS__ 19 | * "Hello World" from Azure and Cloud9 20 | * How to set up an Azure account 21 | * How to set up a Cloud9 account 22 | * How to configure Cloud9 to deploy to Azure 23 | * Deploy a "Hello World!" 24 | * What is Node and how does it work? 25 | * The Node Event Loop 26 | * JavaScript and Node 27 | * JavaScript primer 28 | * Creating Node projects 29 | * Hello World 30 | * What a Node project looks like in the filesystem 31 | * Node modules 32 | * Introduction to modules 33 | * Writing your own module 34 | * Using Node Package Manager (npm) 35 | * Package.json 36 | 37 | 2. __Hour 2: Node and Web Applications__ 38 | * HTTP and Node 39 | * Responding to requests 40 | * Response object 41 | * Content types 42 | * Working with the filesystem 43 | * Reading files 44 | * Sending files in response objects 45 | * Caching 46 | * File watchers 47 | * A Modern Application Framework 48 | * Recap on previous lessons 49 | * Requirements for modern applications 50 | * Example of a modern app 51 | * Node.js enables concurrency 52 | * Socket.io: A Primer 53 | * Why use Socket.io? 54 | * Creating a connection between client and server 55 | * Sending data to the client 56 | 57 | 3. __Hour 3: Building Real Web Applications with Node__ 58 | * Saving Files 59 | * Overview of Windows Azure Blob Storage 60 | * Saving files to Blob Storage 61 | * Getting files from Blob Storage 62 | * Working with a Database 63 | * Overview of Windows Azure Table Storage 64 | * Using Azure Table Storage in Node.JS 65 | * Azure Table Storage Entity Conventions 66 | * Creating a strongly-typed data model for Table Storage 67 | * Saving records 68 | * Looking up a specific key 69 | * Queries 70 | * Updating records 71 | * Working with Cookies 72 | * What are cookies? 73 | * Setting cookie values 74 | * Getting cookie values 75 | * Deleting cookies 76 | 77 | 78 | ### Tools 79 | Here are some of the tools and platforms we will be using throughout Node bootcamp: 80 | 81 | * [Cloud9 IDE](http://c9.io/ "Cloud9 IDE") 82 | * [Windows Azure](http://windows.azure.com/ "Windows Azure Console") 83 | * [Windows Azure SDK for Node.JS](https://github.com/WindowsAzure/azure-sdk-for-node "Windows Azure SDK for Node.JS") 84 | 85 | #### Schedule 86 | Here's what the schedule looks like: 87 | 88 | * 9:00am – Doors open 89 | * 9:30am – Hands-on training begins 90 | * 12:30pm – Training ends; Lunch 91 | * 1:00pm – Node bake-off begins (create your own project from scratch) 92 | * 5:00pm – Node bake-off ends; Judging of projects begins 93 | * 6:00pm - Judging is finished – prizes and other magical things 94 | 95 | 96 | ### Upcoming Events 97 | 98 | 1. April 21, 2012 MSFT SVC, Mountain View, CA **[register](http://nodejsatmicrosoftsvc.eventbrite.com/)** 99 | 100 | 2. April 28, 2012 Gangplank, Phoenix, AZ Coming Soon! 101 | 102 | 103 | ### Additional Resources 104 | Other useful things for you to check out in addition to the content at Node Bootcamp. 105 | 106 | * [Windows Azure for Node.JS](https://www.windowsazure.com/en-us/develop/nodejs/ "Windows Azure Node.JS Developer Center") 107 | * [Node Beginner Book](http://nodebeginner.org "The Node Beginner Book") 108 | * [Node.JS Manual](http://nodemanual.org/latest/ "Community-driven Node.JS Guide and Manual") -------------------------------------------------------------------------------- /lessonplan.md: -------------------------------------------------------------------------------- 1 | ## Node Bootcamp Lesson Plan 2 | 3 | 1. __Hour 1: Introduction to Node.JS__ 4 | * "Hello World" from Azure 5 | * How to set up an Azure account 6 | * How to configure Cloud9 to deploy to Azure 7 | * Deploy a "Hello World!" 8 | * What is Node and how does it work? 9 | * The Node Event Loop 10 | * JavaScript and Node 11 | * JavaScript primer 12 | * Objects and variables 13 | * The JavaScript typing system 14 | * Functions 15 | * JavaScript event model and callbacks 16 | * Closures and passing arguments 17 | * Node.JS patterns for functions 18 | * Creating Node projects 19 | * Hello World 20 | * What a Node project looks like in the filesystem 21 | * Node modules 22 | * Introduction to modules 23 | * Writing your own module 24 | * Using Node Package Manager (npm) 25 | * Package.json 26 | 27 | 2. __Hour 2: Node and Web Applications__ 28 | * HTTP and Node 29 | * Ports and process.env.PORT 30 | * Responding to requests 31 | * Response object 32 | * Content types 33 | * Working with the filesystem 34 | * Reading files 35 | * Sending files in response objects 36 | * Caching 37 | * File watchers 38 | * A Modern Application Framework 39 | * Recap on previous lessons 40 | * Requirements for modern applications 41 | * Example of a modern app 42 | * Node.js enables concurrency 43 | * Socket.io: A Primer 44 | * Why use Socket.io? 45 | * Creating a connection between client and server 46 | * Sending data to the client 47 | * Example application 48 | 49 | 3. __Hour 3: Building Real Web Applications with Node__ 50 | * Saving Files 51 | * Overview of Windows Azure Blob Storage 52 | * Saving files to Blob Storage 53 | * Getting files from Blob Storage 54 | * Working with a Database 55 | * Overview of Windows Azure Table Storage 56 | * Using Azure Table Storage in Node.JS 57 | * Saving records 58 | * Looking up a specific key 59 | * Queries 60 | * Updating records 61 | * Working with Cookies 62 | * What are cookies? 63 | * Setting cookie values 64 | * Getting cookie values 65 | * Deleting cookies 66 | 67 | 4. __Optional: Socket.IO Breakout Session__ 68 | * What is Socket.IO? 69 | * Introduction to WebSockets and Server-push 70 | * Socket.IO scenarios 71 | * Socket.IO overview 72 | * Server-side "Hello World" 73 | * Client-side "Hello World" 74 | * Socket.IO 101 75 | * Opening anc closing connections 76 | * Broadcasting (to everyone) 77 | * Broadcasting to groups 78 | 79 | 5. __Optional: Express Breakout Session__ 80 | * What is Express? 81 | * Introduction to MVC 82 | * Connect Middleware 83 | * Getting Started with Express 84 | * Installing Express 85 | * Scaffolding Express applications 86 | * Express project structure 87 | * Routing 88 | * Introduction to routes 89 | * Handling HTTP verbs 90 | * Writing route handlers 91 | * Passing route variables 92 | * Creating route modules 93 | * Creating Views 94 | * Introduction to Jade 95 | * Basic rules 96 | * Working with variables 97 | * Working with conditionals 98 | * Iterating over collections 99 | * Creating a layout 100 | * Creating a partial 101 | * Creating a view 102 | * Referencing a view from inside a route handler 103 | * Working with the Response object 104 | * Setting response headers 105 | * Passing local variables to views 106 | * Passing objects back to views 107 | * Creating Redirects 108 | * Sending downloadable files 109 | * Working with the Request object 110 | * Parsing incoming arguments with Request.Param 111 | * Working with sessions 112 | * Working with the Request.Flash object 113 | * Express Extras 114 | * Dynamic view helpers 115 | * App.param interceptors 116 | * Catch-all routes 117 | * Creating real data models with Azure Table Storage 118 | * How table storage works 119 | * How to install the Azure npm module 120 | * Connecting to your table storage account 121 | * Writing a real table storage model 122 | * Integrating your model with route handlers in Express -------------------------------------------------------------------------------- /sessions/hour1/06_working-with-packages/npm-packages/readme.md: -------------------------------------------------------------------------------- 1 | Installing and Using Modules via Node Package Manager (npm) 2 | -------- 3 | 4 | The code in this particular example serves plain text back in response to an HTTP request 5 | connection after using a third-party module to alter the case of the sentence we're sending back to the end-user. 6 | 7 | This simple Node.JS application depends on the [slang](https://github.com/devongovett/slang) package, so we have to install it via [npm](http://npmjs.org/ "Node Package Manager"). 8 | 9 | #### Installing npm Packages via npm install {package name} 10 | npm is the central package repository for Node.JS. As of writing this NPM has just under 7,000 packages that are free for you to include in your Node applications as you see fit. 11 | 12 | The npm utility is installed by default whenever you [install the Node.JS runtime](http://nodejs.org/) on any system. 13 | 14 | To install a package manually, all you need to do is use the _npm install_ command followed by the package name, like so: 15 | 16 | c:\introtonode\npm-packages> npm install slang 17 | 18 | This will automatically install the slang binaries in a `node_modules` subdirectory of your current working folder: 19 | 20 | c:\introtonode\npm-packages 21 | c:\introtonode\npm-packages\server.js 22 | c:\introtonode\npm-packages\node_modules\ 23 | c:\introtonode\npm-packages\node_modules\slang\ 24 | c:\introtonode\npm-packages\node_modules\slang\... {module files} 25 | 26 | Now you can include the slang module anywhere in your current Node application, like this: 27 | 28 | var slang = require("slang") 29 | 30 | Node's module system knows to always search in your project's `node_modules` directory when looking for modules to include, so you never need to worry about relative paths and such when you include it. 31 | 32 | #### Installing via package.json 33 | A better approach to managing npm packages in your application is to use the `package.json` file - this package describes all of the dependenices your application has for both development mode and production mode, and npm can use this information to automatically install all of your packages with one simple command: 34 | 35 | c:\introtonode\npm-packages> npm install 36 | 37 | When you run `npm install` in the same directory as your package.json file, npm will automatically ingest the contents of it and install all of the packages listed. 38 | 39 | Here's what the syntax looks like for the package.json file included in this example: 40 | 41 | { 42 | "name": "npm-package-example" 43 | , "version": "0.0.1" 44 | , "private": true 45 | , "dependencies": { 46 | "slang":">=0.2.0" 47 | } 48 | } 49 | 50 | This package.json file specifies that this application requires a version of the slang package that is at least version 0.2.0, so as new versions are released npm will always install the latest. If the application needed to run a specific version of slang for compatability or stability reasons, you could tell npm to install only version 0.2.0 like this 51 | 52 | { 53 | "name": "npm-package-example" 54 | , "version": "0.0.1" 55 | , "private": true 56 | , "dependencies": { 57 | "slang":"0.2.0" 58 | } 59 | } 60 | 61 | Package.json is important because this is often what's used to tell your webservers in production environments what packages are needed in order to run your application; otherwise you'd have to manually manage package installations on each server which isn't a scalable or safe approach to package management. 62 | 63 | #### Running the Sample Application 64 | 65 | To run the example code included in this directory: 66 | 67 | c:\introtonode\npm-packages> npm install 68 | c:\introtonode\npm-packages> node server.js 69 | c:\introtonode\npm-packages> starting server on port {PORT} 70 | 71 | To test your application, simply visit http://127.0.0.1:{PORT} in your web browser or use curl if you have it: 72 | 73 | $ curl http://127.0.0.1:{PORT} 74 | $ hello world! 75 | 76 | #### Where can I get `curl` for Windows? 77 | 78 | * It's included in [Git Bash](http://code.google.com/p/msysgit/). 79 | * It's included in [GOW: GNU on Windows](https://github.com/bmatzelle/gow). 80 | * It can be installed with [Cygwin](http://cygwin.com/). 81 | * You can download other builds from the [curl homepage] (http://curl.haxx.se/download.html). 82 | 83 | #### From the [npm FAQ](http://npmjs.org/doc/faq.html): "Should I check my `node_modules` folder into git?" 84 | 85 | * Check `node_modules` into git for things you deploy, such as websites and apps. 86 | * Do not check `node_modules` into git for libraries and modules intended to be reused. 87 | * Use `npm` to manage dependencies in your dev environment, but not in your deployment scripts. 88 | 89 | #### Finding `npm` modules 90 | 91 | Browse http://search.npmjs.org/ or use `npm search`. Also, some modules like HTTP are so important they ship with Node. See http://nodejs.org/docs/latest/api/index.html. 92 | 93 | #### How can I redownload modules that have changed? 94 | 95 | You can simply delete the module from `node_modules` and then run `node install module` again. You may wish to run `npm cache clean` to force a module to be redownloaded from npmjs.org. If you're on Windows and get errors when installing a module, try deleting everything under ~\appcache\roaming\npm-cache. This is likely a bug in npm version 1.1.0-3. 96 | 97 | -------------------------------------------------------------------------------- /sessions/hour3/05_working_with_cookies/readme.md: -------------------------------------------------------------------------------- 1 | Working with Cookies: 2 | ===================== 3 | 4 | Cookies are small and delicious pieces of data that web browsers store for web applications. Cookies are a key part of any web application, without them, you would have to log-in to a web application every time you visited it and URLs would look really ugly. 5 | 6 | Think of it this way: Without cookies, you would have no way of knowing if someone had already loaded your application and it would be really hard to keep track of what their last action was. 7 | 8 | Below is a full-featured Node application that demonstrates how set cookies, how to delete them and how to access cookies you've set previously. 9 | 10 | Don't worry about understanding everything just yet, we'll be explaing what were doing below. 11 | 12 | 13 | ```JavaScript 14 | var http = require('http'); 15 | var Cookies = require('cookies'); 16 | var url = require('url'); 17 | 18 | http.createServer(function(req, res) { 19 | var query = url.parse(req.url, true).query; 20 | var cookies = new Cookies(req, res); 21 | 22 | if (query["remember"]) { 23 | cookies.set('thing_to_remember', query["remember"]); 24 | res.writeHeader(302, {'Location':'/'}); 25 | res.end(); 26 | return; 27 | } 28 | 29 | if (query["forget"]) { 30 | var expireDate = new Date(1900, 1, 1); 31 | cookies.set('thing_to_remember', '', { 'expires': expireDate }); 32 | res.writeHeader(302, {'Location':'/'}); 33 | res.end(); 34 | return; 35 | } 36 | 37 | var message = ''; 38 | if (cookies.get('thing_to_remember')) { 39 | message = 'Hello again! You asked me to remember: ' + cookies.get('thing_to_remember'); 40 | } 41 | else { 42 | message = 'You have not asked me to remember anything yet. What shall I remember?'; 43 | } 44 | 45 | res.writeHead(200, {'Content-Type': 'text/html'}); 46 | res.end('' + 47 | message + 48 | '
' + 49 | '' + 50 | '' + 51 | '' + 52 | '
'); 53 | 54 | }).listen(process.env.PORT); 55 | ``` 56 | 57 | Not terribly complicated, right? We can thank the [cookies module](https://github.com/jed/cookies) for that! 58 | 59 | The way that you actually set a cookie is via a HTTP header. We could have done this with a .writeHeader or .addHeader method. You get a cookie by reading the headers that the browser has sent us. Again, we could have done this by using the .headers() method on the request object. But as you can imagine, manipulating headers directly isn't very fun and there's some nuance to doing that correctly. Luckly the cookies module takes care of all of that for us! 60 | 61 | Don't forget! To use the cookies module, you'll have to run ''npm install cookies''. 62 | 63 | Alright, lets look at some of the key parts in the code above. By now, most of it should make sense to you. So we're going to stick to explaining how to use the cookies module. 64 | 65 | You need to initialize a new cookies object with your request and response objects. Like so: 66 | ```JavaScript 67 | var cookies = new Cookies(req, res); 68 | ``` 69 | 70 | The ''query'' object contains all the GET paramaters that were present in the URL we are handling. If there is a ''remember'' paramater, it means that you typed something in the text box and clicked the "Remember this" button. 71 | 72 | So, if you clicked the "Remember this" button, we set a cookie with the name of "thing_to_remember" and set the value to the thing you typed in the text box. 73 | 74 | When we're done, we redirect you back to the "root" URL. This is done so that you can see that we really are saving this data in your browser and not in the URL! 75 | 76 | ```JavaScript 77 | if (query["remember"]) { 78 | cookies.set('thing_to_remember', query["remember"]); 79 | res.writeHeader(302, {'Location':'/'}); 80 | res.end(); 81 | return; 82 | } 83 | ``` 84 | 85 | If there is a ''forget'' paramater in the URL that we're handling, it means you clicked the "Forget Everything" button. 86 | 87 | To delete a cookie, we must set another cookie with the same name as the one we want to delete, except with an expiration date in the past. 88 | 89 | So, if you clicked the "Forget Everything" button. We "delete" the "thing_to_remember" cookie. When we're done, we redirect you back to the "root" URL. Like before, this is done so that you can see that we really are deleting this data and not just faking it. 90 | 91 | ```JavaScript 92 | if (query["forget"]) { 93 | var expireDate = new Date(1900, 1, 1); 94 | cookies.set('thing_to_remember', '', { 'expires': expireDate }); 95 | res.writeHeader(302, {'Location':'/'}); 96 | res.end(); 97 | return; 98 | } 99 | ``` 100 | 101 | Finally, if we weren't setting or deleting a cookie. That means we want to see what's in the cookie. To do that, we use the .get() method on the cookie object: 102 | ```JavaScript 103 | cookies.get('thing_to_remember') 104 | ``` 105 | 106 | So there you have it! 107 | 108 | Here's a quick summary: 109 | 110 | * "npm install cookies" 111 | * var Cookies = require('cookies') 112 | * var cookies = new Cookies(req, res) 113 | * cookies.set('key','value') 114 | * cookies.get('key') 115 | -------------------------------------------------------------------------------- /sessions/hour1/00_hello-world-from-azure/readme.md: -------------------------------------------------------------------------------- 1 | "Hello World" from Azure 2 | ======================== 3 | 4 | All of our instruction today will be done in the Cloud9 IDE and deployed to Azure. 5 | So, the very first thing you'll need to do is sign up for both of those services. 6 | 7 | Step 1: Sign up for your trial Azure account 8 | -------------------------------------------- 9 | * See your class instructor for directions on how to sign up for your trial Azure account. 10 | * Visit http://windows.azure.com and make sure that you can sign in. 11 | 12 | Step 2: Sign up for the Cloud9 IDE 13 | ---------------------------------- 14 | * Using the same browser that you used to sign in to Windows Azure in Step 1: 15 | * Visit http://c9.io 16 | * Enter in your "Desired username", "Your email", and "Re-enter your email". 17 | * Click "Sign me up". 18 | * Check your email for an email from "info@c9.io" with the Subject line of "Welcome to Cloud9! Please activate your account!" 19 | * Click on the link that looks like this: http://c9.io/activate.html?uid=xxxxx&code=xxxxxx&redirect=%2Fdashboard.html 20 | * You will be sent to an "Activate account" page. 21 | * Enter in your "Password", and "Confirm password" 22 | * Click "ACTIVATE" 23 | * Create a new project: 24 | * Look for the "MY PROJECTS" pane on the left side of your screen. 25 | * Click on the "+" that is pointed to by the "Create a project here" arrow. 26 | * Select the "Create a new project" option. 27 | * Name your project. (We suggest you name it "helloworld". 28 | * Leave the "Project type" set to "Git". 29 | * Click "CREATE". 30 | 31 | Step 3: Write your "Hello World" in Cloud9! 32 | ------------------------------------------- 33 | * Click the green "START EDITING" button. 34 | * If this is your first Cloud9 project, you will be prompted with a "Here are a few pointers to get you started!" dialog. 35 | * Read the text and click on the "click here!" link to see a brief demo. 36 | * This is a short, useful walkthrough, we suggest following it. You'll learn some useful stuff! 37 | * Create a new file named "server.js" 38 | * Click on the "File" menu, select the "New File" option. 39 | * Paste in [this "Hello world" code](https://gist.github.com/1794418). 40 | * Click on the "File" menu, select the "Save" option. 41 | * In the "Save as": box, enter "server.js", then click "Save" 42 | * Run your code! 43 | * Click on the "debug" button. 44 | * Wait a few moments for the "Output" pane to pop up at the bottom of your screen. 45 | * In the output pane, you should see a link that looks like: http://nodebootcamp.xxxxxx.c9.io/ 46 | * Click on that link. 47 | * You should see "Hello world" in your browser! 48 | 49 | Step 4: Deploy to Azure 50 | ----------------------- 51 | * Click on the "Deploy" button on the left side of the Cloud9 IDE: 52 | * ![Image of the Deploy button](http://content.screencast.com/users/franusic/folders/Jing/media/671b7cf1-deee-403e-9c4d-e44808afcc35/2012-02-10_1918.png) 53 | * Click on the "+" button. You should see some green text saying "Create a Deploy Server" pointing at it. 54 | * ![Image of "Create a Deploy Server"](http://content.screencast.com/users/franusic/folders/Jing/media/dec9e426-4ad7-43cf-94c0-32c4571c9723/2012-02-10_1921.png) 55 | * On the "Choose type:" menu, select "Windows Azure". 56 | * Click on the green "DOWNLOAD WINDOWS AZURE SETTINGS" button. 57 | * This will open a new window or tab in your browser. 58 | * A file ending in ".publishsettings" should automatically start to download 59 | * ![Image of the Add a deploy target" window](http://content.screencast.com/users/franusic/folders/Jing/media/0067ad35-cd2f-4aee-b4e3-063d4cd75311/2012-02-10_1929.png) 60 | * Switch back to the window or tab where the Cloud9 IDE is running. 61 | * Press the "Choose File" button. 62 | * Select the ".publishsettings" file that was downloaded in the step above. 63 | * Press the green "UPLOAD" button. 64 | * ![What the UPLOAD button looks like](http://content.screencast.com/users/franusic/folders/Jing/media/70151c98-bb61-4c63-9e17-ce281bfb6bce/2012-02-10_1941.png) 65 | * (If you have multiple subscriptions, you'll be prompted to select one) 66 | * Click the "+ Create New" button. 67 | * Give your deployment a name. (We suggest: "nodecamp-" + $your_twitter_handle) 68 | * Change the number of instances to "2" 69 | * Leave all the rest of the settings at their defaults. 70 | * Click the green "CREATE" button. 71 | * (You may be prompted to enter a RDP username and password. If you are, do so.) 72 | * ![What the settings dialog looks like](http://content.screencast.com/users/franusic/folders/Jing/media/11a2a76e-e0dc-42e6-af80-a1f6764b3ef6/2012-02-10_1950.png) 73 | * Wait for the Windows Azure deploy target to say "Active" 74 | * Click on the Windows Azure deploy target. You'll see a fly-out with some options and a big green "DEPLOY" button. 75 | * Click the big green "DEPLOY" button. 76 | * ![What the fly-out looks like](http://content.screencast.com/users/franusic/folders/Jing/media/9398026a-a3d4-4b3b-b038-10e9864c181e/2012-02-10_1957.png) 77 | * You'll be prompted to create "web.config". Click "Yes". 78 | * You'll be prompted to create a "csdef" file. Click "Yes". 79 | * You'll be prompted to select an instance size for the csdef file. Select "Extra small" and click "Create". 80 | * You should see a deploy status with a faint grey spinning gear. 81 | * (You may have to hit "reload" in your browser to get status updates) 82 | * When the deployment finishes (this will take 5-8 minutes). Click on the deploy target, then click on the "Url:" in the flyout. 83 | * (If your followed our naming suggestion above, this Url will look like this: http://nodecamp-xxxxxx.cloudapp.net) 84 | * When you see "Hello World" on your .cloudapp.net url. You're done! 85 | * Hooray! 86 | 87 | See also: 88 | ========= 89 | 90 | * Getting started with the Cloud 9 IDE: 91 | * http://support.cloud9ide.com/entries/20583558-lesson-1-creating-a-new-account 92 | * http://support.cloud9ide.com/entries/20548092-lesson-2-creating-a-new-project 93 | * http://support.cloud9ide.com/entries/20640198-lesson-3-writing-a-node-js-hello-world-program 94 | * Connecting Cloud 9 to Windows Azure: 95 | * http://cloud9ide.posterous.com/windows-azure-on-cloud9 -------------------------------------------------------------------------------- /sessions/hour3/03_using-a-data-model-with-Azure-tables/winenotebook_with_model.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var formidable = require('formidable'), util = require('util'); 3 | var uuid = require('node-uuid'); 4 | var port = process.env.PORT || 1337; // for C9, Azure, or when running locally. 5 | var azure = require('azure'); 6 | var Wine = require('./models/wine').Wine; 7 | 8 | var wines = new Wine(); 9 | wines.init(); 10 | 11 | function entityInsertedCallback(error, serverEntity) { 12 | if (error === null) { 13 | console.log('Successfully inserted entity ' + serverEntity.Winery); 14 | } else { 15 | console.log('Could not insert entity into table: ' + error.code); 16 | console.log('Message: ' + error.message); 17 | } 18 | } 19 | 20 | function entityDeletedCallback(error) { 21 | if (error === null) { 22 | console.log('Successfully deleted entity'); 23 | } 24 | else { 25 | console.log('Could not delete entity: ' + error.code); 26 | console.log('Message: ' + error.message); 27 | } 28 | } 29 | 30 | http.createServer(function (req, res) { 31 | if (req.url === '/favicon.ico') return; 32 | 33 | if (req.url == '/addWine' && req.method.toLowerCase() == 'post') { 34 | // Read http://nodebeginner.org to learn more about formidable. 35 | var form = new formidable.IncomingForm(); 36 | form.parse(req, function (err, fields, files) { 37 | res.writeHead(200, { 'content-type': 'text/plain' }); 38 | res.write('Received this via HTTP POST:\n\n'); 39 | res.write(util.inspect({ fields: fields }) + '\n\n'); 40 | res.end('Check console for status of adding this, and reload "/" to see it.'); 41 | var wine = fields; 42 | wine['RowKey'] = uuid(); 43 | wine['TastedOn'] = new Date(); 44 | // Add additional keys and values to the entity, making sure they're not empty. Then delete properties which 45 | // were parsed from the incoming form data. 46 | if (fields.AdditionalKey1 !== '') wine[fields.AdditionalKey1] = fields.AdditionalValue1; 47 | if (fields.AdditionalKey2 !== '') wine[fields.AdditionalKey2] = fields.AdditionalValue2; 48 | if (fields.AdditionalKey3 !== '') wine[fields.AdditionalKey3] = fields.AdditionalValue3; 49 | delete wine.AdditionalKey1; delete wine.AdditionalValue1; 50 | delete wine.AdditionalKey2; delete wine.AdditionalValue2; 51 | delete wine.AdditionalKey3; delete wine.AdditionalValue3; 52 | wines.save(wine, entityInsertedCallback); 53 | }); 54 | return; 55 | } 56 | 57 | if (req.url == '/deleteWine' && req.method.toLowerCase() == 'post') { 58 | // delete a wine 59 | var form = new formidable.IncomingForm(); 60 | form.parse(req, function (err, fields, files) { 61 | res.writeHead(200, { 'content-type': 'text/plain' }); 62 | res.write('Received this via HTTP POST:\n\n'); 63 | res.write(util.inspect({ fields: fields }) + '\n\n'); 64 | res.end('Check console for status of deleting this, and reload "/" to see it.'); 65 | var wine = fields; 66 | wines.destroy(wine, entityDeletedCallback); 67 | }); 68 | return; 69 | } 70 | 71 | res.writeHead(200, { 'Content-Type': 'text/html' }); 72 | res.write('

Wine "Node"-book

'); 73 | var form = '
' + 74 | '' + 75 | '
' + 76 | '' + 77 | '
' + 78 | '' + 79 | '
' + 80 | '' + 81 | '
' + 82 | '' + 83 | '
' + 84 | '' + 85 | '' + 86 | '
' 87 | res.write(form + '
'); 88 | 89 | // read & display data from table 90 | wines.findAll(entitiesQueriedCallback); 91 | function entitiesQueriedCallback(error, serverEntities) { 92 | if (error === null) { 93 | res.write('