Overview

Midnight is an open source web framework for node.js that makes building modern web applications easy.

Features:

Here's a simple Midnight application:

var midnight = require('midnight'),
	app = midnight();

app.route('/', function(request, response) {
	response.send('Hello world!');
});

app.start();

That's it.

Guide

Getting started

Let's take a closer look on how to get application up and running.

Install Midnight using Node Packaged Modules:

npm -g install midnight

Connect framework is optional, but highly recommended:

npm -g install connect

Prerequisites for running a Midnight application are now installed. Next step is to create a new application.

New application

Creating a new application is effortless:

midnight create example

Following directory structure is created:

Install the dependencies:

cd example
npm install

Next step is to run the application:

node app

Well done, application is now running at http://localhost:8080.

Scroll down to learn more about the Midnight framework.

Routes

Setting a new route is super easy:

app.route('/', function(request, response) {
	response.status(200)
		.content('text/plain')
		.send('Hello world!');
}).method(['GET', 'POST']); // Allow GET and POST requests

Route parameters can be captured as shown below:

app.route('/post/:id', function(request, response) {
	app.log.info('Get post id %s', request.params.id);
	response.status(200)
		.set('Content-Type', 'text/plain')
		.send('Hello world!');
}).get(); // Allow GET requests only

See Route and app.route for more detailed documentation.

Templates

Midnight supports following template engines by default:

Use the following command to get full list of supported template engines:

midnight engines

To use Hogan.js as a template engine:

app.engine(app.engines.hogan);

Which is equivalent to this:

app.engine({
	require: 'hogan.js',
	compile: function(data, options, done) {
		done(this.module.compile(data));
	},
	render: function(template, object, options, done) {
		done(template.render(object));
	}
});

Render a template:

response.status(200)
	.render('index.html', { query: request.query });

Here's how to create a new template engine wrapper:

app.engine({
	require: 'hogan.js', // Will populate this.module with loaded module
	cache: false, // Optional, by default every template will be cached in non-development mode
	read: function(path, done) { // Optional
		// Custom template reader
		// Asynchronous operations are acceptable
		// Path is absolute path to the template file

		// this.module is the loaded module declared in this.require

		fs.readFile(path, 'utf8', function(err, data) {
			// Call 'done' once finished
			done(err, data);
		});
	},
	compile: function(data, options, done) { // Optional
		// Compile the template
		// Asynchronous operations are acceptable
		// Data is string template
		// template will be cached if 'cache' property is true or missing

		// this.module is the loaded module declared in this.require

		// Call 'done' once finished
		done(this.module.compile(data));
	},
	render: function(template, object, options, done) { // Required
		// Render the template
		// Asynchronous operations are acceptable

		// this.module is the loaded module declared in this.require

		// Call 'done' once finished
		done(template.render(object));
	}
});

Middleware

Midnight is fully compatible with Connect framework.

Following example will populate request.query object containing HTTP GET parameters:

var connect = require('connect');

// Use query parsing middleware for every requests
app.use(connect.query());

app.route('/', function(request, response) {
	response.status(200).send(JSON.stringify(request.query));
});

In case a route specific middleware is required:

var connect = require('connect');

app.route('/', function(request, response) {
	response.status(200).send(JSON.stringify(request.body));
}).use(connect.json()); // Use JSON middleware just for this route

It's also possible to create a custom middleware:

app.use(function(request, response, next) {
	// Set every response status to 200 by default
	response.status(200);
	next();
});

app.route('/', function(request, response) {
	response.send('Response status is 200');
})

Static files

Serving static files with Midnight and Connect is easy:

var connect = require('connect');

app.use(connect.static(app.config.root + '/static'));

Example above serves static files with a following directory layout:

Requesting http://localhost:8080/stylesheets/style.css serves the stylesheets with ease.

Dynamic stylesheets

Using CSS preprocessors can greatly improve development speed and productivity.

Following examples will cover LESS, SASS and Stylus. However, there's plenty more to choose from.

Remember to serve static files when using dynamic stylesheets.

LESS

Preprocess .less stylesheets from /static/stylesheets/

var less = require('less-middleware');

// /stylesheets/style.css
app.use(less({
	src: app.config.root + '/static'
}));

See less.js-middleware for more details.

SASS

Preprocess .scss stylesheets from /static/stylesheets/

var sass = require('node-sass');

// /stylesheets/style.css
app.use(sass.middleware({
	src: app.config.root + '/static'
}));

See node-sass for more details.

Stylus

Preprocess .styl stylesheets from /static/stylesheets/

var stylus = require('stylus');

// /stylesheets/style.css
app.use(stylus.middleware({
	src: app.config.root + '/static'
}));

See stylus for more details.

Error handling

In development mode it's recommended to use error handler middleware:

var connect = require('connect');

if(app.config.env == 'development')
	app.use(connect.errorHandler());

Plugins

Midnight has a plugin system built-in to easily extend the application.

Here's how to create a new plugin:

var plugin = {
	name: 'plugin-name',
	attach: function(options) {
		// Called when plugin is attached
		// Options can be passed as a parameter
		// 'this' is the application context
	},
	init: function(next) {
		// Called when the application is initialized
		
		// Extend the application
		// Asynchronous operations are acceptable
		// 'this' is the application context

		// Call next() when the initialization is finished
		next();
	}
}

