micro

Micro is a micro framework along the same lines a Sinatra, running on top of Proton.js (hence JSGI).

npm install micro
20 downloads in the last week
40 downloads in the last month

See use.no.de/micro.

Introduction

Micro.js is a micro webapp framework along the same lines as Sinatra, running on top of Proton.js, and hence JSGI (using jsgi-node) and Node.js (although there is no reason it couldn't be made to work in other CommonJS environments).

Usage

Installation

NPM is recommended for development, although for production you might want to find/build a package for your operating system:

npm install micro

Loading

To load Micro.js:

var micro = require('micro');

Defining a Web Application

To define a web application, use the micro.webapp factory function to create a web application class (prototype function) suitable for running with Proton.js. To add routes and corresponding actions, use the get, post, put and del functions attached to the class (these are not depenent on being invoked on the web application and can safely be assigned to local variables):

var WebApp = exports.WebApp = micro.webapp(),
    get    = WebApp.get;

get('/', function (request, response) {
    response.ok('text/html');
    return 'hello, world!';
});

Alternatively, you can pass a function to micro.webapp that is passed the get, post, put and del functions so you can setup your web application class there:

var WebApp = new micro.webapp(function (get) {
    get('/', function (request, response) {
        response.ok('text/html');
        return 'hello, world!';
    });
});

These two styles can be mixed, although it is encouraged that a consistent style is adopted.

Each of the methods for creating an action takes a route (see "Routing" below) and callback function for the action to run when the route matches. The action callback takes a request object (see "Request API" below) as the first parameter and the response object (see "Response API" below) as the second parameter. The value of this within the function will be the instance of the web application class.

The expected return value depends on whether the action decides to handle the request or not, which it can choose to do by returning a value that evaluates to true in a boolean context or an empty string (indicating an empty body in the response).

Alternatively the action can return a promise. In this case the action is able to decide whether to handle the request by resolving the promise with a true value (as above) or an empty string, or decline the request by resolving it with null or other value that evaluates to false in a boolean context (excluding the empty string).

Routing

All routes are relative to a root URL that is expressed outside of the webapp (Micro.js webapps can easily be moved to a diffrent URL space).

Simple Routes

A simple route is just a string that matches the path of the requested URL (e.g. '/').

Routes with Named Parameters

A route with named parameters contains named placeholder starting with a colon. For example:

get('/hello/:name', function (request, response, args) {
    response.ok('text/html');
    return 'hello, ' + args.name;
});

Requests to /hello/tom now result in "hello, tom". By default placeholders will accept any sequence of one or more characters excluding a forward slash, although this can be overridden (see "Validating Parameters for Named and Postional Parameters" below).

Routes with Positional Parameters

Alternatively you can create placeholders with an asterisk, although you can't mix named and positional placeholder. For example:

get('/hello/*', function (request, response, name) {
    response.ok('text/html');
    return 'hello, ' + name;
});

This behaves the same as the example with named parameters above. By default placeholders will accept any sequence of one or more characters excluding a forward slash, although this can be overridden (see "Validating Parameters for Named and Postional Parameters" below).

Validating Parameters for Named and Positional Parameters

In order to restrict the values that will be accepted by a route, a placeholder can be followed by a fragment of a regular expression contained in round brackets. As the regular expression is specified as part of a string rather than as a regular expression literal, backslashes will have to be in pairs as they would for the parameter to new RegExp. For example:

get('/hello/:name(\\w+)', function (request, response, args) {
    // ...
});

This works for both named and positional placeholders (e.g. '/hello/*(\\w+)' with positional placeholders).

RegExp Routes

If the first parameter to get, post, etc. is a regular expression, the corresponding action will be invoked when the regular expression matches the path in the http request. Any captures in the regular expression are passed as arguments to the action callback. For example:

get(/^\/hello\/(\w+)$/, function (request, response, name) {
    response.ok('text/html');
    return 'hello, ' + name;
});

This behaves the same as for the previous examples.

Function Routes

If you've got really complicated requirements for routing, you can pass a function as the route. The function is passed the request path and its invocant (the value of "this" within the function) is the request. The function should return an containing zero or more arguments for the action callback if the route matches. For example:

get(function (path) {
    if (path === '/foo' && this.queryString === '?a=1') {
        return ['bar'];
    }
}, function (request, response, baz) {
    // baz contains 'bar' here
});

Although this feature is supported, it isn't really recommended as it makes the code less readable/maintainable. The recommended practise is to use one of the other routes and put non-path based checks into the action callback, moving onto the next route by returning:

get('/foo', function (request, response) {
    if (this.queryString !== '?a=1') {
        return;
    }
    // ...
})

General API Notes

Each method without a specified return value will return the object that the method is invoked on, so that methods can be chained (if that's your thing).

WebApp API

The WebApp is the the class created with micro.webapp. When the application is ran (either responding to HTTP requests, or as part of a test suite), an instance of this class is created, which is the value of this within each of the action method, as well as within each method that you attach to the class' prototype. The following helper functions are attached to the WebApp prototype function (class) that add routes and corresponding actions to the class.

  • WebApp.get(route, action) - add a handler for GET requests.
  • WebApp.post(route, action) - add a handler for POST requests.
  • WebApp.put(route, action) - add a handler for PUT requests.
  • WebApp.del(route, action) - add a handler for DELETE requests.
  • WebApp.handleStatic(root, prefix) - add a handler for static assets contained within a directory (root) when requested under a URL prefix.
  • webapp.handle(request) - called by Proton to handle an incoming request. Could also be useful for testing. Returns a response as specified by JSGI and expected by Proton (note that this is not the same as the response object that is the invocant to the action callbacks described below in "Response API").

Request API

The request object passed to each action callback (and the invocant to function routes) is a standard JSGI request object (see the latest CommonJS proposal for JSGI and jsgi-node, which is used by micro.js and proton.js).

Response API

The response object (the invocant to each action callback) has the following methods:

  • this.setStatus(code) - takes an integer status code and sets it on the response (default 404). See also "Status and Type Shortcuts" below.
  • this.setType(contentType) - takes a string containing the value for the "Content-Type" of the response (default "text/plain"). See also "Status and Type Shortcuts" below.
  • this.addToBody(content) - adds content (a string or a file descriptor to read the content from) to the body of the response (can also be achieved by returning the content from the action or resolving a returned promise with the content).

Status and Type Shortcuts

The following are shortcut methods for settting the status of the response and other header fields that are commonly returned with them (e.g. Content-Type). The most common are "ok", "notFound" and "internalServerError", although if you're an HTTP nut then anything you might want should be here (if it isn't raise a ticket/make a pull request).

TODO - most of these are not implemented yet...

Status Code Method Additional Notes
200 OK response.ok(contentType)  
201 Created response.created(contentType, location) The location parameter contains the URI of the newly created resource.
202 Accepted response.accepted(contentType)  
203 Non-Authoritative Information response.nonAuthoritativeInformation(contentType)  
204 No Content response.noContent() There is no content-type parameter as there is not content.
205 Reset Content response.resetContent() There is no content-type parameter as no response body should be included with this response code.
206 Partial Content response.partialContent(contentType, headers) The headers parameter must contain keys and values corresponding to the required parameters as specified in the spec.
300 Multiple Choices response.multipleChoices(contentType, location?) The optional location parameter contains the URI of the prefered choice of representation.
301 Moved Permanently response.movedPermanently(location, contentType?) The location parameter contains the URL being redirected to (this can be relative to the requested URL). The content-type parameter is optional and defaults to text/html. A short message and hyperlink is added to the body if a body is not added.
302 Found response.found(location, contentType?) The location parameter contains the URL being redirected to (this can be relative to the requested URL). The content-type parameter is optional and defaults to text/html. A short message and hyperlink is added to the body if a body is not added.
303 See Other response.seeOther(location, contentType?) The location parameter contains the URL being referenced (this can be relative to the requested URL). The content-type parameter is optional and defaults to text/html. A short message is added to the body if a body is not added.
304 Not Modified response.notModified(headers) The headers parameter must contain keys and values corresponding to the required parameters as specified in the spec.
305 Use Proxy response.useProxy(location) The location parameters should contain the URI of the proxy.
307 Temporary Redirect response.temporaryRedirect(location, contentType?) The location parameter contains the URL being redirected to (this can be relative to the requested URL). The content-type parameter is optional and defaults to text/html. A short message and hyperlink is added to the body if a body is not added.
400 Bad Request response.badRequest() This shouldn't normally be called as a malformed request shouldn't make it to the web appliction.
401 Unauthorized response.unauthorized(contentType, wwwAuthenticate) The wwwAuthenticate parameter should contain the value for the WWW-Authenticate header.
403 Forbidden response.forbidden(contentType)  
404 Not Found response.notFound(contentType)  
405 Method Not Allowed response.methodNotAllowed(contentType, methods) The methods parameter is an array containing HTTP methods that are allowed for the "Allow" header.
406 Not Acceptable response.notAcceptable(contentType)  
407 Proxy Authentication Required response.proxyAuthenticationRequired(contentType, proxyAuthentciate) The proxyAuthenticate parameter contains the value for the "Proxy-Authenticate" header.
408 Request Timeout response.requestTimeout(contentType)  
409 Conflict response.conflict(contentType)  
410 Gone response.gone(contentType)  
411 Length Required response.lengthRequired(contentType)  
412 Precondition Failed response.preconditionFailed(contentType)  
413 Request Entity Too Large response.requestEntityTooLarge(contentType, retryAfter?) The optional retryAfter parameter is for the "Retry-After" parameter in case that the condition is temporary.
414 Request-URI Too Long response.requestURITooLong(contentType)  
415 Unsupported Media Type response.unsupportedMediaType(contentType)  
416 Requested Range Not Satisfiable response.requestedRangeNotSatisfiable(contentType, contentRange?) The optional "contentRange" parameter contains the value for the "Content-Range" header.
417 Expectation Failed response.expectationFailed(contentType)  
500 Internal Server Error response.internalServerError(contentType)  
501 Not Implemented response.notImplemented(contentType)  
502 Bad Gateway response.badGateway(contentType)  
503 Service Unavailable response.serviceUnavailable(contentType)  
504 Gateway Timeout response.gatewayTimeout(contentType)  
505 HTTP Version Not Supported response.httpVersionNotSupported(contentType)  

Views

Pluggable views is an upcoming feature. It is possible to use Spectrum.js templates as follows:

var micro    = require('micro/micro'),
    spectrum = require('spectrum');

var WebApp = exports.WebApp = micro.webapp(),
    get = WebApp.get;

WebApp.prototype.init = function () {
    this.view = new spectrum.Renderer(__dirname + '/../views');
};

get('/', function (request, response) {
    return this.view.render('/index.spv', {}).then(function (output) {
        response.ok('text/html');
        return output;
    });
});

WebApp.handleStatic(__dirname.replace(/\/lib$/, '/static'));

Copyright and Licensing

This code is an alpha quality prototype. It is not recommended for production applications.

Copyright 2010 British Broadcasting Corporation

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

npm loves you