├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── index.js ├── package.json └── static │ └── file.txt ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | sudo: false 5 | cache: 6 | directories: 7 | - node_modules 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # hyperserv change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [4.0.1](https://github.com/bcomnes/hyperserv/releases/v4.0.1) 8 | 9 | ### Patch Changes 10 | 11 | - Remove http-hash-router from deps. Tracking in the fork instead. 12 | 13 | ### Commits 14 | 15 | * [[`eecb36c85a`](https://github.com/bcomnes/hyperserv/commit/eecb36c85a)] - remove http-hash-router (Bret Comnes) 16 | 17 | 18 | [view diff](https://github.com/bcomnes/hyperserv/compare/v4.0.0...v4.0.1) 19 | 20 | ## [4.0.0](https://github.com/bcomnes/hyperserv/releases/v4.0.0) 21 | 22 | ### Major Changes 23 | 24 | - Use introspection to use both express and http-hash-router style route functions. 25 | - You no longer need to wrap express routes in `Hyperserv.makeRoute`. 26 | 27 | ### Commits 28 | 29 | * [[`23f54d2484`](https://github.com/bcomnes/hyperserv/commit/23f54d2484)] - use @bret/http-hash-router 2.0.0 (Bret Comnes) 30 | * [[`d76e8db792`](https://github.com/bcomnes/hyperserv/commit/d76e8db792)] - pushing http-hash-router to track with greenkeepr (Bret Comnes) 31 | 32 | [view diff](https://github.com/bcomnes/hyperserv/compare/v3.0.1...v4.0.0) 33 | 34 | ## [3.0.1](https://github.com/bcomnes/hyperserv/releases/v3.0.1) 35 | 36 | [view diff](https://github.com/bcomnes/hyperserv/compare/v3.0.0...v3.0.1) 37 | 38 | ### Patch Changes 39 | 40 | - update to using a fork of http-hash-router 41 | 42 | ### Commits 43 | 44 | * [[`4a1771cd57`](https://github.com/bcomnes/hyperserv/commit/4a1771cd57)] - use updated hash router fork (Bret Comnes) 45 | 46 | ## [3.0.0](https://github.com/bcomnes/hyperserv/releases/v3.0.0) 47 | 48 | [view diff](https://github.com/bcomnes/hyperserv/compare/v2.0.0...v3.0.0) 49 | 50 | ### Breaking Changes 51 | 52 | - remove static file server and dependencies 53 | 54 | ### Commits 55 | 56 | * [[`d9904e0ceb`](https://github.com/bcomnes/hyperserv/commit/d9904e0ceb)] - Remove static file server (Bret Comnes) 57 | * [[`6bf8804cd8`](https://github.com/bcomnes/hyperserv/commit/6bf8804cd8)] - Merge pull request #2 from bcomnes/greenkeeper-snazzy-5.0.0 (Bret Comnes) 58 | * [[`001676c665`](https://github.com/bcomnes/hyperserv/commit/001676c665)] - chore(package): update snazzy to version 5.0.0 (greenkeeperio-bot) 59 | * [[`b9df576119`](https://github.com/bcomnes/hyperserv/commit/b9df576119)] - Merge pull request #1 from bcomnes/greenkeeper-standard-8.0.0 (Bret Comnes) 60 | * [[`0152e7969a`](https://github.com/bcomnes/hyperserv/commit/0152e7969a)] - chore(package): update standard to version 8.0.0 (greenkeeperio-bot) 61 | * [[`f164c58114`](https://github.com/bcomnes/hyperserv/commit/f164c58114)] - Update README.md (Bret Comnes) 62 | * [[`2680444d92`](https://github.com/bcomnes/hyperserv/commit/2680444d92)] - Update README.md (Bret Comnes) 63 | * [[`247bf5b388`](https://github.com/bcomnes/hyperserv/commit/247bf5b388)] - update to 2.0.0 to rework api (Bret Comnes) 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Bret Comnes 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyperserv 2 | 3 | **Project depreciated**: This was a fun project but you should use [express](https://expressjs.com) or [fastify](https://www.fastify.io) for real work. 4 | 5 | A hypermodular http server that glues together [http](https://nodejs.org/api/http.html), [stack](http://github.com/creationix/stack) and [@bret/http-hash-router](https://github.com/bcomnes/http-hash-router). 6 | 7 | ``` 8 | npm i hyperserv 9 | ``` 10 | 11 | [![Build Status](https://travis-ci.org/bcomnes/hyperserv.svg?branch=master)](https://travis-ci.org/bcomnes/hyperserv) 12 | [![Dependency Status](https://david-dm.org/bcomnes/hyperserv.svg)](https://david-dm.org/bcomnes/hyperserv) 13 | 14 | ## Why? 15 | 16 | Express is a reliable and widely understood web-framework, but the dream of node.js was framework free network applications. [http-framework](https://github.com/Raynos/http-framework) and [substack-flavored-webapp](https://github.com/substack/substack-flavored-webapp) are excellent counterpoints to frameworks like express and hapi but come along with a pile of boilerplate. hyperserv aims to glue together the basics of any web server by providing a routing layer and a middleware layer to offer up a quick way to write small webservers the hypermodular way (or, more specifically, one hypermodular way)! 17 | 18 | How you launch and configure your webservers seems to be a deeply personal ceremony. hyperserv leaves this up to you and just puts together the webserver for you. 19 | 20 | ## Usage 21 | 22 | ```js 23 | var minimist = require('minimist') 24 | var morgan = require('morgan') 25 | var Hyperserv = require('hyperserv') 26 | var app = new Hyperserv() 27 | var argv = minimist(process.argv.slice(2), { 28 | alias: { p: 'port' }, 29 | default: { port: 8000 } 30 | }) 31 | var ecstatic = require('ecstatic') 32 | var path = require('path') 33 | 34 | process.title = 'hyperserv' 35 | 36 | // Reconfigure the middlewre stack sitting in front of the routes. 37 | app.composeStack([ 38 | morgan('dev') 39 | ]) 40 | 41 | var staticPath = path.join(__dirname, 'static') 42 | console.log(staticPath) 43 | app.router.set('/static', ecstatic({ 44 | root: staticPath, 45 | baseDir: 'static', 46 | handleError: false 47 | })) 48 | 49 | // Set up routes 50 | app.router.set('/', function (req, res, opts, cb) { 51 | res.end('hi') 52 | }) 53 | 54 | // Set up routes 55 | app.router.set('/:name', function (req, res, opts, cb) { 56 | res.end('hello ' + opts.params.name) 57 | }) 58 | 59 | // Routes can fly fast and loose. It don't matter 60 | app.router.set('/crash', function (req, res, opts, cb) { 61 | throw new Error('This route crashed intentionally') 62 | }) 63 | 64 | function expressMiddleware (req, res, next) { 65 | res.write(JSON.stringify(req.opts) + '\n') 66 | res.end('this is an express/connect style middleware layer') 67 | } 68 | 69 | app.router.set('/:name/express', expressMiddleware) 70 | 71 | app.httpServer.listen(argv.port) 72 | ``` 73 | 74 | ## API 75 | 76 | #### Routes vs Layers No More! 77 | 78 | Hyperserv now supports connect style routes out of the box. The `opts` object that holds params and app level vars are now extended into the `req.opts` object by default, when the route doesn't accept an `opts` argument. 79 | 80 | #### `var app = new Hyperserv([options])` 81 | 82 | Returns a new hyperserv `app` object. It sets up an httpServer that has a middleware handler and router. 83 | 84 | Default options: 85 | 86 | ```js 87 | { 88 | layers: [ require('morgan')('dev') ], 89 | sendTraces: true, 90 | logTraces: true, 91 | logDetails: true 92 | } 93 | ``` 94 | 95 | - `layers`: Provide an array of middleware functions (`function layer (req, res, cb) {}`) that get stuck in front of the final routing layer. You can reconfigure this layer at any point with `server.composeStack`. 96 | - `sendTraces`: Specify if stack traces are sent in the `res` if the `req` runs into any kind of error. Defaults to `false` 97 | - `logTraces`: Attach the default error handler to the `error` event emitted from the server whenever it encounters an error. 98 | 99 | ```js 100 | function errorHandler (err) { 101 | if (err.statusCode !== 404) console.log(err) 102 | } 103 | ``` 104 | 105 | - `logDetails`: Attach the default server start log message to the server to fire when it starts listening. 106 | 107 | #### `app.httpServer` 108 | 109 | This is an instance of `http.createServer`. It isn't started yet, so set up your event handlers, and turn it on with `app.httpServer.listen(port)` 110 | 111 | #### `app.router` 112 | 113 | This is the `@bret/http-hash-router` router object that has simply been attached to the `http` server instance. Read all about it here: 114 | 115 | - [bcomnes/http-hash-router](https://github.com/bcomnes/http-hash-router) 116 | - [Matt-Esch/http-hash](https://github.com/Matt-Esch/http-hash) 117 | 118 | #### `app.router.set(pattern, routeHandler)` 119 | 120 | This sets a route and a route handler. Remember, routeHandlers expect the following signature `function route (req, res, opts, cb) {}`. You can compose middleware stack's to plop inside of route handlers using [`stack.compose`](https://github.com/creationix/stack/blob/master/stack.js#L36). 121 | 122 | See [bcomnes/http-hash-router#example](https://github.com/bcomnes/http-hash-router#example) 123 | 124 | #### `app.composeStack([ layers ])` 125 | 126 | This lets you pass an array of middleware layers (`function layer (req, res, cb) {}`) to stick in front of the `http-hash-router` layer. You can do body parsing, cookie parsing, sessions, and auth stuff here. Calling `composeStack` tosses the existing middleware stack out in favor of the one you pass in here. 127 | 128 | #### `app.errorHandler` 129 | 130 | This is the default error handler that gets passed to the server's `error` event when `logTraces` is set to true when creating a `server`. It is an instance method that you can reassign if you want. 131 | 132 | ```js 133 | function errorHandler (err) { 134 | if (err.statusCode !== 404) console.log(err) 135 | } 136 | ``` 137 | 138 | If you want to use it as is with the server, use the `logTraces` option. It is exported only for convince and access and should not be normally used directly. 139 | 140 | #### `app.logDetails` 141 | 142 | This is the default logging function that runs when the server starts listening. Use the `logDetails` options to turn it on or off. It is an instance method that you can reassign if you want. 143 | 144 | #### `Hyperserv.makeRoute(layer)` (Depreciated) 145 | 146 | Pass in a connect style middleware layer and get back a `http-hash-router` route handler. The returned route handler mixes in any options it receives on its `opts` argument to `req.opts`. 147 | 148 | ```js 149 | function makeRoute (layer) { 150 | return (req, res, opts, cb) => { 151 | req.opts = extend(req.opts, opts) 152 | layer(req, res, cb) 153 | } 154 | } 155 | ``` 156 | 157 | 158 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var minimist = require('minimist') 2 | var morgan = require('morgan') 3 | var Hyperserv = require('../') 4 | var app = new Hyperserv() 5 | var argv = minimist(process.argv.slice(2), { 6 | alias: { p: 'port' }, 7 | default: { port: 8000 } 8 | }) 9 | var ecstatic = require('ecstatic') 10 | var path = require('path') 11 | 12 | process.title = 'hyperserv' 13 | 14 | // Reconfigure the middlewre stack sitting in front of the routes. 15 | app.composeStack([ 16 | morgan('dev') 17 | ]) 18 | 19 | var staticPath = path.join(__dirname, 'static') 20 | 21 | app.router.set('/static/*', ecstatic({ 22 | root: staticPath, 23 | baseDir: 'static', 24 | handleError: false 25 | })) 26 | 27 | // Set up routes 28 | app.router.set('/', function (req, res, opts, cb) { 29 | res.end('hi') 30 | }) 31 | 32 | // Set up routes 33 | app.router.set('/:name', function (req, res, opts, cb) { 34 | res.end('hello ' + opts.params.name) 35 | }) 36 | 37 | // Routes can fly fast and loose. It don't matter 38 | app.router.set('/crash', function (req, res, opts, cb) { 39 | throw new Error('This route crashed intentionally') 40 | }) 41 | 42 | function expressMiddleware (req, res, next) { 43 | res.write(JSON.stringify(req.opts) + '\n') 44 | res.end('this is an express/connect style middleware layer') 45 | } 46 | 47 | app.router.set('/:name/express', expressMiddleware) 48 | 49 | app.httpServer.listen(argv.port) 50 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js", 9 | "dev": "nodemon index.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "ecstatic": "^2.1.0", 15 | "minimist": "^1.2.0", 16 | "morgan": "^1.7.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/static/file.txt: -------------------------------------------------------------------------------- 1 | Put static files here 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var HttpHashRouter = require('@bret/http-hash-router') 3 | var morgan = require('morgan') 4 | var Stack = require('stack') 5 | var finalhandler = require('finalhandler') 6 | 7 | function Hyperserv (opts) { 8 | if (!(this instanceof Hyperserv)) return new Hyperserv(opts) 9 | if (!opts) opts = {} 10 | var self = this 11 | 12 | this.sendTraces = (opts.sendTraces === undefined) ? true : !!opts.sendTraces 13 | this.router = HttpHashRouter() 14 | this.onReq = onReq 15 | this.httpServer = http.createServer(this.onReq) 16 | this.finalhandler = finalhandler 17 | 18 | this._logDetails = (opts.logDetails === undefined) ? true : !!opts.logDetails 19 | this._logTraces = (opts.logTraces === undefined) ? true : !!opts.logTraces 20 | this._layers = opts.layers || [this.logger] 21 | this._stack = Stack.compose.apply(null, this._layers) 22 | 23 | if (this._logDetails) this.httpServer.on('listening', logDetails) 24 | if (this._logTraces) this.httpServer.on('error', this.errorHandler) 25 | 26 | function onReq (req, res) { 27 | var done = self.finalhandler(req, res, { 28 | onerror: self.errorHandler, 29 | env: self.sendTraces ? 'development' : 'production' 30 | }) 31 | 32 | self._stack(req, res, routeReq) 33 | 34 | function routeReq (err) { 35 | if (err) return done(err) 36 | try { 37 | self.router(req, res, {}, done) 38 | } catch (err) { 39 | done(err) 40 | } 41 | } 42 | } 43 | 44 | function logDetails () { 45 | console.log(`listening on http://localhost:${self.httpServer.address().port}`) 46 | if (self._serveStatic) { 47 | console.log('serving static from ' + 48 | `${self._staticPath} at ${self._staticMount}`) 49 | } 50 | } 51 | } 52 | 53 | Hyperserv.prototype.composeStack = function (newLayers) { 54 | this._layers = newLayers 55 | this._stack = Stack.compose.apply(null, this._layers) 56 | } 57 | 58 | Hyperserv.prototype.errorHandler = function (err) { 59 | if (err.statusCode !== 404) console.log(err) 60 | } 61 | 62 | Hyperserv.prototype.logger = morgan('dev') 63 | 64 | module.exports = Hyperserv 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperserv", 3 | "version": "4.0.1", 4 | "description": "hypermodular http server", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "dependency-check . --no-dev && standard | snazzy && tape test.js | tap-format-spec" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/bcomnes/hyperserv.git" 12 | }, 13 | "keywords": [ 14 | "youtube-dl" 15 | ], 16 | "author": "Bret Comnes", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/bcomnes/hyperserv/issues" 20 | }, 21 | "homepage": "https://github.com/bcomnes/hyperserv#readme", 22 | "devDependencies": { 23 | "@tap-format/spec": "^0.2.0", 24 | "changelog-maker": "^2.2.4", 25 | "dependency-check": "^4.1.0", 26 | "nodemon": "^1.18.9", 27 | "request": "^2.85.0", 28 | "snazzy": "^8.0.0", 29 | "standard": "^14.0.0", 30 | "tape": "^4.6.0" 31 | }, 32 | "dependencies": { 33 | "@bret/http-hash-router": "^2.0.0", 34 | "finalhandler": "^1.0.0", 35 | "morgan": "^1.7.0", 36 | "stack": "^0.1.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var request = require('request') 3 | var Hyperserv = require('./index') 4 | var assign = Object.assign 5 | var port = 8976 6 | var url = `http://localhost:${port}` 7 | 8 | var defaults = { 9 | serveStatic: false, 10 | logDetails: false 11 | } 12 | 13 | test('is function', function (t) { 14 | t.equal(typeof Hyperserv, 'function') 15 | t.end() 16 | }) 17 | 18 | test('can set and request urls urls', function (t) { 19 | var app = Hyperserv(defaults) 20 | 21 | app.router.set('/foo', function foo (req, res, opts, cb) { 22 | res.end('foo') 23 | }) 24 | 25 | app.router.set('/bar', function bar (req, res, opts, cb) { 26 | res.end('bar') 27 | }) 28 | 29 | app.httpServer.listen(port) 30 | 31 | request(`${url}/foo`, function onResp (err, resp, body) { 32 | t.error(err, 'error free response') 33 | 34 | t.equal(resp.statusCode, 200, '200 status code to /foo') 35 | t.equal(resp.body, 'foo', '/foo body === "foo"') 36 | 37 | request(`${url}/bar`, function onResp (err, resp, body) { 38 | t.error(err, 'error free response') 39 | 40 | t.equal(resp.statusCode, 200, '200 status code to /bar') 41 | t.equal(resp.body, 'bar', '/bar body === "bar"') 42 | 43 | app.httpServer.close() 44 | t.end() 45 | }) 46 | }) 47 | }) 48 | 49 | test('set routes after the server started', function (t) { 50 | var app = Hyperserv(defaults) 51 | app.httpServer.listen(port) 52 | 53 | request(`${url}/foo`, function onResp (err, resp, body) { 54 | t.error(err, 'error free response') 55 | t.equal(resp.statusCode, 404, '404 status code to /foo') 56 | 57 | app.router.set('/foo', function foo (req, res, opts, cb) { 58 | res.end('foo') 59 | }) 60 | 61 | request(`${url}/foo`, function onResp (err, resp, body) { 62 | t.error(err, 'error free response') 63 | 64 | t.equal(resp.statusCode, 200, '200 status code to /foo') 65 | t.equal(resp.body, 'foo', '/foo body === "foo"') 66 | app.httpServer.close() 67 | t.end() 68 | }) 69 | }) 70 | }) 71 | 72 | test('create custom middleware stacks', function (t) { 73 | function testLayer (req, res, next) { 74 | t.ok(req, 'middleware got req object') 75 | t.ok(res, 'middleware got res object') 76 | t.equal(typeof next, 'function', 'next is a function') 77 | t.pass('middleware layer ran fine') 78 | next() 79 | } 80 | 81 | var newLayerRun = false 82 | function newLayer (req, res, next) { 83 | newLayerRun = !newLayerRun 84 | next() 85 | } 86 | 87 | var app = Hyperserv(assign({}, defaults, { layers: [testLayer] })) 88 | app.router.set('/foo', function foo (req, res, opts, cb) { 89 | res.end('foo') 90 | }) 91 | app.httpServer.listen(port) 92 | 93 | request(`${url}/foo`, function onResp (err, resp, body) { 94 | t.error(err, 'error free response') 95 | t.equal(resp.statusCode, 200, '200 status code to /foo') 96 | t.equal(resp.body, 'foo', '/foo body === "foo"') 97 | 98 | t.doesNotThrow(app.composeStack.bind(app, [newLayer]), 'new middleware stack created') 99 | t.notOk(newLayerRun, 'middleware doesnt run right away after composition') 100 | request(`${url}/foo`, function onResp (err, resp, body) { 101 | t.error(err, 'error free response') 102 | t.equal(resp.statusCode, 200, '200 status code to /foo') 103 | t.equal(resp.body, 'foo', '/foo body === "foo"') 104 | 105 | t.ok(newLayerRun, 'middleware ran') 106 | app.httpServer.close() 107 | t.end() 108 | }) 109 | }) 110 | }) 111 | --------------------------------------------------------------------------------