// Attach the plugin with optional parameters
app.attach(plugin, { sky: 'blue' });

Application server won't start until all the plugins are initialized.

Examples

Plenty of more examples included with the source code.

Go to Github and browse the repository.

API Reference

Application

app.configure(config)

app.configure({
	port: 9000,
	views: '/templates'
});

app.log.info('Server will run at port %s', app.config.port);

To reveal the default configuration:

app.log.info(JSON.stringify(app.config));

{
	'host': '127.0.0.1',
	'port': 8080,
	'views': '/views',
	'root': '/application/root/path',
	'env': 'development',
	'log': 2, // app.log.level.info
	'version': '0.0.1'
}

app.start([config])

Start the application.

app.start();

Alternatively, start the server with configuration parameter.

app.start({
	port: 9000
});

app.log

Set log level:

// Default logging level
app.config.log = app.log.level.info;

Logging.

// Trace
app.log.trace('Finely detailed information');

// Debug
app.log.debug('Detailed information');

// Info
app.log.info('Interesting runtime events');

// Warning
app.log.warn('Runtime situations that are undesirable');

// Error
app.log.error('Runtime errors or unexpected conditions');

// Fatal
app.log.fatal('This is very bad');

app.route(pattern, [function])

Declare a new route:

app.route('/post/:id', function(request, response) {
	response.send(request.params);
});

It's possible to use regular expression as a pattern:

// Accept requests with url /route/{parameter}
app.route(new RegExp('^\\/route\/(?:([^\\/]+?))\\/?$', 'i'), function(request, response) {
	response.send(request.params);
});

Routes can be used like below to set additional parameters:

app.route('/', function(request, response) {
	response.send('Hello world!');
}).get(); // Allow GET requests only

Leave function parameter out to simply include route specific middleware:

var connect = require('connect');

// Use logging middleware for /
app.route('/').use(connect.logger());

See Route for more examples.

app.engine(function)

Set template engine.

app.engine(function(render, template, object, options) {
	var buffer = '';
	require('mu2').compileAndRender(template, object).on('data', function(data) {
		buffer += data;
	}).on('end', function() {
		render(buffer);
	});
});

app.utils

Provides some useful tools, see Github for complete list.

app.use(function)

Use middleware for all the requests.

var connect = require('connect');

// Parse query parameters and place them in request.query object
app.use(connect.query());

app.attach(plugin, [options])

Attach a plugin to the application.

app.attach(plugin);

See Plugins for more examples.

Route

See Routes for examples on how to set routes.

route.method(method)

Specify allowed methods for the route

// Accept GET requests
route.method('GET');

// Accept GET, POST and DELETE requests
route.method(['GET', 'POST', 'DELETE']);

Above is equivalent to:

// Accept GET requests
route.get();

// Accept GET, POST and DELETE requests
route.get().post().delete();

All HTTP methods are allowed in case no method is specified.

route.get()

Allow GET requests.

route.get();

route.post()

Allow POST requests.

route.post();

route.put()

Allow PUT requests.

route.put();

route.delete()

Allow DELETE requests.

route.delete();

route.use(function)

Use middleware just for this route.

var connect = require('connect');

// http://localhost:8080/post?id=24
app.route('/post', function(request, response) {
	// Render: { id: 24 }
	response.status(200).send(JSON.stringify(request.query))
}).use(connect.query());

Request

route.method

Get request method.

request.get(header)

Get request header.

request.get('Content-Type');

request.content()

Get request Content-Type.

request.content();

Response

response.status(code)

Set status code.

response.status(200);

response.set(key, [value])

Set response header.

// Set response header
response.set('Access-Control-Allow-Origin', '*');

// Remove response header
response.set('Access-Control-Allow-Origin');

response.get(header)

Get response header.

response.get('Content-Type');

response.content([type])

Set Content-Type response header.

// Set response header
response.content('image/png');

// Get response header
response.content();

response.redirect(location)

Redirect to a location.

response.redirect('/');

response.encoding(encoding)

Set response encoding.

response.encoding('utf-8');

response.send(response)

Send a response.

// Content-Type: text/html
response.send('Hello world!');

Depending on the object type different Content-Type header is used:

// Content-Type: application/json
response.send({ name: 'Harry' });

// Content-Type: application/json
response.send(['Apples', 'Oranges']);

// Content-Type: application/octet-stream
response.send(new Buffer('Hello world!'));

This method automatically adds Content-Length header for the response.

response.render(template, [variables], [options])

Render a template.

 // Render without template variables
response.render('index.html');

// Render with template variables
response.render('index.html', { name: 'Harry' });

Include options parameter to pass options to the template engine:

response.render('index.html', { name: 'Harry' }, { cache: true });

Please note, template engine wrapper needs to take care of the options.

Template variables will always contain globals and config objects:

app.globals.title = 'Page title';

While the template could look like this:

<html>
<head>
	<title>{{ globals.title }}</title>
</head>
<body>
	<p>Server is running at port {{ config.port }}.</p>
</body>
</html>

Which renders to:

<html>
<head>
	<title>Page title</title>
</head>
<body>
	<p>Server is running at port 8080.</p>
</body>
</html>