Berliner.

The lightweight web framework

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:

Install it

$ npm install berliner

Hello, world

app = require 'berliner'

app.get '/', -> 'Hello, world!'

app.run 4567

Settings

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

Handling HTTP methods, statuses, headers, etc.

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

WebSocket and EventSource

app.websocket '/ws/:name', ->
  @socket.onmessage = (e)=>
    @socket.send @params.name + ': ' + e.data

app.eventsource '/ws/:name', ->
  setInterval (=> @socket.send @params.name + ': PUSH!'), 5000

Sessions

app.get '/counter', ->
  @session.counter ||= 0
  @session.counter += 1
  @render @session.counter.toString()

Cookies

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

Helpers, EJS and HAML

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() %>!
"""

Context groups

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

Credits

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.