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 | '');
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 | 
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 | '');
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 | * 
53 | * Click on the "+" button. You should see some green text saying "Create a Deploy Server" pointing at it.
54 | * 
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 | * 
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 | * 
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 | * 
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 | * 
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 = ''
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('
');
94 | for (var index in serverEntities) {
95 | res.write('
');
114 | } else {
115 | res.end('Could not query entities: ' + error.code);
116 | console.log('Could not query entities: ' + error.code);
117 | }
118 | }
119 |
120 | }).listen(port);
121 |
--------------------------------------------------------------------------------
/sessions/hour2/04_socket-io-a-primer/readme.md:
--------------------------------------------------------------------------------
1 | Bi-Directional Communication with Socket.io
2 | ===========================================
3 |
4 | In the last lesson we hammered on the point that Node.JS enables concurrency in
5 | your application.
6 |
7 | One of the most awesome utilities to take advantage of concurrency is socket.io.
8 | Before we get into what socket.io is and how to use it, a brief history on how
9 | users and websites have traditionally interacted.
10 |
11 | ### A Brief History
12 |
13 | Up until a few years ago, the web was primarily concerned with one metric:
14 | getting webpages to visitors as quickly as possible.
15 |
16 | User sends request -> Server
17 | Server sends data back -> Client
18 | Connection terminated.
19 |
20 | Note, however, that this does not allow the server to send data back to the
21 | client as updates on the server occur. For many websites, this is still the
22 | model; you have to refresh the page to see updates.
23 |
24 | ### Today
25 |
26 | Nowadays, the metric is centered on providing a more engaging user experience.
27 | One of the most powerful realizations of this potential is on Facebook: the news
28 | ticker, notifications and chat are pillars of the experience and it works to
29 | keep users engaged.
30 |
31 | The technological approach Facebook uses to drive this experience is
32 | bi-directional communication. What is bi-directional communication? It means
33 | on top of the established client-request/server-respond model, the server can
34 | send messages to the client without the client specifically requesting them.
35 |
36 | ### The Socket.io Way
37 |
38 | The way socket.io works is its server component "tricks" the client into staying
39 | connected to the server until the connection times out. When that connection
40 | times out, the client connects again. And again, on and on - forever. Why? When
41 | the server has something meaningful to say to the client - for example, that a
42 | new chat message has come in - the server sends a message to the client and
43 | closes out the connection. Then the client connects again, and the process
44 | continues. In this way the client is always "primed" to receive messages from
45 | the server.
46 |
47 | Socket.io wasn't the first to pioneer this technique, but it has become the de
48 | facto standard for bi-directional communication in Node.JS apps.
49 |
50 | Of course, this description has cut a lot of corners for brevity, but you can
51 | learn more about socket.io's approach and internals at [LearnBoost's socket.io
52 | page on GitHub](https://github.com/LearnBoost/socket.io).
53 |
54 | ### Implementing Socket.io
55 |
56 | To use socket.io in your node project, run `npm install socket.io` inside the
57 | project directory. On Cloud9, this can be accomplished from the CLI at the
58 | bottom of the IDE. From there, getting socket.io in your code is as simple as
59 |
60 | ```javascript
61 | var socketio = require('socket.io');
62 | ```
63 |
64 | Socket.io works by piggybacking onto your existing HTTP server instance to
65 | listen for incoming connections and messages from clients.
66 |
67 | ```javascript
68 | // From 'https://github.com/mmukhin/psitsmike_example_1'
69 | var app = require('express').createServer();
70 | var io = require('socket.io').listen(app);
71 |
72 | app.listen(process.env.PORT || 8080);
73 |
74 | app.get('/', function (req, res) {
75 | res.sendfile(__dirname + '/index.html');
76 | });
77 |
78 | var usernames = {};
79 |
80 | io.sockets.on('connection', function (socket) {
81 |
82 | socket.on('sendchat', function (data) {
83 | io.sockets.emit('updatechat', socket.username, data);
84 | });
85 |
86 | socket.on('adduser', function(username){
87 | socket.username = username;
88 | usernames[username] = username;
89 | socket.emit('updatechat', 'SERVER', 'you have connected');
90 | socket.broadcast.emit('updatechat', 'SERVER', username + ' has connected');
91 | io.sockets.emit('updateusers', usernames);
92 | });
93 |
94 | socket.on('disconnect', function(){
95 | delete usernames[socket.username];
96 | io.sockets.emit('updateusers', usernames);
97 | socket.broadcast.emit('updatechat', 'SERVER', socket.username + ' has disconnected');
98 | });
99 | });
100 | ```
101 |
102 | You'll notice there's a lote of new things in the example above. The first of which is the use
103 | of the "[express](http://expressjs.com/)" module. We aren't going to go into the
104 | details of express. While it does much more than just this, for now you can think of
105 | express as a module that simplifies request handling. It's what allows us to handle requests
106 | to '/' with a simple `app.get('/', ...`, it's also what allows us to send the user a file
107 | with one line (`res.sendfile(__dirname + '/index.html');`).
108 |
109 | The next thing that should stand out to you is the
110 | `var io = require('socket.io').listen(app);` code near the top. This line is how socket.io
111 | piggybacks onto the existing HTTP server instance, as we mentioned earlier.
112 |
113 | By and large the syntax for socket.io is very much like natural language.
114 | Socket.io listens for incoming connections on our HTTP server with a specific URL request.
115 | Only then will it call the callback function with the `socket` parameter supplied.
116 | This `socket` parameter is an object with everything we need to know about the
117 | connecting client.
118 |
119 | From there we can send and receive messages to/from the client. `socket.emit`
120 | sends a message (the 2nd parameter) on the namespace (1st parameter). In this
121 | case the namespaces are 'updatechat' and 'updateusers'.
122 | This means the client must be listening for messages on one of those namespaces to
123 | receive a message. Let's see a brief example of what this looks like on the client side:
124 |
125 | ```javascript
126 | var socket = io.connect();
127 |
128 | socket.on('updatechat', function (username, data) {
129 | $('#conversation').append(''+username + ': ' + data + ' ');
130 | });
131 | ```
132 |
133 | First a connection is initiated. When the socket receives a message on the 'updatechat'
134 | namespace, it gets passed the `data` parameter. When this client receives an 'updatechat'
135 | message, it will use jQuery to append that message into the '#conversation' DIV.
136 |
137 | Take a look at the index.html file in this directory to see the rest of the client implementation!
138 |
139 | So, even though we just covered a simple little example,
140 | hopefully with this foundation you can start dreaming up ways to dispatch server-side
141 | events to your clients: a new chat message, a new database entry, a weather
142 | advisory, new joystick movement.
143 |
144 | Bi-directional communication ties all the pieces together so your users are
145 | always engaged with your server. Welcome to the new web.
--------------------------------------------------------------------------------
/sessions/hour3/01_working-with-a-database/winenotebook.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 |
7 |
8 | //if (process.env.C9_PORT) { // Test if we're running on Cloud9. Change these to your own credentials.
9 | var account = 'azurelognodes';
10 | var accountKey = '7XSaCMwQACwq84ScdZyDGHC34qAKXwnfKy4cmlVsRR4nZUr1OdkJE3+pAeReWp0M5JLvm705dWzOyPnQaMnRxA==';
11 | var tableHost = 'http://table.core.windows.net';
12 | //}
13 |
14 | var tableService = azure.createTableService(account, accountKey, tableHost);
15 | //tableService.logger = new azure.Logger(azure.Logger.LogLevels.DEBUG);
16 |
17 | var tableName = 'wines'; // Name your table here.
18 |
19 | // Create the table
20 | tableService.createTableIfNotExists(tableName, tableCreatedOrExists);
21 | function tableCreatedOrExists(error) {
22 | if (error === null) {
23 | console.log('Using table ' + tableName);
24 | } else {
25 | console.log('Could not use table: ' + error.code);
26 | }
27 | }
28 |
29 | function entityInsertedCallback(error, serverEntity) {
30 | if (error === null) {
31 | console.log('Successfully inserted entity ' + serverEntity.Winery);
32 | } else {
33 | console.log('Could not insert entity into table: ' + error.code);
34 | }
35 | }
36 |
37 | function entityDeletedCallback(error) {
38 | if (error === null) {
39 | console.log('Successfully deleted entity');
40 | }
41 | else {
42 | console.log('Could not delete entity: ' + error.code);
43 | }
44 | }
45 |
46 | http.createServer(function (req, res) {
47 | if (req.url === '/favicon.ico') return;
48 |
49 | if (req.url == '/addWine' && req.method.toLowerCase() == 'post') {
50 | // Read http://nodebeginner.org to learn more about formidable.
51 | var form = new formidable.IncomingForm();
52 | form.parse(req, function (err, fields, files) {
53 | res.writeHead(200, { 'content-type': 'text/plain' });
54 | res.write('Received this via HTTP POST:\n\n');
55 | res.write(util.inspect({ fields: fields }) + '\n\n');
56 | res.end('Check console for status of adding this, and reload "/" to see it.');
57 | var wine = fields;
58 | wine['RowKey'] = uuid();
59 | wine['TastedOn'] = new Date();
60 | // Add additional keys and values to the entity, making sure they're not empty. Then delete properties which
61 | // were parsed from the incoming form data.
62 | if (fields.AdditionalKey1 !== '') wine[fields.AdditionalKey1] = fields.AdditionalValue1;
63 | if (fields.AdditionalKey2 !== '') wine[fields.AdditionalKey2] = fields.AdditionalValue2;
64 | if (fields.AdditionalKey3 !== '') wine[fields.AdditionalKey3] = fields.AdditionalValue3;
65 | delete wine.AdditionalKey1; delete wine.AdditionalValue1;
66 | delete wine.AdditionalKey2; delete wine.AdditionalValue2;
67 | delete wine.AdditionalKey3; delete wine.AdditionalValue3;
68 | tableService.insertEntity(tableName, wine, entityInsertedCallback);
69 | });
70 | return;
71 | }
72 |
73 | if (req.url == '/deleteWine' && req.method.toLowerCase() == 'post') {
74 | // delete a wine
75 | var form = new formidable.IncomingForm();
76 | form.parse(req, function (err, fields, files) {
77 | res.writeHead(200, { 'content-type': 'text/plain' });
78 | res.write('Received this via HTTP POST:\n\n');
79 | res.write(util.inspect({ fields: fields }) + '\n\n');
80 | res.end('Check console for status of deleting this, and reload "/" to see it.');
81 | var wine = fields;
82 | tableService.deleteEntity(tableName, wine, entityDeletedCallback);
83 | });
84 | return;
85 | }
86 |
87 | res.writeHead(200, { 'Content-Type': 'text/html' });
88 | res.write('
Wine Notebook
');
89 | var form = '
'
103 | res.write(form + '');
104 |
105 | // read & display data from table
106 | var query = azure.TableQuery.select().from(tableName);
107 | tableService.queryEntities(query, entitiesQueriedCallback);
108 | function entitiesQueriedCallback(error, serverEntities) {
109 | if (error === null) {
110 | res.write('
');
111 | for (var index in serverEntities) {
112 | res.write('
');
131 | } else {
132 | res.end('Could not query entities: ' + error.code);
133 | console.log('Could not query entities: ' + error.code);
134 | }
135 | }
136 |
137 | }).listen(port);
138 |
--------------------------------------------------------------------------------
/sessions/hour1/04_javascript-primer/readme.md:
--------------------------------------------------------------------------------
1 | # Quick Overview of JavaScript
2 | Given that all Node.JS applications are written in JavaScript, here's a primer of
3 | some of the basic JavaScript language features and syntax that you will need to know
4 | in the course of working with Node.
5 |
6 | ## Variables
7 | Variables in JavaScript:
8 | - Can start with a-z,A-Z, and "_"
9 | - Are dynamically typed, as shown in the example below:
10 |
11 | ```JavaScript
12 | var x = 5; //this just became an integer
13 | x = "seven"; //this just became a string
14 | //note that these are still typed
15 | //you just don't have to tell JavaScript what the type is
16 |
17 | var b; //has default value of undefined
18 | b = true; //assigning boolean literal
19 | b = false;
20 | ```
21 |
22 | - Variables are case sensitive, so 'accountNumber != AccountNumber'
23 |
24 | __Built-in types in JavaScript__:
25 |
26 | - integers (int)
27 | - floating point (float)
28 | - boolean (bool)
29 | - strings (String)
30 | - arrays (Array)
31 |
32 | ## Functions
33 | Functions in JavaScript follow a similar convention to many other popular programming languages like PHP:
34 |
35 | ```JavaScript
36 | function fakemathfunction(a,b){
37 | a++;
38 | b = b+3;
39 | return a * b;
40 | }
41 |
42 | var c = fakemathfunction(4,5);
43 |
44 | //functions are objects in JavaScript and are assignable
45 | var fmf = fakemathfunction;
46 | var d = fmf(5,6);
47 |
48 | //functions can be passed to other functions
49 | function callafunction(f) {
50 | return f(7,8);
51 | }
52 |
53 | var e = callafunction(fakemathfunction);
54 |
55 | //functions can be passed anonymously
56 | var f = callafunction( function(a,b) {
57 | return a+b;
58 | });
59 | ```
60 |
61 | ## Control Flow
62 | Control flow in JavaScript, using conditionals like `if` and `else` statements to direct
63 | the flow of an application's execution, is once again very similar to other languages:
64 |
65 | ```JavaScript
66 | var a = true, b = false;
67 | if (b) {
68 | alert("you will never see this");
69 | }
70 | else if (a) {
71 | alert("you will always see this");
72 | }
73 | else {
74 | alert("you won't see this, either");
75 | }
76 |
77 | while (a) {
78 | alert("over and over");
79 | break; // breaks out of the loop
80 | }
81 |
82 | do {
83 | alert("over and over");
84 | break;
85 | } while (a);
86 |
87 | for (var i = 0; i<3; i++) {
88 | alert ('number of times: ' + (i+1));
89 | }
90 | ```
91 |
92 | ## Objects
93 | Objects in JavaScript are "special" variables that can have extensible properties.
94 |
95 | - Typically you create an object using keyword 'new' on a function
96 | - Use the 'this' keyword to reference properties explicitly inside functions
97 |
98 | ```JavaScript
99 | function customerobject() {
100 | this.id = 0;
101 | this.name = "default";
102 | }
103 |
104 | var mynewcustomer = new customerobject();
105 | mynewcustomer.id = 123456;
106 | mynewcustomer.name = "John Doe";
107 |
108 | // or do this ...
109 | var obj = {id: 234567, name: "Jane Doe"};
110 |
111 | //JavaScript uses "duck typing"
112 | //so obj could be used anywhere a customerobject was expected
113 | ```
114 | Technically, there's no such thing as a class in JavaScript, but you
115 | can add "methods" to an object in various ways:
116 |
117 | ```JavaScript
118 | function foo() {
119 | this.greeting = "hey";
120 | this.Hello = function() {
121 | return this.greeting;
122 | }
123 | }
124 |
125 | foo.prototype.Howdy = function () {
126 | return this.greeting;
127 | };
128 | ```
129 |
130 | Objects are extremely flexible in JavaScript:
131 |
132 | ```JavaScript
133 | // you can create an "empty" object
134 | var o = new Object();
135 | // you can add properties to an object at any time
136 | o.name = "Judy Doe"
137 | o.SayHello = function() {
138 | return this.name;
139 | };
140 | //special syntax lets you loop through all properties
141 | o.id = 25;
142 | o.foo = "bar";
143 | var z;
144 | for (z in o) {
145 | alert(z + ': ' + o[z]); // access properties with []
146 | // like . but can be referenced by variable
147 | }
148 | ```
149 |
150 | ## Arrays
151 | Arrays are a built-in object type that you can use to store multiple objects of various types
152 |
153 | ```JavaScript
154 | var myArray = new Array();
155 | var yourArray = new Array(7, 14, "six"); // elements don't have to have same type
156 | var hisArray = [];
157 | var herArray = [1, true, 16.7, "howdy", yourArray];
158 |
159 | myArray[14] = "yup" // access using square brackets
160 | // arrays expand as needed and can be "sparse"
161 | var text = myArray[14];
162 |
163 | myArray['wow'] = 'wee'; //index doesn't have to be integer
164 | text = myArray['wow'];
165 | ```
166 |
167 | - "for .. in" works for arrays, too
168 |
169 | ```JavaScript
170 | var x = [2, 7, "foo"];
171 | x[17] = "bar";
172 | var z;
173 | for (z in x) {
174 | alert(z + ': ' + x[z]);
175 | }
176 | ```
177 |
178 | ## JavaScript event model and callbacks
179 |
180 | ```JavaScript
181 | // define function with the callback argument
182 | function fakemathfunction(a, b, callback) {
183 | a++;
184 | b = b+3;
185 | var c = a * b;
186 | callback(c);
187 | }
188 |
189 | // call the function
190 | fakemathfunction(5, 15, function(num) {
191 | console.log("callback: " + num);
192 | });
193 | ```
194 |
195 | ## Closures and passing arguments
196 | Closures happen when you pass an object declared outside an event callback
197 | and the JavaScript runtime makes a reference to that object available to the callback funciton
198 | even though the object was declared in a different scope.
199 |
200 | ```JavaScript
201 |
202 | function firstfunction(secondfunction) {
203 | secondfunction("changed value");
204 | }
205 |
206 | function test() {
207 |
208 | var value = "original value";
209 |
210 | alert(value); //first alert showing string 'original value'
211 |
212 | firstfunction(function(externalValue) {
213 | value = externalValue;
214 | });
215 |
216 | alert(value); //second alert showing string 'changed value'
217 | }
218 | ```
219 |
220 | ## JavaScript for Node.JS
221 | - No cross-browser issues - you know it's going to be the V8 engine
222 | - Not a client - No DOM!
223 |
224 | ```JavaScript
225 | //where would this go?
226 | alert("huh?");
227 |
228 | //none of these exists on the server
229 | window
230 | document.domain
231 | document.cookie
232 | document.body
233 | // no document . anything!
234 | // or any other DOM object ...
235 | // (event, HTMLElement, Body, etc.)
236 | ```
237 | - Node has different idioms for these types of things
238 |
239 | ## Node.JS patterns for functions
240 |
241 | Node uses various modules that people have written for common tasks
242 |
243 | ```JavaScript
244 | //Add modules by declaring variables
245 | //C'mon - use the same variable name as the module!
246 | var http = require("http");
247 | var url = require("url");
248 |
249 | var port = process.env.PORT; //some idioms are platform-specific
250 | //(determining port to listen on, in this case)
251 | //Node includes its own http server
252 | http.createServer(function(req,res) { //pass an anonymous function with request
253 | //and response parms to createServer
254 | //that function will be called on every
255 | //http request (that's the "event")
256 | res.writeHead(200, { 'Content-Type': 'text/plain' });
257 | //that function calls methods on the response object
258 | res.end('Hello World'); //ultimately, Hello World gets sent to the browser
259 | }).listen(port); //the anonymous object returned from createServer
260 | //doesn't do anything until you call listen on it
261 | ```
262 |
263 | Every http request gets request object and response object
264 |
265 | ```JavaScript
266 | //We can make the event handler for the http request explicit
267 | function onRequest(request, response) {
268 | response.writeHead(200, { 'Content-Type': 'text/plain' });
269 | response.end('Hello World');
270 | }
271 | http.createServer(onRequest).listen(process.env.PORT);
272 | ```
273 |
274 | This will look different, depending on what libraries you use, but ultimately, node "wakes up" and calls a function
275 | (perhaps anonymous, perhaps not) every time an http request comes in. That function is expected to have two
276 | parameters (regardless of what they are named) - a request object and a response object - those objects are
277 | defined in the http module.
278 |
--------------------------------------------------------------------------------
/sessions/hour3/00_saving-files/readme.md:
--------------------------------------------------------------------------------
1 | Saving Files Using Azure Blob Storage
2 | --------
3 | Azure Blob Storage is an expandable data-store suited to images,
4 | large documents, or static web content such as html pages, JavaScript files,
5 | or CSS. Items stored in blob storage have their own URL and may have access
6 | control rules applied to them.
7 |
8 | ### Simple File Upload
9 | Let's create a simple upload utility that performs the following tasks:
10 |
11 | - create a new container (similar to a directory)
12 | - upload a file into blob storage given a container name and file
13 | - list all containers
14 | - list all blobs
15 | - delete a container
16 | - delete a blob
17 |
18 | Each task will be implemented as a RESTfull api call, allowing us to focus on
19 | the server side functionality and leverage curl as our client. (curl is a command
20 | line utility for sending HTTP requets). Here are some sample calls to show how the
21 | REST api will appear to a client:
22 |
23 |
24 |
25 |
HTTP verb
Content
URL
Notes
26 |
27 |
28 |
PUT
file
http://myserver.com/container/newfile
new file
29 |
30 |
31 |
PUT
none
http://myserver.com/container
new container
32 |
33 |
34 |
DELETE
none
http://myserver.com/container/newfile
delete newfile
35 |
36 |
37 |
DELETE
none
http://myserver.com/container
delete container
38 |
39 |
40 |
GET
none
http://myserver.com/
list containers
41 |
42 |
43 |
GET
none
http://myserver.com/container
list files
44 |
45 |
46 |
47 | Looks like quite a bit of work, doesn't it? With Node.js and the Azure SDK it's
48 | only going to take about 100 lines of code, including brackets.
49 |
50 | ####Creating a BLOB Container
51 | In REST PUT operations must be
52 | idempotent. Our file uploader will use the PUT HTTP method to either create
53 | a new blob, or overwrite an existing blob. Within Azure storage, blobs are placed
54 | within a container. This is similar to a directory...so before we can begin
55 | uploading files, we need to at least have a container to place them into.
56 |
57 | First obtain a handle to the blobService:
58 |
59 | ```JavaScript
60 | var azure = require('azure');
61 | var blobService = azure.createBlobService();
62 | ```
63 |
64 | Using that handle, a container may be created like this:
65 |
66 | ```JavaScript
67 | blobService.createContainerIfNotExists(containerName, {
68 | publicAccessLevel: 'blob'}, containerCreated);
69 | ```
70 |
71 | We'll obviously need a way for the user to supply a container name, and for that
72 | we'll use the request URL. The REST request is saying "PUT" at new container
73 | at the URL I'm sending to you. For this we need to parse the URL and split it out a bit:
74 |
75 | ```JavaScript
76 | var parts = req.url.split('/', 3);
77 | var containerName = parts[1];
78 | var blobName = parts[2];
79 | if (req.method == 'PUT') {
80 | ```
81 |
82 | You may test this feature of the example using curl to send a simple PUT request
83 | to the server like this:
84 |
85 | ```
86 | %curl --request PUT http://kagemusha.tcowan.c9.io/mycontainer1
87 | ```
88 |
89 | (replacing the URL base with your own running server). This creates a new container
90 | in your storage account called "mycontainer1".
91 |
92 | Your containers may be protected or public. This exercise will assume public
93 | access, which will also make it easier to discover the changes you have made
94 | using either 'curl' from the command line or a browser.
95 |
96 | ####Uploading a File into Blob Storage
97 | The blob service api has several methods for uploading files to blob storage.
98 | Since Node is stream friendly, the easiest thing that can possibly work will
99 | be to take the request input stream, and stream that directly into a new
100 | Azure Blob. Here's the api signature that will work best
101 |
102 | ```JavaScript
103 | blobService.createBlockBlobFromStream(
104 | containerName, blobName, stream, size, blobCreatedEvent)
105 | ```
106 |
107 | Notice we now need two names to specify, the container and the name of the new
108 | blob to "put" within the specified container. Here's the code snippet from the
109 | example:
110 |
111 | ```JavaScript
112 | var size = req.headers["content-length"];
113 | blobService.createBlockBlobFromStream(containerName, blobName, req, size, blobCreated);
114 | ```
115 |
116 | Notice how we are getting the size of the file being sent. This is standard
117 | HTTP fair. What's even more interesting is how the file is being uploaded
118 | to blob storage. Node.js is stream friendly, and so is the Azure STD. All that is
119 | required is to pass the request object directly to the createBlockBlobFromStream method,
120 | and the Azure SDK does the rest. Now all we need is a curl command to upload a file.
121 |
122 | ```
123 | %curl --data-binary @anyfile.doc --request PUT http://kagemusha.tcowan.c9.io/mycontainer1/anyfile.doc
124 | ```
125 | "anyfile.doc" is assumed to be a file on your system. curl with take that file
126 | and stream it to your Node.js server, which in turn streams the file into Azure
127 | blob storage. If you want to make sure this is happening, visit https://www.myazurestorage.com and use
128 | the tool to browse your azure storage account.
129 |
130 | ####Listing your Containers and Blobs
131 | Before we discuss the listing feature, go ahead and create a few more containers so you'll have something
132 | to list.
133 |
134 | ```
135 | %curl --request PUT http://kagemusha.tcowan.c9.io/mycontainer1
136 | %curl --request PUT http://kagemusha.tcowan.c9.io/mycontainer2
137 | %curl --request PUT http://kagemusha.tcowan.c9.io/mycontainer3
138 | %curl --request PUT http://kagemusha.tcowan.c9.io/mycontainer4
139 |
140 | ```
141 |
142 | So far we've been using PUT to add new things to Azure storage. Listing is a request
143 | to "GET" a list of something, so naturally we'll use an HTTP GET. Here's the code
144 | that lists both containers and blobs:
145 |
146 | ```JavaScript
147 | else if (req.method == 'GET') {
148 | if (containerName == "") {
149 | //list containers
150 | blobService.listContainers(function (error, containers) {
151 | if (error) {
152 | console.log(error);
153 | } else {
154 | containers.forEach(function (container) {
155 | res.write(container.name + '\r\n');
156 | });
157 | }
158 | res.end();
159 | });
160 | } else {
161 | // list blobs
162 | blobService.listBlobs(containerName, function (error, blobs) {
163 | if (error) {
164 | console.log(error);
165 | } else {
166 | blobs.forEach(function (blob) {
167 | res.write(blob.name + " (" + blob.url + ')\r\n');
168 | });
169 | }
170 | res.end();
171 | });
172 | }
173 | ```
174 |
175 | Just as before, we are using the request URL to help us decide what is being requested.
176 | A URL that points to the root of our server will list all containers. A URL that points
177 | to any particular container will list BLOBS within that container. Notice how the BLOBs are listed to
178 | include a url, blob.url. That url points directly to blob storage, bypassing
179 | your Node.js server. While you can stream files from Node, it's important to know that
180 | Azure Storage will stream blobs for you. In cases where you are delivering public images, files, or content it
181 | will be more affordable to use the blobs public URL vs. streaming from your own server. Now lets look
182 | at the curl command to invoke this feature. This is the simplest of all curl commands, a simplet get on a URL.
183 | This command asks our sample Node.js server to list all containers in your azure storage account:
184 |
185 | ```
186 | %curl http://kagemusha.tcowan.c9.io
187 | ```
188 |
189 | To list all BLOBS in container "mycontainer2" use this curl command:
190 |
191 | ```
192 | %curl http://kagemusha.tcowan.c9.io/mycontainer2
193 | ```
194 |
195 | ####Deleting Containers and Blobs
196 | After what we've done till now, this will seem a bit anticlimactic. Delete will obviously
197 | be served by the HTTP verb "DELETE". Essentially we'll be using DELETE instead of PUT against
198 | similar URL strings. To keep the code short and example simple, I've used the
199 | number of path seperated values in the request URL to decide if we're deleting a container or blob:
200 |
201 | ```JavaScript
202 | if (parts.length < 3)
203 | blobService.deleteContainer(containerName, containerDeleted);
204 | else
205 | blobService.deleteBlob(containerName, blobName, blobDeleted);
206 | ```
207 |
208 | And the curl commands (assuming a blob uploaded as "blob1"):
209 |
210 | ```
211 | %curl --request DELETE http://kagemusha.tcowan.c9.io/mycontainer1/blob1
212 | %curl --request DELETE http://kagemusha.tcowan.c9.io/mycontainer1
213 | ```
214 |
--------------------------------------------------------------------------------
/sessions/hour2/02_working-with-filesystem/readme.md:
--------------------------------------------------------------------------------
1 | Working with the Filesystem
2 | ===========================
3 |
4 | In the previous lesson we built out a simple HTTP server that takes requests and
5 | sends back a simple "Hello World!" string:
6 |
7 | ```javascript
8 | var http = require('http');
9 |
10 | function requestCallback(req, res) {
11 | res.writeHead(200, {'Content-Type': 'text/plain'});
12 | res.write('Hello World!');
13 | res.end();
14 | }
15 |
16 | var server = http.createServer(requestCallback);
17 | server.listen(process.env.PORT || 80, "0.0.0.0");
18 | ```
19 |
20 | In this lesson we will do a bit more investigation of the request object, Node's
21 | [filesystem module](http://nodemanual.org/latest/nodejs_ref_guide/fs.html)
22 | and combine the two to send requested files back to the user. Let's start with
23 | the filesystem module.
24 |
25 | ### The Filesystem Module
26 |
27 | There are few points in Node.JS where the asynchronous programming model so
28 | starkly contrasts with convention than with the filesystem module. Why? In most
29 | programming languages, file access means synchronously waiting for the OS to
30 | retrieve the file from disk. In PHP for example if the file read is ongoing
31 | while another visitor requests access to the server, that user must wait for the
32 | read operation to complete.
33 |
34 | Node.JS allows for reading files asynchronously, while other visitors continue
35 | to access the server without delay.
36 |
37 | Consider this simple example, stripped first of HTTP components:
38 |
39 | ```javascript
40 | var fs = require("fs");
41 |
42 | fs.readFile("/etc/hosts", "utf-8", function(err, data) {
43 | if (err)
44 | throw err;
45 | console.log(data);
46 | });
47 |
48 | console.log("FIRST!111!!");
49 | ```
50 |
51 | Let's break this down. First, the filesystem module is loaded with
52 | `var fs = require("fs")`, giving us access to its methods via the `fs` variable.
53 | Next we call the `readFile` method with 3 parameters: the file location, encoding,
54 | and finally a callback function. The 2nd parameter - encoding - is optional; if
55 | it is omitted then the raw buffer data is returned.
56 |
57 | When the file has completely loaded, the 3rd parameter - the callback - is called
58 | with two arguments: `err` (set to null or undefined if no error occurs) and the
59 | `data` from the file.
60 |
61 | Take note of the very last line in this example: `console.log("FIRST!111!!");`.
62 | If you run this code then "FIRST!111!!" will appear _before_ the contents of the
63 | file are spit out. More than demonstrating our fantastic wit with trolling humor,
64 | this drives home our point about the asynchronous model; the event loop continues
65 | on while Node.JS loads up your file.
66 |
67 | ### Sending a Requested File
68 |
69 | Let's build on our HTTP and file knowledge to send requested files back to
70 | visitors.
71 |
72 | ```javascript
73 | var http = require('http');
74 | var fs = require('fs');
75 |
76 | http.createServer(function (req, res) {
77 | fs.readFile(__dirname + req.url, 'utf-8', function(err, data) {
78 | if (err) {
79 | res.writeHead(404, {'Content-Type': 'text/plain'});
80 | res.end('File ' + req.url + ' not found\n');
81 | }
82 | else {
83 | res.writeHead(200, {'Content-Type': 'text/plain'});
84 | res.end(data);
85 | }
86 | });
87 | }).listen(process.env.PORT || 80, "0.0.0.0");
88 | ```
89 |
90 | You can find this code in `server.js` along with this readme.md file. If you
91 | run `server.js` and request "http://[host]/readme.md" the contents of this file
92 | will be returned.
93 |
94 | How does this work? The magic is in `fs.readFile(__dirname + req.url`
95 |
96 | 1. `__dirname` is globally available and set to the directory that the currently
97 | executing script resides in
98 |
99 | 2. `req.url` is set to the path of the URL the user is requesting. If for example
100 | we requested 'http://host/readme.md' then req.url is "/readme.md". If the URL
101 | is only the domain (e.g. "http://nodejs.org") then req.url is "/"
102 |
103 | This code won't work very well if you are requesting non-plaintext files such
104 | as images. In a production file serving environment you would want to set the
105 | Content-Type through a module like [node-mime](https://github.com/bentomas/node-mime).
106 |
107 | ### Caching Files
108 |
109 | While reading files asynchronously is of great benefit to your users, you will
110 | likely want to take advantage of a simple caching method so the code isn't
111 | constantly slamming your disk for the same hilarious cat images.
112 |
113 | ```javascript
114 | var http = require('http');
115 | var fs = require('fs');
116 |
117 | var loadedFiles = {};
118 |
119 | function returnData(res, code, data) {
120 | res.writeHead(code, {'Content-Type': 'text/plain'});
121 | res.end(data);
122 | }
123 |
124 | http.createServer(function (req, res) {
125 | if (loadedFiles[req.url])
126 | return returnData(res, 200, "Cached: " + loadedFiles[req.url]);
127 |
128 | fs.readFile(__dirname + req.url, 'utf-8', function(err, data) {
129 | if (err) {
130 | returnData(res, 404, 'File ' + req.url + ' not found\n');
131 | }
132 | else {
133 | loadedFiles[req.url] = data;
134 | returnData(res, 200, data);
135 | }
136 | });
137 | }).listen(process.env.PORT || 80, "0.0.0.0");
138 | ```
139 |
140 | If the file has been found, it is cached in the loadedFiles object and referenced
141 | by the requested path. Subsequent loads of the same file will be prefixed with
142 | "Cached: " + the file data to demonstrate our cahing technique. Meanwhile we have
143 | abided by the
144 | [DRY principle](http://en.wikipedia.org/wiki/Don't_repeat_yourself) by
145 | consolidating functionality in the `returnData` method.
146 |
147 | ### File Watchers
148 |
149 | Caching is great, but what happens if a cached file has been recently updated?
150 | We don't want to send a stale copy of the file to the client. Luckily for us
151 | Node.JS has [watchers](http://nodemanual.org/latest/nodejs_ref_guide/fs.html#fs.watch).
152 | This means you can set up a watcher on a file or folder and whenever an update
153 | occurs, you get notified.
154 |
155 | Since we are only watching files in this example, we'll use the
156 | [watchFile method](http://nodemanual.org/latest/nodejs_ref_guide/fs.html#fs.watchFile).
157 | The callback takes two
158 | [fs.Stats objects](http://nodemanual.org/latest/nodejs_ref_guide/fs.Stats.html)
159 | as its arguments: `curr` and `prev`
160 |
161 | ```javascript
162 | fs.watchFile('file.html', function (curr, prev) {
163 | console.log('the current modification time is: ' + curr.mtime);
164 | console.log('the previous modification time was: ' + prev.mtime);
165 | });
166 | ```
167 |
168 | Now let's update our sample to include a file watcher on each cached file:
169 |
170 | ```javascript
171 | var http = require('http');
172 | var fs = require('fs');
173 |
174 | var loadedFiles = {};
175 |
176 | function returnData(res, code, data) {
177 | res.writeHead(code, {'Content-Type': 'text/plain'});
178 | res.end(data);
179 | }
180 |
181 | http.createServer(function (req, res) {
182 | var fullPath = __dirname + req.url;
183 |
184 | if (loadedFiles[fullPath])
185 | return returnData(res, 200, "Cached: " + loadedFiles[fullPath]);
186 |
187 | fs.readFile(fullPath, 'utf-8', function(err, data) {
188 | if (err) {
189 | returnData(res, 404, 'File ' + req.url + ' not found\n');
190 | }
191 | else {
192 | // Start watching the file for changes
193 | fs.watchFile(fullPath, function(curr, prev) {
194 | // We only want to know when the "modified" time was updated
195 | if (curr.mtime.getTime() != prev.mtime.getTime()) {
196 | console.log("Cached file [" + fullPath + "] was updated");
197 |
198 | // Read in the file again
199 | fs.readFile(fullPath, 'utf-8', function(err, data) {
200 | if (!err)
201 | loadedFiles[fullPath] = data;
202 | });
203 | }
204 | });
205 |
206 | loadedFiles[fullPath] = data;
207 | returnData(res, 200, data);
208 | }
209 | });
210 | }).listen(process.env.PORT || 80, "0.0.0.0");
211 | ```
212 |
213 | The bulk of our changes start with the comment "//Start watching...". The watcher
214 | callback will fire every time the file is accessed (`curr.atime` is changed) so
215 | we only want to know when the file's content was modified.
216 |
217 | `watchFile` takes an optional 2nd argument `[options]` where you can specify two
218 | properties: `persistent` and `interval`. `persistent` indicates whether the
219 | process should continue to run as long as files are being watched. `interval`
220 | dictates how often the target should be polled, in milliseconds.
221 |
222 | _Fair warning: This technique was for demonstration only and should **not** be used
223 | in a large scale application serving thousands of files. It is quite useful for
224 | other scenarios, however. For ex., imagine you just updated the source for your
225 | application and want to restart the server. A separate node app could watch your
226 | app's `server.js`, see a change was made, then kill and restart the process._
227 |
228 | In the next lesson we will take a step back and look at Node apps from a
229 | conceptual point of view.
--------------------------------------------------------------------------------
/sessions/hour3/01_working-with-a-database/readme.md:
--------------------------------------------------------------------------------
1 | Windows Azure Table Storage
2 | --------
3 | What is Azure Table Storage, and how does one use it from Node. This is paraphrased from https://www.windowsazure.com/en-us/develop/nodejs/how-to-guides/table-services/.
4 |
5 | #### What is Windows Azure Table Storage?
6 |
7 | Azure Table Storage is a NoSQL type of database which can store enormous amounts of data. Data are replicated three times within the same datacenter, and tables are geo-replicated between two datacenters hundreds of miles apart. Tables can be accessed from within Azure, or from a service running elsewhere. It supports a RESTful API for direct access, or one can use a wrapper for access from various environments including Node.
8 |
9 | A single storage account can contain multiple tables. Each table can contain multiple entities. An entity is a set of properties, and can be up to 1 MB. A property is a key-value pair.
10 |
11 | #### What are partition keys and row keys?
12 |
13 | Partition and row keys are part of an entity. Entities with the same partition key are stored together and can be queried quickly. A row key is a unique identifier for each entity. At the very least, an entity will have a PartitionKey, RowKey, and a Timestamp which is added by Azure and can be ignored.
14 |
15 | Quick links to code snippets
16 | --------
17 |
18 | #### How to create an Azure table
19 |
20 | https://gist.github.com/1813187
21 |
22 | #### How to add an entity to a table
23 |
24 | https://gist.github.com/1813271
25 |
26 | #### How to update an entity
27 |
28 | https://gist.github.com/1813285
29 |
30 | #### How to change a group of entities
31 |
32 | https://gist.github.com/1813293
33 |
34 | #### How to query for an entity
35 |
36 | https://gist.github.com/1813302
37 |
38 | #### How to query a set of entities
39 |
40 | https://gist.github.com/1813308
41 |
42 | #### How to query a subset of entity properties
43 |
44 | NB: This won't work in the storage emulator.
45 | https://gist.github.com/1813327
46 |
47 | #### How to delete an entity
48 |
49 | https://gist.github.com/1813337
50 |
51 | #### How to delete a table
52 |
53 | https://gist.github.com/1813345
54 |
55 |
56 | Demo
57 | --------
58 | Let's build a working demo which uses Azure tables. When building a website, you'll probably want to use a proper web framework such as Express, but for our purposes we'll keep things deliberately simple. We'll start with something based on the Hello World example from http://nodejs.org. Note that the arguments inside `listen()` have changed:
59 |
60 | var http = require('http');
61 | http.createServer(function (req, res) {
62 | res.writeHead(200, {'Content-Type': 'text/plain'});
63 | res.end('Hello World\n');
64 | }).listen(port);
65 |
66 | Let's start by importing some modules we'll be using. Add these lines at the top of the file:
67 |
68 | var azure = require('azure');
69 | var formidable = require('formidable'), util = require('util');
70 | var uuid = require('node-uuid');
71 |
72 | `azure` is the Azure SDK, `formidable` is an HTTP forms processing module, `util` contains various utilities, and `node-uuid` will be used to generate UUIDs for the data we'll be putting into Azure Tables.
73 |
74 | Next add this line to set up the application for running either locally, in the Cloud9 IDE, or when deployed on Azure:
75 |
76 | var port = process.env.PORT || 1337; // for C9, Azure, or local.
77 |
78 | Cloud9 and Azure use `process.env.PORT`. If that exists, `port` will be set approriately. Otherwise, port 1337 will be used for running locally.
79 |
80 | Next, add these lines which pertain to Azure:
81 |
82 | var account = azure.ServiceClient.DEVSTORE_STORAGE_ACCOUNT;
83 | var accountKey = azure.ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY;
84 | var tableHost = azure.ServiceClient.DEVSTORE_TABLE_HOST;
85 |
86 | if (process.env.C9_PORT) { // Test if we're running on Cloud9. Change these to your own credentials.
87 | account = 'c9demo';
88 | accountKey = '';
89 | tableHost = 'http://table.core.windows.net';
90 | }
91 |
92 | var tableService = azure.createTableService(account, accountKey, tableHost);
93 | //tableService.logger = new azure.Logger(azure.Logger.LogLevels.DEBUG);
94 |
95 | var tableName = 'wines'; // Name your table here.
96 |
97 | Make sure everything still runs at this point. Be sure to add the modules we'll be using with `npm install`. If you need to debug your connection to Azure, uncomment the `tableService.logger` line. This will display lots of information in the console when you run the app.
98 |
99 | If the application runs, let's proceed. Inside `http.createServer()`, add this as the first line of the callback:
100 |
101 | if (req.url === '/favicon.ico') return;
102 |
103 | Web browsers attempt to fetch favicons, and this line prevents our application from handling these requests.
104 |
105 | Now let's add a form for entering in details about the wine we're drinking. Remember that we're still inside `http.createServer()`:
106 |
107 | res.writeHead(200, { 'Content-Type': 'text/html' });
108 | res.write('
Wine Notebook
');
109 | var form = ''
123 | res.write(form + '');
124 |
125 | Note that the `Content-Type` header is `text/html`, not `text/plain` as you may have seen in other examples.
126 |
127 | The `AdditionalKey` and `AdditionalValue` fields are included to illustrate a fundamental concept in Azure Table Storage. If you think of Azure Tables as traditional relational database tables, you'll miss the real point that Azure Tables are collections of entities, which have key/value properties. There's no schema to adhere to, and entities can have differing properties in any given table.
128 |
129 | To further illustrate this point, we'll display our data in an unordered list. This emphasizes the schemaless key/value store nature of Azure Table Storage. If we displayed data in a table with columns and rows, you might continue to think of Azure Table Storage as just limited to that paradigm. Start by adding these lines:
130 |
131 | // read & display data from table
132 | var query = azure.TableQuery.select().from(tableName);
133 | tableService.queryEntities(query, entitiesQueriedCallback);
134 |
135 | The query will select all entities from our table. You can [query for a single entity](https://gist.github.com/1813302) using a slightly different syntax. In that case, just pass `queryEntity()` a `PartitionKey` and `RowKey` which uniquely identifies an entity, along with the table name and callback. You can also [query for a set of entities](https://gist.github.com/1813308) using `where()`.
136 |
137 | Now lets add `entitiesQueriedCallback`:
138 |
139 | function entitiesQueriedCallback(error, serverEntities) {
140 | if (error === null) {
141 | res.write('
');
142 | for (var index in serverEntities) {
143 | res.write('
');
162 | } else {
163 | res.end('Could not query entities: ' + error.code);
164 | console.log('Could not query entities: ' + error.code);
165 | }
166 | }
167 |
168 | This function is called by `tableService.queryEntities()`. If the call to the database succeeded without an error, then the results are displayed in an unordered list. Again, this emphasizes the schemaless nature of Azure Table Storage. In a production application, you'll of course want to make this look better.
169 |
170 | First you'll notice an HTML form with two hidden input fields. These store an entity's `PartitionKey` and `RowKey`, which uniquely identify an entity and will be used by code to delete an entity. We'll visit this again later.
171 |
172 | Take a look at the `for` loop. Entities contain a `Timestamp` property inserted by Azure. We'll want to ignore this. We'll also want to ignore other system properties such as the etag.
173 |
174 | The `PartitionKey` is used as the first list item. As covered earlier, entities with the same `PartitionKey` will be stored together in Azure. Simply shard your data on various partition keys, and Azure will take care of moving other entities around to avoid database contention. For our wine application, the `PartitionKey` is the type of wine, be it red, white, rose, or something else. This means that red wines will be stored separately from white wines, which would help with scaling if this application were to see heavy traffic.
175 |
176 | Make sure that the application still runs at this point. There's no data to display yet, however.
177 |
178 | Let's write code to handle the form submission from adding a wine:
179 |
180 | if (req.url == '/addWine' && req.method.toLowerCase() == 'post') {
181 | // Read http://nodebeginner.org to learn more about formidable.
182 | var form = new formidable.IncomingForm();
183 | form.parse(req, function (err, fields, files) {
184 | res.writeHead(200, { 'content-type': 'text/plain' });
185 | res.write('Received this via HTTP POST:\n\n');
186 | res.write(util.inspect({ fields: fields }) + '\n\n');
187 | res.end('Check console for status of adding this, and reload "/" to see it.');
188 | var wine = fields;
189 | wine['RowKey'] = uuid();
190 | wine['TastedOn'] = new Date();
191 | // Add additional keys and values to the entity, making sure they're not empty. Then delete properties which
192 | // were parsed from the incoming form data.
193 | if (fields.AdditionalKey1 !== '') wine[fields.AdditionalKey1] = fields.AdditionalValue1;
194 | if (fields.AdditionalKey2 !== '') wine[fields.AdditionalKey2] = fields.AdditionalValue2;
195 | if (fields.AdditionalKey3 !== '') wine[fields.AdditionalKey3] = fields.AdditionalValue3;
196 | delete wine.AdditionalKey1; delete wine.AdditionalValue1;
197 | delete wine.AdditionalKey2; delete wine.AdditionalValue2;
198 | delete wine.AdditionalKey3; delete wine.AdditionalValue3;
199 | tableService.insertEntity(tableName, wine, entityInsertedCallback);
200 | });
201 | return;
202 | }
203 |
204 | When you click the "Add" button, the form is posted back to /addWine and the above code is run. It uses a module called Formidable which handles all the tricky business of parsing an HTML form. This code displays the submitted fields with `util.inspect()`. In production code, you'll probably want to redirect the user back to "/".
205 |
206 | To add an entity to Azure Table Storage, simply create a JavaScript object with the properties you want to include. Each entity must be unique, so be certain to use a unique combination of `PartitionKey`s and `RowKey`s. Don't set the `Timestamp` as that's done automatically. The above code uses `var wine = fields` as a starting point. The `PartitionKey` is actually a form field and is therefore already set. `wine['RowKey']` is set to a UUID so that each entity is unique. Remember that the `PartitionKey` in our example is just the type of wine, be it red, white, etc. You need a unique `RowKey` to make the entity unique. `wine['TastedOn']` is automatically set to the current date.
207 |
208 | The next few lines parse the additional keys and values. Remember that these are included to illustrate the schemaless nature of Azure Table Storage. In our app, perhaps a user wants to include a property about which restaurant they were in, or what they were eating at the time, or anything else which comes to mind.
209 |
210 | You can't add empty keys to an Azure table, so we check for that. If a key is not empty, that means a user typed something in. `wine[fields.AdditionalKey1] = fields.AdditionalValue1` creates a property on the `wine` object with the key and value being what the user typed in. After this, the `AdditionalKey` and `AdditionalValue` properties must be deleted, or else they'll be added to the entity also. Finally, the entity is inserted with a call to `insertEntity()`. Try running the app and adding a wine. Reload '/' to see what's added. Play around with including additional properties. Again, an Azure table is just a collection of entities with various properties. Entities in a given table are not required to all have the same properties.
211 |
212 | Lastly, let's add code to handle deleting an entity.
213 |
214 | if (req.url == '/deleteWine' && req.method.toLowerCase() == 'post') {
215 | // delete a wine
216 | var form = new formidable.IncomingForm();
217 | form.parse(req, function (err, fields, files) {
218 | res.writeHead(200, { 'content-type': 'text/plain' });
219 | res.write('Received this via HTTP POST:\n\n');
220 | res.write(util.inspect({ fields: fields }) + '\n\n');
221 | res.end('Check console for status of deleting this, and reload "/" to see it.');
222 | var wine = fields;
223 | tableService.deleteEntity(tableName, wine, entityDeletedCallback);
224 | });
225 | return;
226 | }
227 |
228 | This is almost the same code as what we used for adding an entity. You simply create a JavaScript object with a `PartitionKey` and `RowKey`, and then pass it to `deleteEntity()`. Note that instead of creating a new object with`var wine = fields`, we could have simply passed in `fields` to `deleteEntity()` since `fields` already contains the `PartitionKey` and `RowKey` we want to delete.
229 |
230 | If you want to modify an entity instead of deleting it, [the code is the same except call `updateEntity()` instead](https://gist.github.com/1813285). Naturally, use a different callback. The take-home lesson is that to delete or modify an entity, simply create a JavaScript object with the appropriate `PartitionKey` and `RowKey` and then call `deleteEntity()` or `updateEntity()`. When displaying an entity you wish to update in an HTML form, simply use two hidden input fields to store the `PartitionKey` and `RowKey`. Of course, you'll have to validate that these values have not been tampered with before accepting them before passing your object to `deleteEntity()` or `updateEntity()`.
231 |
232 |
--------------------------------------------------------------------------------