├── .gitignore ├── .npmignore ├── History.md ├── Makefile ├── Readme.md ├── index.js ├── package.json ├── src └── error.js └── test ├── index.js └── mocha.opts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | components 4 | build 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 2.0.1 / 2016-10-15 3 | ================== 4 | 5 | * json only error responses 6 | * minor readme updates 7 | 8 | 2.0.0 / 2016-10-15 9 | ================== 10 | 11 | * Starting anew :sparkles: 12 | 13 | 0.6.0 / 2015-10-15 14 | ================== 15 | 16 | * add middleware timing via `DEBUG=trace:time` 17 | 18 | 0.5.0 / 2015-10-10 19 | ================== 20 | 21 | * deprecate node 0.12 support for roo(1) by removing harmony proxy stuff for node 4 22 | 23 | 0.4.5 / 2015-08-17 24 | ================== 25 | 26 | * fix path for react 27 | 28 | 0.4.4 / 2015-08-13 29 | ================== 30 | 31 | * fix basic auth 32 | 33 | 0.4.3 / 2015-07-30 34 | ================== 35 | 36 | * bump koa to latest (v0.21.0) 37 | 38 | 0.4.2 / 2015-07-08 39 | ================== 40 | 41 | * pathname shouldnt affect assets 42 | 43 | 0.4.1 / 2015-06-25 44 | ================== 45 | 46 | * add cuteness 47 | 48 | 0.4.0 / 2015-06-25 49 | ================== 50 | 51 | * add wtch and update help 52 | * make executable more like beefy 53 | 54 | 0.3.3 / 2015-05-31 55 | ================== 56 | 57 | * pass process.env as env to templates by default (ex. title= env.NODE_ENV) 58 | 59 | 0.3.2 / 2015-05-31 60 | ================== 61 | 62 | * make views just work (install jade and hogan.js by default) 63 | 64 | 0.3.1 / 2015-05-07 65 | ================== 66 | 67 | * recursively update mounts 68 | 69 | 0.3.0 / 2015-04-16 70 | ================== 71 | 72 | * readme and roo.cluster(opts) support 73 | * cleanup dependencies. remove support for node 0.10.x 74 | * replace duo specific code with more generic koa-bundle 75 | * added: debug 76 | * added: roo.bundle() 77 | * add roo.bundle(...) 78 | * update makefile 79 | * add tests 80 | * allow user to pass options to static middleware 81 | 82 | 0.2.0 / 2015-01-19 83 | ================== 84 | 85 | * started: tests 86 | * remove: default logging 87 | * add: log filtering 88 | 89 | 0.1.8 / 2015-01-11 90 | ================== 91 | 92 | * do not compile duo in production 93 | 94 | 0.1.7 / 2015-01-11 95 | ================== 96 | 97 | * bump koa-error 98 | * fix roo's 'test' 99 | * loosen dependency pinning on duo-build 100 | 101 | 0.1.6 / 2014-12-09 102 | ================== 103 | 104 | * add missing uniques 105 | 106 | 0.1.5 / 2014-12-09 107 | ================== 108 | 109 | * fix duo builds 110 | 111 | 0.1.4 / 2014-12-08 112 | ================== 113 | 114 | * major speed fix when using duo 115 | 116 | 0.1.3 / 2014-11-27 117 | ================== 118 | 119 | * add koa-bodyparser 120 | 121 | 0.1.2 / 2014-11-26 122 | ================== 123 | 124 | * add koa-error (my branch until merge) 125 | 126 | 0.1.1 / 2014-11-17 127 | ================== 128 | 129 | * add roo#all(route, [middleware, ...], handler) 130 | 131 | 0.1.0 / 2014-11-12 132 | ================== 133 | 134 | * updated names. disable duo in production. add a way to pass multiple statics from CLI 135 | * Release 0.0.12 136 | * support html pages using hogan 137 | * kroute => kr 138 | 139 | 0.0.12 / 2014-11-12 140 | ================== 141 | 142 | * support html pages using hogan 143 | 144 | 0.0.11 / 2014-11-07 145 | ================== 146 | 147 | * replaced kroute with kr. 148 | * fixed roo.directory() 149 | 150 | 0.0.10 / 2014-11-07 151 | ================== 152 | 153 | * log once 154 | 155 | 0.0.9 / 2014-11-07 156 | ================== 157 | 158 | * resolve duo dependencies 159 | 160 | 0.0.8 / 2014-11-07 161 | ================== 162 | 163 | * expose roo#render(view, locals) 164 | 165 | 0.0.7 / 2014-11-06 166 | ================== 167 | 168 | * fix root for duo and jade 169 | 170 | 0.0.6 / 2014-11-06 171 | ================== 172 | 173 | * mounts now respect original roo. 174 | * change prototype to roo. 175 | * pass params to jade 176 | 177 | 0.0.5 / 2014-11-05 178 | ================== 179 | 180 | * fix mount 181 | 182 | 0.0.4 / 2014-11-05 183 | ================== 184 | 185 | * update to use koa-static 186 | 187 | 0.0.3 / 2014-11-05 188 | ================== 189 | 190 | * allow default port finding 191 | 192 | 0.0.2 / 2014-11-05 193 | ================== 194 | 195 | * bundle marked 196 | 197 | 0.0.1 / 2010-01-03 198 | ================== 199 | 200 | * Initial release 201 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @mocha --harmony 4 | 5 | .PHONY: test 6 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Roo 3 | 4 | **NOTE: 2.x is a significant departure from 0.x. Roo has been updated to reflect the changing landscape. The new roo is only meant to recieve and response to dynamic data.** 5 | 6 | Jump-start your front-end server. Bundles and configures the boilerplate of a Koa app. 7 | 8 | ## Installation 9 | 10 | ```sh 11 | npm install roo 12 | ``` 13 | 14 | ## Features 15 | 16 | * Deployable: Ready to be deployed to Dokku or Heroku 17 | * Composable: Mount Koa servers within or mount within other Koa servers. 18 | 19 | ## Example 20 | 21 | ```js 22 | const roo = Roo() 23 | const users = Roo() 24 | 25 | roo.mount('/users', users) 26 | 27 | users.get('/', function * () { 28 | this.body = 'users!' 29 | }) 30 | 31 | roo.listen(3000) 32 | ``` 33 | 34 | ## API 35 | 36 | ##### `Roo()` 37 | 38 | Initialize `Roo`. 39 | 40 | ##### `Roo.{get,post,put,delete,...}(route[, middleware, ...], handle)` 41 | 42 | Add a route to `Roo`. Routing is powered by [kr](https://github.com/lapwinglabs/kr), so visit there for API details. 43 | 44 | ```js 45 | roo.post('/signup', signup) 46 | ``` 47 | 48 | ##### `Roo.use(generator)` 49 | 50 | Pass additional middleware `generator`'s to `Roo`. 51 | 52 | ##### `Roo.mount([path], app)` 53 | 54 | Mount an app inside of `Roo` at `path`. `path` defaults to `/` 55 | 56 | ```js 57 | const app = roo() 58 | const dash = roo() 59 | app.mount('/dashboard', dash); 60 | ``` 61 | 62 | ##### `Roo.listen(port, fn)` 63 | 64 | Start the server on `port`. You may pass the environment variable `PORT=8080` in to specify a port. Otherwise it defaults to `3000` if otherwise not specified. 65 | 66 | ##### `Roo.listener()` 67 | 68 | Get koa's request handler. Useful if you'd like to proxy requests. 69 | 70 | ## Test 71 | 72 | ``` 73 | npm install 74 | make test 75 | ``` 76 | 77 | ## Why Roo? 78 | 79 | Roo is short for Kangaroo. I wrote this while visiting Australia for [CampJS](http://campjs.com) and I have Kangaroos on my mind. 80 | 81 | ## Credits 82 | 83 | Kangaroo Icon by [Olivier Guin](http://thenounproject.com/olivierguin) 84 | 85 | ## License 86 | 87 | MIT 88 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | const methods = require('methods').concat('all') 6 | const sliced = require('sliced') 7 | const koa = require('koa') 8 | 9 | /** 10 | * Middleware 11 | */ 12 | 13 | const bodyparser = require('koa-bodyparser') 14 | const error = require('./src/error') 15 | const mount = require('koa-mount') 16 | const kr = require('kr') 17 | 18 | /** 19 | * Export `Roo` 20 | */ 21 | 22 | module.exports = Roo 23 | 24 | /** 25 | * Initialize Roo 26 | */ 27 | 28 | function Roo () { 29 | if (!(this instanceof Roo)) return new Roo() 30 | this.app = koa() 31 | 32 | // allow for proxy requests 33 | this.app.proxy = true 34 | 35 | // initial middleware boiler 36 | this.app.use(error()) 37 | this.app.use(bodyparser()) 38 | } 39 | 40 | /** 41 | * Attach methods 42 | */ 43 | 44 | methods.map(method => { 45 | Roo.prototype[method] = function () { 46 | this.app.use(kr[method].apply(null, sliced(arguments))) 47 | return this 48 | } 49 | }) 50 | 51 | /** 52 | * Use 53 | */ 54 | 55 | Roo.prototype.use = function (fn) { 56 | this.app.use(fn) 57 | return this 58 | } 59 | 60 | /** 61 | * Mount 62 | */ 63 | 64 | Roo.prototype.mount = function (path, app) { 65 | app = app.app || app 66 | path = path || '/' 67 | 68 | this.app.use(mount(path, app.app || app)) 69 | return this 70 | } 71 | 72 | /** 73 | * Listener 74 | */ 75 | 76 | Roo.prototype.listener = function () { 77 | return this.app.callback() 78 | } 79 | 80 | /** 81 | * Listen 82 | */ 83 | 84 | Roo.prototype.listen = function (port, fn) { 85 | return this.app.listen(port, fn) 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roo", 3 | "version": "2.0.1", 4 | "description": "tiny koa server", 5 | "keywords": [ 6 | "server", 7 | "koa" 8 | ], 9 | "author": "Matthew Mueller ", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/matthewmueller/roo.git" 13 | }, 14 | "dependencies": { 15 | "koa": "^1.2.4", 16 | "koa-bodyparser": "^2.2.0", 17 | "koa-mount": "^1.3.0", 18 | "kr": "^0.0.1", 19 | "methods": "^1.1.2", 20 | "sliced": "^1.0.1" 21 | }, 22 | "devDependencies": { 23 | "babel-eslint": "7.0.0", 24 | "mocha": "^3.1.2", 25 | "snazzy": "5.0.0", 26 | "supertest": "^2.0.0" 27 | }, 28 | "main": "index", 29 | "scripts": { 30 | "lint": "snazzy", 31 | "pretest": "npm run lint" 32 | }, 33 | "standard": { 34 | "parser": "babel-eslint", 35 | "globals": [ 36 | "describe", 37 | "it", 38 | "before", 39 | "beforeEach", 40 | "after", 41 | "afterEach" 42 | ] 43 | } 44 | } -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | // Pulled from: https://github.com/koajs/json-error/blob/1.0.1/index.js 2 | 3 | 'use strict' 4 | 5 | /** 6 | * Error properties 7 | */ 8 | 9 | const props = [ 'name', 'code', 'message', 'stack', 'type' ] 10 | 11 | /** 12 | * Export `error` 13 | */ 14 | 15 | module.exports = error 16 | 17 | /** 18 | * JSON error handler 19 | */ 20 | 21 | function error () { 22 | return function * handle (next) { 23 | try { 24 | yield* next 25 | 26 | // future proof status 27 | const status = this.response.status 28 | if (!status) this.throw(404) 29 | if (status === 404 && this.response.body == null) this.throw(404) 30 | } catch (err) { 31 | // set body 32 | const body = {} 33 | const status = err.status || 500 34 | 35 | // set all properties of error onto the object 36 | Object.keys(err).forEach(function (key) { 37 | body[key] = err[key] 38 | }) 39 | props.forEach(function (key) { 40 | let value = err[key] 41 | if (value) body[key] = value 42 | }) 43 | 44 | // emit the error if we really care 45 | if (!err.expose && status >= 500) { 46 | this.app.emit('error', err, this) 47 | } 48 | 49 | this.response.status = status 50 | this.response.body = body 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | var request = require('supertest') 6 | var assert = require('assert') 7 | var Roo = require('../') 8 | 9 | /** 10 | * Tests 11 | */ 12 | 13 | describe('Roo', function () { 14 | describe('roo.mount(path, roo)', function () { 15 | it('should support mounting other roo apps', function (done) { 16 | var a = Roo() 17 | var b = Roo() 18 | a.mount('/a', b) 19 | b.get('/b', function *() { 20 | this.body = 'from b' 21 | }) 22 | 23 | request(a.listen()) 24 | .get('/a/b') 25 | .expect(200) 26 | .expect('from b', done) 27 | }) 28 | 29 | it('mount should work recursively', function (done) { 30 | var a = Roo() 31 | var b = Roo() 32 | var c = Roo() 33 | 34 | c.get('/c', function *() { 35 | this.body = 'from c' 36 | }) 37 | 38 | b.mount('/', c) 39 | a.mount('/', b) 40 | 41 | request(a.listen()) 42 | .get('/c') 43 | .expect(200) 44 | .expect('from c', done) 45 | }) 46 | 47 | it('mount should work recursively (order shouldnt matter)', function (done) { 48 | var a = Roo() 49 | var b = Roo() 50 | var c = Roo() 51 | 52 | c.get('/c', function *() { 53 | this.body = 'from c' 54 | }) 55 | 56 | a.mount('/', b) 57 | b.mount('/', c) 58 | 59 | request(a.listen()) 60 | .get('/c') 61 | .expect(200) 62 | .expect('from c', done) 63 | }) 64 | }) 65 | 66 | describe('routing', function () { 67 | it('should support GET requests', function (done) { 68 | var roo = Roo() 69 | roo.get('/user', function *() { 70 | this.body = 'matt' 71 | }) 72 | 73 | request(roo.listen()) 74 | .get('/user') 75 | .expect(200) 76 | .end(function (err, res) { 77 | if (err) return done(err) 78 | assert(res.text == 'matt') 79 | done() 80 | }) 81 | }) 82 | 83 | it('should suport post requests', function (done) { 84 | var roo = Roo() 85 | roo.post('/user', function *() { 86 | this.body = 'matt' 87 | }) 88 | 89 | request(roo.listen()) 90 | .post('/user') 91 | .expect(200) 92 | .end(function (err, res) { 93 | if (err) return done(err) 94 | assert(res.text == 'matt') 95 | done() 96 | }) 97 | }) 98 | 99 | it('should support parameters', function (done) { 100 | var roo = Roo() 101 | roo.post('/:user', function *() { 102 | assert('matt' == this.params.user) 103 | this.body = 'matt' 104 | }) 105 | 106 | request(roo.listen()) 107 | .post('/matt') 108 | .expect(200) 109 | .end(function (err, res) { 110 | if (err) return done(err) 111 | assert(res.text == 'matt') 112 | done() 113 | }) 114 | }) 115 | }) 116 | 117 | describe('errors', (done) => { 118 | it('should handle unexpected errors', (done) => { 119 | var roo = Roo() 120 | roo.post('/:user', function * () { 121 | oh.dear.lol 122 | }) 123 | 124 | request(roo.listen()) 125 | .post('/matt') 126 | .expect(500) 127 | .end(function (err, res) { 128 | if (err) return done(err) 129 | assert.equal(res.body.message, 'oh is not defined') 130 | assert.equal(res.status, 500) 131 | done() 132 | }) 133 | }) 134 | 135 | it('should handle thrown errors', (done) => { 136 | var roo = Roo() 137 | roo.post('/:user', function * () { 138 | this.throw(403) 139 | }) 140 | 141 | request(roo.listen()) 142 | .post('/matt') 143 | .expect(403) 144 | .end(function (err, res) { 145 | if (err) return done(err) 146 | assert.equal(res.body.message, 'Forbidden') 147 | assert.equal(res.status, 403) 148 | done() 149 | }) 150 | }) 151 | 152 | it('should handle custom messages', (done) => { 153 | var roo = Roo() 154 | roo.post('/:user', function * () { 155 | this.throw(403, 'you shall not pass') 156 | }) 157 | 158 | request(roo.listen()) 159 | .post('/matt') 160 | .expect(403) 161 | .end(function (err, res) { 162 | if (err) return done(err) 163 | assert.equal(res.body.message, 'you shall not pass') 164 | assert.equal(res.status, 403) 165 | done() 166 | }) 167 | }) 168 | 169 | it('should handle 404s', (done) => { 170 | var roo = Roo() 171 | request(roo.listen()) 172 | .post('/matt') 173 | .expect(404) 174 | .end(function (err, res) { 175 | if (err) return done(err) 176 | assert.equal(res.body.message, 'Not Found') 177 | assert.equal(res.status, 404) 178 | done() 179 | }) 180 | }) 181 | }) 182 | }) 183 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --harmony 3 | --------------------------------------------------------------------------------