Do you like Sinatra, but find it a little heavy? Bloated with things like simple error handling and CSRF protection; sick of the impedence mismatch of using one language on the server and another on the client; it’s not even asynchronous?!
Berliner is the framework for you! In just 16 lines of delicious CoffeeScript, it gives you all this, using the infinitely scalable horse power of Node.js:
GET
, POST
, PUT
,
DELETE
, PATCH
, HEAD
, and
OPTIONS
:named
and
*
matchers$ npm install berliner
app = require 'berliner' app.get '/', -> 'Hello, world!' app.run 4567
app.public = __dirname + '/public' # where to find static files app.views = __dirname + '/views' # where to find view templates app.session_secret = 'abcde12345' # key for encrypting sessions
app.get '/confs/:name', -> JSON.stringify @params app.put '/confs/:name', (name) -> name app.get '/download/*.*', -> @params.splat.join ', ' app.post '/confs', -> @status 201 @headers 'Content-Type': 'application/json' JSON.stringify @params app.get '/legacy', -> @redirect '/hello' app.options '/', -> @headers 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, PUT, DELETE' @render ''
This app responds like so:
$ curl 'localhost:4567/confs/eurucamp?bears=awesome' {"name":"eurucamp","bears":"awesome"} $ curl -X PUT localhost:4567/confs/jsconf jsconf $ curl localhost:4567/download/foo.js foo, js $ curl -iX POST localhost:4567/confs -d 'horses=fake' HTTP/1.1 201 Created Content-Type: application/json Content-Length: 17 Set-Cookie: session=XABfKjq2xvCavSitaxu0BC9XSl...; Path=/; HttpOnly Connection: keep-alive {"horses":"fake"} $ curl -i localhost:4567/legacy HTTP/1.1 303 See Other Location: /hello Connection: keep-alive Transfer-Encoding: chunked $ curl -iX OPTIONS localhost:4567/ HTTP/1.1 200 OK Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, PUT, DELETE Connection: keep-alive
app.websocket '/ws/:name', -> @socket.onmessage = (e)=> @socket.send @params.name + ': ' + e.data app.eventsource '/ws/:name', -> setInterval (=> @socket.send @params.name + ': PUSH!'), 5000
app.get '/counter', -> @session.counter ||= 0 @session.counter += 1 @render @session.counter.toString()
app.get '/', -> @cookie my_cookie: 'hello' @cookie another_cookie: {value: 'something', path: '/welcome', expires: new Date(2012,11,25), http: true} app.get '/welcome', -> @render @cookies.my_cookie
app.views = __dirname + '/views' app.helpers site_name: -> 'Awesome.net' app.get '/hello', -> @ejs 'hello', locals: {name: @params.name}
# views/hello.ejs Hello <%= name %>, welcome to <%= site_name() %>!
You can use @ejs
or @haml
to render a template.
@render
just takes a string and writes it to the response
body. If your route handler returns a string, that string will be the
body. Otherwise, you must call a rendering function.
$ curl 'localhost:4567/hello?name=_why' Hello _why, welcome to Awesome.net!
You can also register templates in the app code itself:
app.template 'hello.ejs', """ Hello <%= name %>, welcome to <%= site_name() %>! """
You can set up a set of routes with a common prefix and add before-filters that only apply to that context.
app.get '/', -> 'Hello!' app.context '/auth', (auth) -> auth.before (next) -> if @request.headers.authorization next() else @status 401 @render 'Authorization required' auth.get '/', -> 'Secret'
$ curl localhost:4567/ Hello! $ curl localhost:4567/auth Authorization required $ curl localhost:4567/auth -H 'Authorization: foo' Secret
Created by James Coglan at Eurucamp 2012, inspired by Konstantin Haase. Released under the MIT license.
If you find bugs, you’re probably holding it wrong. Since this is such a small module, it’s almost certainly bug-free; in fact I didn’t bother writing tests because it’s obviously correct and test frameworks are ugly and stupid.