Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve configurability and make redis optional #5

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ derby-starter
=============

A generic server for use with Derby apps

This project exists purely to DRY [derby-examples](https://github.com/codeparty/derby-examples).
It is not intended to be used with all Derby apps.

Do not use this in a production environment. Redis support is disabled.
8 changes: 8 additions & 0 deletions config/development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mongo": {
"db": "derby-starter-development"
},
"session": {
"secret": "USE A REAL SECRET WHEN YOU DEPLOY"
}
}
137 changes: 137 additions & 0 deletions config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
var convict = require('convict');
var path = require('path');
var url = require('url');
var check = require('convict/node_modules/validator').check

module.exports = function config(options, cb) {
envAlias('MONGOHQ_URL', 'MONGO_URL');
envAlias('MONGOLAB_URL', 'MONGO_URL');

envAlias('OPENREDIS_URL', 'REDIS_URL');
envAlias('REDISTOGO_URL', 'REDIS_URL');
envAlias('REDISCLOUD_URL', 'REDIS_URL');

var config = convict({
env: {
doc: 'The application environment',
format: [
'development',
'test',
'production'
],
default: 'development',
env: 'NODE_ENV'
},
port: {
doc: 'The ipv4 address to bind.',
format: 'port',
default: 3000,
env: 'PORT'
},
ip: {
doc: 'The ipv4 address to bind.',
format: 'ipaddress',
default: '0.0.0.0',
env: 'IP'
},
static: {
doc: 'The folders express.static should serve.',
format: mountPoints,
default: path.resolve('public')
},
mongo: {
url: {
format: mongoUrl,
default: undefined,
env: 'MONGO_URL',
arg: 'mongo-url' // eg. $ npm start --mongo-url "mongodb://127.0.0.1:27017/whatever"
},
host: {
format: String,
default: 'localhost',
env: 'MONGO_HOST'
},
port: {
format: 'port',
default: 27017,
env: 'MONGO_PORT'
},
db: {
format: String,
default: undefined,
env: 'MONGO_DB'
},
opts: {
safe: {
format: Boolean,
default: true,
env: 'MONGO_SAFE'
}
}
},
session: {
secret: {
doc: 'Secret used to sign the session cookie',
format: secret(16),
default: undefined,
env: 'SESSION_SECRET'
}
}
});

config.load(options);
config.loadFile(__dirname + '/' + config.get('env') + '.json');

if (config.has('mongo.url') === false) setMongoUrl(config);
if (cb) cb(null, config);

config.validate();
return config;
}

function setMongoUrl(config) {
var m = config.get('mongo');
config.set('mongo.url', 'mongodb://' + m.host + ':' + m.port + '/' + m.db);
}

function secret(length) {
return function (val) {
check(val).notEmpty().len(length);
}
}

function optionalSecret(length) {
return function (val) {
if (!!val) secret(length)(val);
}
}

function checkUrl(protocol, attributes) {
return function (val) {
var u = url.parse(val);
check(u.protocol, 'Wrong protocol.').equals(protocol)
attributes.forEach(function (attr) {
check(u[attr]).notEmpty()
})
}
}

function mongoUrl(val) {
checkUrl('mongodb:', ['hostname','port','path'])(val)
}

function envAlias(source, target) {
if (process.env[source]) process.env[target] = process.env[source];
}

function mountPoints(val) {
if (Array.isArray(val)) {
val.forEach(function (el) {
check(el.route).contains('/');
check(el.dir).notEmpty();
})
} else {
check(val).notEmpty();
}
}

8 changes: 8 additions & 0 deletions config/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mongo": {
"url": "mongodb://localhost:27017/derby-starter-test"
},
"session": {
"secret": "USE A REAL SECRET WHEN YOU DEPLOY"
}
}
8 changes: 5 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ exports.run = run;

