This is an opinionated template for nodejs apps. The primary means of configuration is:
require('lib/nuts').deez()
- Hapi - Web Server
- Webpack - Asset management
- React - Javscript UI framework
- Flux - Frontend application architecture
- Backbone - Used for the
M
inMVC
- Bootstrap - CSS/HTML framework
- Mongoose - Mongo ORM
- Kue - Job queue
The main goal of this project is to provide as much out-of-the-box functionality as possible to get a project off the ground. A lot of heavy lifting and boilerplate code is already done so you can focus on what makes your app unique.
Some of the primary features are:
- Shared templates for server-side and client-side rendering
- Basic transactional emails and templates
- Login, forgot password, registration flows
- Delayed processing of jobs
- Good architectural patterns and practices
There are lots more things that to be added so suggestions and PR's are welcome.
- Redis
- MongoDB
To spin up the server just run npm start
. This will compile all of the assets and start the server on port 3000
. You can change this by either setting an environment variable PORT=3000
or by changing the default settings in config/settings.js
.
Foreman is used for managing the processes so if you want to change what gets run during development you can update the Procfile.dev
file.
To open up a REPL with a fully loaded environment you can run npm run console
. This makes it easy to execute commands in your environment and should be very familiar to those coming from Rails.
You can configure your environment settings in settings.yml
. The file is precompiled using the Lodash template syntax similar to ERB. All settings are available through Nuts.settings
.
Most things can be accessed via the global Nuts
object. This is not necessary, but it's done for convenience.
Views are located in the app/assets/javascript/views
folder. This path is already setup so you can easily return the view from your controller by calling reply.view('myview.jsx')
.
Page layouts are used to render a full page on the server. Constructing things like raw javascript tags for 3rd party libraries or html doctypes is tricky to do with React so it's handy to use a different mechanism.
Layouts are located in the app/assets/javascript/views/layouts/page
folder. They are rendered as a lodash template and should have a .template
extension. Page layouts should be very generic and provide as little templating as possible. For the most part the default template can just be modified to fit your needs. It's likely rare that you would need to have more than one.
Content layouts are located in the app/assets/javascript/views/layouts/page
folder. They are useful for providing the meat-and-potatoes structure of the page. This is where headers, footers, and menus would likely be rendered using React. If you have a splash landing page that doesn't have the same structure as the rest of the app then this would be a case to create a new content layout.
Actions are simple single purpose functions that act in some way on one or more models. They are used to encapsulate business logic. They can either be require
d directly or accessed via Nuts.actions.myAction
;
They take the following format:
// app/actions/MyAction.js
module.exports = function(/* optional params */) {
var deferred = Nuts.defer();
process.nextTick(function() {
// If process was successful then resolve the promise
deferred.resolve(/* optional data */);
// If there was an error then return it
deferred.reject(/*some err*/)
})
return deferred.promise;
}
They can then be invoked like this:
Nuts.actions.MyAction().then(function() {
// Completed successfully
}).fail(function(err) {
// Action failed
}).done();
The Nuts.defer()
is just a wrapper for the Q
promise library. You can find out more about it here;
The default environment is development
. It can be overriden with an environment variable NODE_ENV=production
. There should be a corresponding file in app/config/environments
for each environment. These can be used if you need to load specific settings or configurations for each environment.
These are configurations that are loaded in every environment. They are not loaded in a specific order. They're useful for configuring various libraries and plugins
Hapi plugins are configured just like other initializers. You can access the Hapi server via Nuts.server
.
// config/initializers/good.js
Nuts.server.pack.register({
plugin: require('good'),
options: {
subscribers: {
console: ['request', 'log', 'error']
}
}
}, function(err) {
if(err) throw err;
});
You can read more about Hapi plugins here.
Assets are built using webpack. If you want to build and watch the assets for changes you can run npm run assets
. This process will happen automtically for you in development when you run npm start
.
Background jobs are processed using a redis job queue called Kue.
As long as Redis is running locally on a default port and IP it will work out of the box when running in development.
If you want to run it on Heroku using a Redis plugin you can just specify which environment variable contains the proper connection string you want to use. Check your settings to figure out which connectionstring your Redis plugin uses and then specify it in the REDIS_ENV_KEY
env var.
For instance, if you have the Redis To Go plugin installed you can tell Kue to use that connectionstring by setting it like this:
heroku config:set REDIS_ENV_KEY=REDISTOGO_URL
First you need to get access the kue jobs object
var jobs = Nuts.require('lib/jobs/queue').connect();
Calling connect
returns a singleton that can be used to create the jobs. Here is an example that sends an email confirmation when a user registers:
// app/actions/registerUser.js
module.exports = function(params) {
var deferred = Nuts.defer();
new Nuts.models.User(params).save(function(err, savedUser) {
if(err) return deferred.reject(err);
var jobs = Nuts.require('lib/jobs/queue').connect();
jobs
.create('send_email_confirmation', {
email: savedUser.email,
title: savedUser.email
})
.priority('high')
.attempts(5)
.save(function(err) {
if(err) Nuts.reportError(err);
deferred.resolve(savedUser);
});
});
return deferred.promise;
};
Consult the Kue documentation for more information on various options you can use when creating jobs.
Job processors are grouped by type. This allows different workers to be setup to process different kinds of jobs. Processors must be located in the lib/jobs
folder and should have the following format:
module.exports = {
process: function(concurrency) { /** process Job **/ }
}
Here is a working example that process email confirmation jobs:
//lib/jobs/email.js
var DEFAULT_CONCURRENCY = 5;
var jobs = require('./queue').connect();
module.exports = {
process: function(concurrency) {
jobs.process('send_email_confirmation', (concurrency || DEFAULT_CONCURRENCY), function(job, done) {
Nuts.actions.sendEmailConfirmation(job.data.email).then(function(result) {
done();
}).fail(function(err) {
Nuts.reportError(err);
done(err);
});
});
}
}
Consult the Kue documentation for more information about processing jobs.
Workers are segmented by the different types of processors located in lib/jobs
. You can start a worker on a specific process like this:
KUE_NAME=email KUE_CONCURRENCY=10 ./bin/kue-worker
This will run the lib/jobs/email
processor with a default concurrency of 10
.
Be sure to add this to your Procfile
to run it in production or your Procfile.dev
to run it locally.
email_worker: KUE_NAME=email KUE_CONCURRENCY=10 ./bin/kue-worker
There is a web interface you can access that will allow you to view all of the jobs and their progress. The ports is specified in Nuts.settings.kue.port
. By default you can access it locally at http://localhost:3800.
This kit is designed to make it easy to deploy to heroku. Here is what you need to setup a new app on Heroku:
heroku apps:create <app_name>
heroku addons:add mongohq:sandbox
heroku config:set NODE_ENV=production
heroku config:set DOMAIN=<app_name>.herokuapp.com
git push heroku master
During deployment, Heroku uses the npm postinstall
hook to automatically compile and minfy all of the assets. You can read more about it here.
Contributions are welcomed and encouraged. Just fork it and open a PR from your feature branch. I built this template specifically for me to use in side-projects and hackathons but if you find it useful I'd like to hear about it.
...and the maintainers of all the other libraries.
#License
This project is released under the MIT License.