Skip to content
Browse files

First attempt at a component-based express app ala TJ

  • Loading branch information...
0 parents commit eca8aea792e44cfbc57334c74cfbbb0b3045307d @hunterloftis committed
2 .gitignore
@@ -0,0 +1,2 @@
+node_modules
+
8 Makefile
@@ -0,0 +1,8 @@
+start:
+ npm start
+
+open:
+ (sleep 2 && open http://localhost:3000) &
+ npm start
+
+.PHONY: start open
0 Readme.md
No changes.
24 app.js
@@ -0,0 +1,24 @@
+var express = require('express');
+var path = require('path');
+
+var config = require('./package.json').publicConfig;
+
+var logger = require('./lib/logger');
+var balance = require('./lib/balance');
+var middleware = require('./lib/globalMiddleware');
+var users = require('./lib/users');
+var dashboard = require('./lib/dashboard');
+var flash = require('./lib/flash');
+
+balance(function() {
+ var app = express();
+
+ logger(app, config);
+ middleware(app, config);
+ flash(app);
+ users(app);
+ dashboard(app);
+
+ app.listen(config.http_port);
+ console.log("Listening on", config.http_port);
+});
18 lib/balance/index.js
@@ -0,0 +1,18 @@
+var cluster = require('cluster');
+var os = require('os');
+
+module.exports = function balance(init) {
+ return cluster.isMaster? initMaster() : init();
+};
+
+function initMaster() {
+ cluster.on('death', function(worker) {
+ cluster.fork();
+ });
+
+ var workerCount = os.cpus().length;
+ var i = workerCount;
+ while(i--) {
+ cluster.fork();
+ }
+}
10 lib/dashboard/dashboard.jade
@@ -0,0 +1,10 @@
+extends ../shared/layout
+
+block content
+ .dash-header
+ .half-drop
+ h1.dash-title Dashboard
+ .half-drop
+ ul.h-menu
+ li!= user.email
+ li: a(href='/signout') Sign out
10 lib/dashboard/index.js
@@ -0,0 +1,10 @@
+var path = require('path');
+
+module.exports = function(app) {
+
+ app.get('/dashboard', app.user.loggedIn, function(req, res) {
+ return res.render(path.join(__dirname, 'dashboard'), {
+ user: req.session.user
+ });
+ });
+};
20 lib/flash/index.js
@@ -0,0 +1,20 @@
+module.exports = function(app) {
+
+ app.use(function(req, res, next) {
+ req.flash = function(message) {
+ var messages = req.session.messages || [];
+ messages.push(message);
+ req.session.messages = messages;
+ };
+ return next();
+ });
+
+ app.use(function(req, res, next) {
+ res.locals.getMessages = function() {
+ var messages = req.session.messages || [];
+ req.session.messages = [];
+ return messages;
+ };
+ return next();
+ });
+};
5 lib/flash/messages.jade
@@ -0,0 +1,5 @@
+mixin messages()
+ ul.flash-messages
+ - messages = getMessages()
+ - each message in messages
+ li!= message
51 lib/globalMiddleware/index.js
@@ -0,0 +1,51 @@
+var express = require('express');
+var connectTimeout = require('connect-timeout');
+var RedisStore = require('connect-redis')(express);
+var path = require('path');
+var stylus = require('stylus');
+
+module.exports = function(app, config) {
+
+ app.set('view engine', 'jade');
+
+ // Timeouts
+ var timeouts = connectTimeout({
+ throwError: true,
+ time: config.request_timeout
+ });
+
+ // Sessions
+ var sessions = express.session({
+ store: new RedisStore()
+ });
+
+ // Stylus
+ function compile(str, path) {
+ return stylus(str)
+ .set('compress', config.stylus_compress)
+ .set('filename', path);
+ }
+ var styles = stylus.middleware({
+ src: path.join(__dirname, '..'),
+ dest: path.join(__dirname, '../../public'),
+ debug: config.stylus_debug,
+ compile: compile,
+ force: config.stylus_force
+ });
+
+ // Cookies
+ var cookies = express.cookieParser(config.session_secret);
+
+ // Static files
+ var staticFiles = express['static'](path.join(__dirname, '../../public'));
+
+
+ app.use(timeouts); // request timeouts
+ app.use(express.compress()); // gzip
+ app.use(styles); // css
+ app.use(staticFiles); // 'public'
+ app.use(cookies); // req.cookies
+ app.use(sessions); // req.session
+ app.use(express.bodyParser()); // req.body & req.files
+ app.use(express.methodOverride()); // '_method' property in body (POST -> DELETE / PUT)
+};
9 lib/logger/index.js
@@ -0,0 +1,9 @@
+module.exports = function(app, config) {
+
+ app.use(function log(req, res, next) {
+ if (config.log_requests) {
+ console.log(req.method + ' ' + req.url);
+ }
+ return next();
+ });
+};
82 lib/shared/global.styl
@@ -0,0 +1,82 @@
+*
+ box-sizing border-box
+ margin 0
+ padding 0
+
+ul
+ list-style-type none
+
+body
+ background #eee
+ font-family helvetica, arial, sans-serif
+ color #333
+
+h1
+ font-weight 100
+ color #666
+ font-size 32px
+ line-height 32px
+
+.flash-messages
+ li
+ background #faffbd
+ padding 10px 20px
+ font-size 14px
+ border-bottom solid 1px #ccc
+
+.title-bar
+ padding 20px 0
+
+.form-gateway
+ width 400px
+ padding 15px 30px
+ background #fafafa
+ box-shadow 0 1px 10px rgba(0, 0, 0, 0.25)
+ margin 30px auto
+
+.input-text
+ border-radius 3px
+ font-size 18px
+ color #666
+ padding 10px 20px
+ background-color #fff
+ width 100%
+ margin 0 0 10px 0
+ border solid 1px #ddd
+
+.button-bar
+ padding-top 10px
+ text-align right
+
+.button-submit
+ font-size 18px
+ padding 10px 20px
+ border solid 1px #ddd
+ background #eee
+ border-radius 3px
+ margin 0 0 10px 0
+ display inline-block
+
+.dash-header
+ width 100%
+ background #fff
+ padding 20px 20px 10px 20px
+ vertical-align bottom
+
+.half-drop
+ width 50%
+ display inline-block
+ vertical-align bottom
+
+.h-menu
+ text-align right
+ > li
+ display inline-block
+ margin-right 10px
+ > a
+ display inline-block
+
+.micro
+ font-size 14px
+ color #999
+ padding 5px
16 lib/shared/layout.jade
@@ -0,0 +1,16 @@
+include ../flash/messages
+
+!!! 5
+html
+ head
+ meta(charset="utf-8")
+ title Components test
+ block styles
+ link(rel='stylesheet', type='text/css', href='/shared/global.css')
+
+body
+ mixin messages()
+
+ block content
+
+ block scripts
50 lib/users/index.js
@@ -0,0 +1,50 @@
+var path = require('path');
+
+var UserModel = require('./userModel');
+
+module.exports = function(app) {
+
+ app.user = {
+ loggedIn: function(req, res, next) {
+ if (req.session.user) {
+ return next();
+ }
+ req.session.redirect = req.url;
+ return res.redirect('/404');
+ }
+ };
+
+ app.get('/', function signIn(req, res) {
+ if (req.session.user) {
+ return res.redirect('/dashboard');
+ }
+ return res.render(path.join(__dirname, 'signin'));
+ });
+
+ app.post('/signin', function(req, res) {
+ var creds = {
+ email: req.body.email,
+ pass: req.body.password
+ };
+ UserModel.authenticate(creds, function(err, user) {
+ if (user) {
+ req.session.user = user;
+ return res.redirect('/');
+ }
+ else {
+ req.flash('Sorry, that username or password was not found.');
+ return res.redirect('/');
+ }
+ });
+ });
+
+ app.all('/signout',
+ function(req, res, next) {
+ req.session.regenerate(next);
+ },
+ function(req, res, next) {
+ req.flash("You have been signed out.");
+ return res.redirect('/');
+ }
+ );
+};
11 lib/users/signin.jade
@@ -0,0 +1,11 @@
+extends ../shared/layout
+
+block content
+ form.form-gateway(action='/signin', method='post')
+ .title-bar
+ h1 Who are you?
+ input.input-text(type='text', name='email')
+ input.input-text(type='password', name='password')
+ p.micro test@dummy.com:bacon
+ .button-bar
+ button.button-submit(type='submit') Sign in
22 lib/users/userModel.js
@@ -0,0 +1,22 @@
+var users = {
+ 'hunter@hunterloftis.com': 'password',
+ 'test@dummy.com': 'bacon'
+};
+
+module.exports = {
+ authenticate: function(creds, done) {
+ if (!creds.email && creds.pass) {
+ return done(new Error('Email and pass required'));
+ }
+ var pass = users[creds.email];
+ if (!pass) {
+ return done(new Error('No such user'));
+ }
+ if (pass === creds.pass) {
+ return done(undefined, {
+ email: creds.email
+ });
+ }
+ return done('Invalid password');
+ }
+}
27 package.json
@@ -0,0 +1,27 @@
+{
+ "name": "node-components-test",
+ "version": "0.0.1",
+ "author": "Hunter Loftis <hunter@hunterloftis.com>",
+ "dependencies": {
+ "express": "git://github.com/visionmedia/express.git",
+ "connect-timeout": "*",
+ "connect-redis": "*",
+ "jade": "*",
+ "stylus": "*"
+ },
+ "devDependencies": {
+
+ },
+ "scripts": {
+ "start": "node app"
+ },
+ "publicConfig": {
+ "http_port": 3000,
+ "request_timeout": 10000,
+ "session_secret": "bp2secret",
+ "log_requests": false,
+ "stylus_compress": true,
+ "stylus_debug": true,
+ "stylus_force": false
+ }
+}
16 public/shared/global.css
@@ -0,0 +1,16 @@
+*{box-sizing:border-box;margin:0;padding:0}
+ul{list-style-type:none}
+body{background:#eee;font-family:helvetica,arial,sans-serif;color:#333}
+h1{font-weight:100;color:#666;font-size:32px;line-height:32px}
+.flash-messages li{background:#faffbd;padding:10px 20px;font-size:14px;border-bottom:solid 1px #ccc}
+.title-bar{padding:20px 0}
+.form-gateway{width:400px;padding:15px 30px;background:#fafafa;box-shadow:0 1px 10px rgba(0,0,0,0.25);margin:30px auto}
+.input-text{border-radius:3px;font-size:18px;color:#666;padding:10px 20px;background-color:#fff;width:100%;margin:0 0 10px 0;border:solid 1px #ddd}
+.button-bar{padding-top:10px;text-align:right}
+.button-submit{font-size:18px;padding:10px 20px;border:solid 1px #ddd;background:#eee;border-radius:3px;margin:0 0 10px 0;display:inline-block}
+.dash-header{width:100%;background:#fff;padding:20px 20px 10px 20px;vertical-align:bottom}
+.half-drop{width:50%;display:inline-block;vertical-align:bottom}
+.h-menu{text-align:right;}
+.h-menu > li{display:inline-block;margin-right:10px;}
+.h-menu > li > a{display:inline-block}
+.micro{font-size:14px;color:#999;padding:5px}

0 comments on commit eca8aea

Please sign in to comment.
Something went wrong with that request. Please try again.