function run(app, options, cb) {
options || (options = {});
var port = options.port || process.env.PORT || 3000;
var config = require('../config')(options);
var port = config.get('port');
var ip = config.get('ip');

function listenCallback(err) {
console.log('%d listening. Go to: http://localhost:%d/', process.pid, port);
cb && cb(err);
}
function createServer() {
if (typeof app === 'string') app = require(app);
require('./server').setup(app, options, function(err, expressApp) {
require('./server').setup(app, config, function(err, expressApp) {
if (err) throw err;
var server = require('http').createServer(expressApp);
server.listen(port, listenCallback);
server.listen(port, ip, listenCallback);
});
}
derby.run(createServer);
Expand Down
61 changes: 12 additions & 49 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,16 @@
var coffeeify = require('coffeeify');
var derby = require('derby');
var express = require('express');
var redis = require('redis');
var RedisStore = require('connect-redis')(express);
var racerBrowserChannel = require('racer-browserchannel');
var liveDbMongo = require('livedb-mongo');
var parseUrl = require('url').parse;
var MongoStore = require('connect-mongo')(express);
derby.use(require('racer-bundle'));

exports.setup = setup;

function setup(app, options, cb) {
var redisClient;
if (process.env.REDIS_HOST) {
redisClient = redis.createClient(process.env.REDIS_PORT, process.env.REDIS_HOST);
redisClient.auth(process.env.REDIS_PASSWORD);
} else if (process.env.OPENREDIS_URL) {
var redisUrl = parseUrl(process.env.OPENREDIS_URL);
redisClient = redis.createClient(redisUrl.port, redisUrl.hostname);
redisClient.auth(redisUrl.auth.split(":")[1]);
} else {
redisClient = redis.createClient();
}
// redisClient.select(1);
function setup(app, config, cb) {

var mongoUrl = process.env.MONGO_URL || process.env.MONGOHQ_URL || 'mongodb://localhost:27017/derby-app';
// The store creates models and syncs data
var store = derby.createStore({
db: liveDbMongo(mongoUrl + '?auto_reconnect', {safe: true})
, redis: redisClient
});

store.on('bundle', function(browserify) {
// Add support for directly requiring coffeescript in browserify bundles
browserify.transform({global: true}, coffeeify);

// HACK: In order to use non-complied coffee node modules, we register it
// as a global transform. However, the coffeeify transform needs to happen
// before the include-globals transform that browserify hard adds as the
// first trasform. This moves the first transform to the end as a total
// hack to get around this
var pack = browserify.pack;
browserify.pack = function(opts) {
var detectTransform = opts.globalTransform.shift();
opts.globalTransform.push(detectTransform);
return pack.apply(this, arguments);
};
});
var store = require('./store')(derby, config);

var publicDir = __dirname + '/../public';

Expand All @@ -65,20 +29,19 @@ function setup(app, options, cb) {

.use(express.cookieParser())
.use(express.session({
secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE'
, store: new RedisStore()
secret: config.get('session.secret'),
store: new MongoStore({url: config.get('mongo.url')})
}))
.use(createUserId)

if (options && options.static) {
if(Array.isArray(options.static)) {
for(var i = 0; i < options.static.length; i++) {
var o = options.static[i];
expressApp.use(o.route, express.static(o.dir));
}
} else {
expressApp.use(express.static(options.static));
var serveStatic = config.get('static')
if (Array.isArray(serveStatic)) {
for(var i = 0; i < serveStatic.length; i++) {
var s = serveStatic[i];
expressApp.use(s.route, express.static(s.dir));
}
} else {
expressApp.use(express.static(serveStatic));
}

expressApp
Expand Down
47 changes: 47 additions & 0 deletions lib/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
var liveDbMongo = require('livedb-mongo');
var coffeeify = require('coffeeify');

module.exports = store;

function store(derby, config) {
var mongo = config.get('mongo');

// A real app should use redis to allow running multiple server processes.
//
// var db = liveDbMongo(mongo.url + '?auto_reconnect', mongo.opts);
//
// var oplogDb = db; // you can store the oplog in a different database
// var redisClient = require('redis').createClient();
// var redisObserver = require('redis').createClient();
//
// var driver = require('livedb').redisDriver(oplogDb, redisClient, redisObserver)
//
// var opts = {
// backend: {
// db: db,
// driver: driver
// }
// };

var opts = { db: liveDbMongo(mongo.url + '?auto_reconnect', mongo.opts) };
var store = derby.createStore(opts);

store.on('bundle', function(browserify) {
// Add support for directly requiring coffeescript in browserify bundles
browserify.transform({global: true}, coffeeify);

// HACK: In order to use non-complied coffee node modules, we register it
// as a global transform. However, the coffeeify transform needs to happen
// before the include-globals transform that browserify hard adds as the
// first trasform. This moves the first transform to the end as a total
// hack to get around this
var pack = browserify.pack;
browserify.pack = function(opts) {
var detectTransform = opts.globalTransform.shift();
opts.globalTransform.push(detectTransform);
return pack.apply(this, arguments);
};
});

return store;
}
12 changes: 5 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@
"url": "https://github.com/codeparty/derby-starter/issues"
},
"dependencies": {
"derby": "~0.6",
"derby": "git://github.com/codeparty/derby.git#375a2da411df1e9fdefcef43c71fa3205603bf2b",
"racer": "git://github.com/codeparty/racer.git#b3048e712caf9d47b5bd7a5d2e5421aaf8b11c0b",
"express": "~3.4.8",
"connect-mongo": "~0.4.1",
"convict": "~0.4.2",
"coffee-script": "~1.7.1",
"coffeeify": "~0.6.0",
"redis": "~0.10.1",
"connect-redis": "~1.4.7",
"racer-browserchannel": "^0.2.0",
"racer-browserchannel": "~0.2.0",
"racer-bundle": "~0.1.1",
"livedb-mongo": "~0.3.0"
},
"optionalDependencies": {
"hiredis": "~0.1.16"
}
}
File renamed without changes.