See use.no.de/micro.
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).
NPM is recommended for development, although for production you might want to find/build a package for your operating system:
npm install micro
To load Micro.js:
var micro = require('micro');
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).
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).
A simple route is just a string that matches the path of the requested URL (e.g. '/'
).
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).
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).
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).
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.
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;
}
// ...
})
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).
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").
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).
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).
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) |
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'));
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.