diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/README.md b/README.md
index cbba4e2..db68ccc 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,13 @@
# Real Time Web With Node.js
-This is the code for the [Real Time Web with Node.js](http://www.codeschool.com/courses/real-time-web-with-nodejs) demo app.
+This is the code for the [Real Time Web with Node.js][realtime] demo app, __chattr__.
+
+## Getting Started
+
+To run the server you'll need [Node.js](http://nodejs.org) and [Redis](http://redis.io) installed. Then run the following commands:
+
+1. `$ npm install`
+1. `$ redis-server &`
+1. `$ node app.js`
+
+At this point you will be able to go to the address `http://localhost:8000/` in your web browser to interact with the app.
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..9f164df
--- /dev/null
+++ b/app.js
@@ -0,0 +1,106 @@
+var express = require('express');
+var app = express();
+var server = require('http').createServer(app);
+var io = require('socket.io').listen(server);
+var redisClient = require('./lib/redisClient.js');
+
+var port = process.env.PORT || 8000;
+server.listen(port);
+console.log('Server listening on port %d', port);
+
+/**
+ * Serve static files from `public`
+ */
+
+app.use(express.static(__dirname + '/public'));
+
+/**
+ * Handle all routes to the webserver
+ */
+
+app.get('/*', function (req, res) {
+ res.sendfile(__dirname + '/index.html');
+});
+
+/**
+ * Store a message in redis
+ *
+ * @function storeMessage
+ * @param {String} name
+ * @param {Object} data
+ */
+
+function storeMessage (name, data) {
+ var message = JSON.stringify({name:name, data:data});
+
+ // store up to 10 messages
+ redisClient.lpush('messages', message, function(error) {
+ if (error) throw error;
+ redisClient.ltrim('messages', 0, 10);
+ });
+};
+
+/**
+ * Handle connnection Websockets
+ */
+
+io.sockets.on('connection', function (client) {
+
+
+ /**
+ * When users join, set their nickname and broadcast they are here
+ * Add the user to redis set `chatters`
+ */
+
+ client.on('join', function(name) {
+ client.set('nickname', name);
+ client.broadcast.emit('add chatter', name);
+ redisClient.sadd('chatters', name);
+
+ /**
+ * Add all current chatters to the current client’s chatters list
+ */
+
+ redisClient.smembers('chatters', function(error, names) {
+ names.forEach(function(name) {
+ client.emit('add chatter', name);
+ });
+ });
+
+ /**
+ * Add latest chat messages to current client
+ */
+
+ redisClient.lrange('messages', 0, -1, function( error, messages) {
+ messages = messages.reverse();
+
+ messages.forEach(function(message) {
+ message = JSON.parse(message);
+ client.emit('messages',message.name + ' : ' + message.data);
+ });
+ });
+ });
+
+ /**
+ * When a message comes through, get the name and broadcast the messsage
+ * Store the message after we get the nicname
+ */
+
+ client.on('messages', function(message) {
+ client.get('nickname',function(error, name) {
+ storeMessage(name, message);
+ client.broadcast.emit('messages', name + ' : ' + message);
+ });
+ });
+
+ /**
+ * When a user disconnects, get their name and broadcast they left
+ */
+
+ client.on('disconnect', function(name) {
+ client.get('nickname', function(error, name) {
+ client.broadcast.emit('remove chatter', name);
+ redisClient.srem('chatters', name);
+ });
+ });
+});
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..7dd3b04
--- /dev/null
+++ b/index.html
@@ -0,0 +1,22 @@
+
+
+
+ chattr
+
+
+
+
chattr
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/redisClient.js b/lib/redisClient.js
new file mode 100644
index 0000000..734e6c5
--- /dev/null
+++ b/lib/redisClient.js
@@ -0,0 +1,8 @@
+var redis = require('redis');
+var redisClient = redis.createClient();
+
+redisClient.on('error', function(err) {
+ console.log( 'error : ' + err );
+});
+
+module.exports = redisClient;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7ba0a40
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "chattr",
+ "description": "A simple chat server based on the Real Time Web with Node.js codeschool class.",
+ "version": "0.1.0",
+ "author": {
+ "name": "dan entous",
+ "email": "contact@pennlinepublishing.com"
+ },
+ "contributors": [
+ {
+ "name": "dan entous",
+ "email": "contact@pennlinepublishing.com"
+ }
+ ],
+ "dependencies": {
+ "redis": "^0.10.1",
+ "express": "^3.4.8",
+ "socket.io": "^0.9.16"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/dan-nl/realtimewebnode.git"
+ },
+ "scripts": {
+ "start": "node app.js"
+ },
+ "readme": "# Real Time Web With Node.js\n\nThis is the code for the [Real Time Web with Node.js](http://www.codeschool.com/courses/real-time-web-with-nodejs) demo app.\n\n## redis\n\nYou’ll need to make sure you have a redis server running in order to use the application see the [Redis Quick Start](http://redis.io/topics/quickstart) for information on installing redis.\n\n## Install\n\nThe fastest way to get __chattr__ up and running:\n\n * On Mac or Linux, make sure you have [nodejs][nodejs] and [npm][npm] installed\n\n```\ngit clone git@github.com:dan-nl/realtimewebnode.git\ncd realtimewebnode\nnpm install\nnpm start\n```",
+ "readmeFilename": "Readme.md",
+ "_id": "chattr@0.1.0",
+ "_from": "chattr@"
+}
diff --git a/public/app.css b/public/app.css
new file mode 100644
index 0000000..5f58f7d
--- /dev/null
+++ b/public/app.css
@@ -0,0 +1,23 @@
+body { font-family: sans-serif; color: #333; }
+
+#chatters { width: 100px; margin: 0 1em 0 0; float: left; list-style: none; }
+
+#chatters, #chat-console { height: 300px; overflow: auto; padding: 1%;}
+
+#chat-console, #chatters, #chat-form input[type=text] { border: 1px solid #ccc; }
+
+#chat-console, #chat-form { margin-right: 7%; }
+
+#chat-form { margin-top: 1em; text-align: right; clear: both; }
+
+#chat-form input[type=text] { width: 80%; font-size: 120%; padding: 1%; }
+
+.connected { color: #8b0000; }
+
+/* http://hellohappy.org/css3-buttons/ */
+input[type=submit] { background-color: #eeeeee; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #cccccc)); background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); background-image: -o-linear-gradient(top, #eeeeee, #cccccc); background-image: linear-gradient(top, #eeeeee, #cccccc); border: 1px solid #ccc; border-bottom: 1px solid #bbb; border-radius: 3px; color: #333; font-size: 100%; padding: 8px 0; text-align: center; text-shadow: 0 1px 0 #eee; width: 150px; }
+
+input[type=submit]:hover { background-color: #dddddd; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #dddddd), color-stop(100%, #bbbbbb)); background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); background-image: linear-gradient(top, #dddddd, #bbbbbb); border: 1px solid #bbb; border-bottom: 1px solid #999; cursor: pointer; text-shadow: 0 1px 0 #ddd; }
+
+input[type=submit]:active { border: 1px solid #aaa; border-bottom: 1px solid #888; -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; }
+
diff --git a/public/app.js b/public/app.js
new file mode 100644
index 0000000..ed39a18
--- /dev/null
+++ b/public/app.js
@@ -0,0 +1,56 @@
+;(function(){
+
+ var socket = io.connect();
+ var chatters = document.getElementById('chatters');
+ var chat_input = document.getElementById('chat-input');
+ var chat_console = document.getElementById('chat-console');
+ var nickname;
+
+ function removeChatter(name) {
+ var current_chatters = document.querySelectorAll('[data-name]'),
+ i;
+
+ for (var i = 0; i < current_chatters.length; i += 1) {
+ if (name === current_chatters[i].getAttribute('data-name')) {
+ current_chatters[i].parentNode.removeChild(current_chatters[i]);
+ break;
+ };
+ };
+ };
+
+ function insertChatter(name) {
+ var new_chatter = document.createElement('li');
+ new_chatter.setAttribute('data-name', name);
+ new_chatter.setAttribute('class', 'connected');
+ new_chatter.innerHTML = name;
+ chatters.appendChild(new_chatter);
+ };
+
+ function insertMessage(message) {
+ var new_message = document.createElement('span');
+ new_message.innerHTML = message + ' ';
+ chat_console.appendChild(new_message);
+ };
+
+ document.getElementById('chat-form').onsubmit = function(e) {
+ e.preventDefault();
+ socket.emit('messages', chat_input.value);
+ insertMessage(nickname + ' : ' + chat_input.value);
+ chat_input.value = null;
+ };
+
+ socket.on('messages', function(data) {
+ insertMessage(data);
+ });
+
+ socket.on('connect', function(data) {
+ chat_console.innerHTML = 'connected to the chat socket ';
+ nickname = prompt('what is your nickanme?');
+ socket.emit('join', nickname);
+ });
+
+ socket.on('add chatter', insertChatter);
+ socket.on('remove chatter', removeChatter);
+
+}());
+