├── .nvmrc ├── .gitignore ├── test ├── index.js └── FunHttp.test.js ├── src ├── index.ts ├── interfaces.ts ├── Utils.ts ├── handleResponse.ts ├── bin │ └── fun-http.ts └── FunHttp.ts ├── circle.yml ├── typings.json ├── examples ├── single-function.js ├── basic.js ├── promise.js ├── status-code.js ├── custom-header.js ├── nested-promises.js └── middleware.js ├── typings-custom ├── lodash.d.ts ├── co.d.ts └── commander.d.ts ├── tsconfig.json ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 4.4.5 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /typings 3 | /lib 4 | *.log 5 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./FunHttp.test'); 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import FunHttp from './FunHttp'; 2 | 3 | export { 4 | FunHttp, 5 | }; 6 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4.4.5 4 | test: 5 | pre: 6 | - typings install 7 | - npm run build 8 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fun-http", 3 | "globalDependencies": { 4 | "node": "registry:dt/node#6.0.0+20160602155235" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/single-function.js: -------------------------------------------------------------------------------- 1 | export default async function (req) { 2 | const result = await Promise.resolve('Hello, World!'); 3 | return result; 4 | } 5 | -------------------------------------------------------------------------------- /typings-custom/lodash.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'lodash/isString' { 2 | interface IsString { 3 | (value: any): boolean; 4 | } 5 | 6 | const isString: IsString; 7 | 8 | export = isString; 9 | } 10 | -------------------------------------------------------------------------------- /typings-custom/co.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'co' { 2 | interface Co { 3 | (func: (...args: any[]) => any): Promise; 4 | wrap: (func: (...args: any[]) => any) => (...args: any[]) => Promise; 5 | } 6 | 7 | const co: Co; 8 | 9 | export = co; 10 | } 11 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FunHttp = require('../').FunHttp; 4 | 5 | const app = new FunHttp(); 6 | 7 | app.use(function (req) { 8 | return 'Hello, World!'; 9 | }); 10 | 11 | app.listen(3000, () => { 12 | console.log('server started'); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FunHttp = require('../').FunHttp; 4 | 5 | const app = new FunHttp(); 6 | 7 | app.use(function (req) { 8 | return Promise.resolve('Hello, World!'); 9 | }); 10 | 11 | app.listen(3000, () => { 12 | console.log('server started'); 13 | }); 14 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import {IncomingMessage} from 'http'; 2 | 3 | export interface RouteHandler { 4 | (req: IncomingMessage, next: () => Promise): any; 5 | } 6 | 7 | export class NoHandlerError extends Error { 8 | constructor() { 9 | super(); 10 | this.message = 'there is no handler to handle request'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/status-code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FunHttp = require('../').FunHttp; 4 | 5 | const app = new FunHttp(); 6 | 7 | app.use(function (req) { 8 | return { 9 | status: 404, 10 | json: { 11 | reason: 'not found' 12 | } 13 | }; 14 | }); 15 | 16 | app.listen(3000, () => { 17 | console.log('server started'); 18 | }); 19 | -------------------------------------------------------------------------------- /typings-custom/commander.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'commander' { 2 | interface Program { 3 | usage: (description: string) => Program; 4 | option: (flags: string, description: string, fn: Function | any, defaultValue?: any) => Program; 5 | parse: (argv: any) => any; 6 | outputHelp: () => void; 7 | } 8 | 9 | const program: Program; 10 | 11 | export = program; 12 | } 13 | -------------------------------------------------------------------------------- /examples/custom-header.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FunHttp = require('../').FunHttp; 4 | 5 | const app = new FunHttp(); 6 | 7 | app.use(function (req) { 8 | return { 9 | headers: { 10 | 'x-really-awesome': 'yes!' 11 | }, 12 | json: { 13 | reason: 'yes indeed' 14 | } 15 | }; 16 | }); 17 | 18 | app.listen(3000, () => { 19 | console.log('server started'); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/nested-promises.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FunHttp = require('../').FunHttp; 4 | 5 | const app = new FunHttp(); 6 | 7 | app.use(function (req) { 8 | return { 9 | name: { 10 | first: Promise.resolve('John') 11 | }, 12 | age: Promise.resolve(999), 13 | slogan: 'Hello', 14 | }; 15 | }); 16 | 17 | app.listen(3000, () => { 18 | console.log('server started'); 19 | }); 20 | -------------------------------------------------------------------------------- /examples/middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FunHttp = require('../').FunHttp; 4 | 5 | const app = new FunHttp(); 6 | 7 | app.use(function (req, next) { 8 | return next().then(name => { 9 | return { 10 | hello: name, 11 | }; 12 | }); 13 | }); 14 | 15 | app.use(function () { 16 | return 'World'; 17 | }); 18 | 19 | app.listen(3000, () => { 20 | console.log('server started'); 21 | }); 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "moduleResolution": "node", 6 | "module": "commonjs", 7 | "target": "es6", 8 | "noImplicitAny": true, 9 | "outDir": "./lib", 10 | "sourceMap": true, 11 | "declaration": true, 12 | "listFiles": true, 13 | "pretty": true 14 | }, 15 | "files": [ 16 | "typings-custom/co.d.ts", 17 | "typings-custom/commander.d.ts", 18 | "typings-custom/lodash.d.ts", 19 | "typings/index.d.ts", 20 | "src/index.ts", 21 | "src/bin/fun-http.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/Utils.ts: -------------------------------------------------------------------------------- 1 | export function toPromise(obj: any): Promise { 2 | if (isPromise(obj)) { 3 | return obj; 4 | } 5 | 6 | if (Array.isArray(obj)) { 7 | return arrayToPromise(obj); 8 | } 9 | 10 | if (isObject(obj)) { 11 | return objectToPromise(obj); 12 | } 13 | 14 | return Promise.resolve(obj); 15 | } 16 | 17 | function isPromise(obj: any) { 18 | return obj != null && typeof obj.then === 'function'; 19 | } 20 | 21 | function isObject(val: any) { 22 | return val != null && Object === val.constructor; 23 | } 24 | 25 | function arrayToPromise(obj: any[]) { 26 | return Promise.all(obj.map(toPromise)); 27 | } 28 | 29 | function objectToPromise(obj: any): Promise { 30 | const results = new obj.constructor(); 31 | const keys = Object.keys(obj); 32 | const promises: Promise[] = []; 33 | 34 | for (let i = 0; i < keys.length; i += 1) { 35 | const key = keys[i]; 36 | const promise = toPromise(obj[key]); 37 | if (promise && isPromise(promise)) { 38 | defer(promise, key); 39 | } else { 40 | results[key] = obj[key]; 41 | } 42 | } 43 | 44 | return Promise.all(promises).then(() => results); 45 | 46 | function defer(promise: Promise, key: string) { 47 | promises.push(promise.then(function (res) { 48 | results[key] = res; 49 | })); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/handleResponse.ts: -------------------------------------------------------------------------------- 1 | import {ServerResponse} from 'http'; 2 | import isString = require('lodash/isString'); 3 | 4 | function handleResponseValue(res: ServerResponse, obj: any): void { 5 | if (obj == null) { 6 | res.statusCode = 200; 7 | res.end(); 8 | return; 9 | } 10 | 11 | if (isString(obj)) { 12 | res.statusCode = 200; 13 | res.end(obj); 14 | } else if (obj.status || obj.headers || obj.json || obj.text) { 15 | const headers = obj.headers; 16 | const status = obj.status; 17 | const json = obj.json; 18 | const text = obj.text; 19 | 20 | res.statusCode = status || 200; 21 | 22 | if (headers != null) { 23 | for (const key of Object.keys(headers)) { 24 | res.setHeader(key, headers[key]); 25 | } 26 | } 27 | 28 | if (json != null) { 29 | res.setHeader('Content-Type', 'application/json'); 30 | res.end(JSON.stringify(json)); 31 | } else if (text != null) { 32 | res.end(text); 33 | } else { 34 | res.end(); 35 | } 36 | } else { 37 | res.statusCode = 200; 38 | res.setHeader('Content-Type', 'application/json'); 39 | res.end(JSON.stringify(obj)); 40 | } 41 | } 42 | 43 | function handleResponse(res: ServerResponse, err: Error): void; 44 | function handleResponse(res: ServerResponse, obj: any): void; 45 | function handleResponse(res: ServerResponse, obj: Error | any): void { 46 | if (obj instanceof Error) { 47 | res.statusCode = 500; 48 | res.end(); 49 | } else { 50 | handleResponseValue(res, obj); 51 | } 52 | } 53 | 54 | export {handleResponse as default}; 55 | -------------------------------------------------------------------------------- /src/bin/fun-http.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {resolve} from 'path' 4 | import program = require('commander'); 5 | import FunHttp from '../FunHttp'; 6 | 7 | const funHttpProgram: FunHttpProgram = program 8 | .usage('[options] ') 9 | .option('-p, --port ', 'Specify port number other than 3000', (val: string) => parseInt(val), 3000) 10 | .option('-h, --host ', 'Bind to a specify host rather than listening on any interface', '0.0.0.0') 11 | .parse(process.argv); 12 | 13 | interface FunHttpProgram { 14 | args: string[]; 15 | host: string; 16 | port: number; 17 | } 18 | 19 | const file = funHttpProgram.args[0]; 20 | const host = funHttpProgram.host; 21 | const port = funHttpProgram.port; 22 | 23 | if (!file) { 24 | program.outputHelp(); 25 | process.exit(1); 26 | } 27 | 28 | const absoluteFilePath = resolve(file); 29 | 30 | const es2015ModulesCommonjs = require('babel-plugin-transform-es2015-modules-commonjs'); 31 | const syntaxAsyncFunction = require('babel-plugin-syntax-async-functions'); 32 | const asyncToModuleMethod = require('babel-plugin-transform-async-to-module-method'); 33 | 34 | require('babel-register')({ 35 | plugins: [ 36 | es2015ModulesCommonjs, 37 | syntaxAsyncFunction, 38 | [asyncToModuleMethod, {module: 'bluebird', method: 'coroutine'}] 39 | ] 40 | }); 41 | 42 | const requestHandler = require(absoluteFilePath).default || require(absoluteFilePath); 43 | const app = new FunHttp(); 44 | 45 | app.use(requestHandler); 46 | 47 | app.listen(port, host, () => { 48 | console.log(`server start to listen on ${host}:${port}...`); 49 | }); 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fun-http", 3 | "version": "0.2.1", 4 | "description": "Functional HTTP server for FUN!", 5 | "keywords": [ 6 | "middleware", 7 | "framework", 8 | "application", 9 | "http", 10 | "app", 11 | "web" 12 | ], 13 | "author": "Daiwei Lu (http://daiwei.lu/)", 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/d6u/fun-http.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/d6u/fun-http/issues" 21 | }, 22 | "homepage": "https://github.com/d6u/fun-http#readme", 23 | "main": "lib/index.js", 24 | "typings": "lib/index.d.ts", 25 | "bin": { 26 | "fun-http": "./lib/bin/fun-http.js" 27 | }, 28 | "files": [ 29 | "lib" 30 | ], 31 | "devDependencies": { 32 | "onchange": "2.5.0", 33 | "supertest": "1.2.0", 34 | "tap-spec": "4.1.1", 35 | "tape": "4.5.1", 36 | "tslint": "3.10.2", 37 | "typescript": "1.8.10", 38 | "typings": "1.0.4" 39 | }, 40 | "dependencies": { 41 | "babel-plugin-syntax-async-functions": "6.8.0", 42 | "babel-plugin-transform-async-to-module-method": "6.8.0", 43 | "babel-plugin-transform-es2015-modules-commonjs": "6.8.0", 44 | "babel-register": "6.9.0", 45 | "bluebird": "3.4.0", 46 | "commander": "2.9.0", 47 | "lodash": "4.13.1" 48 | }, 49 | "scripts": { 50 | "clean": "rm -rv lib", 51 | "build": "tsc", 52 | "watch:build": "npm run build -- -w", 53 | "test": "node test | tap-spec", 54 | "watch:test": "npm test -s; onchange 'lib/*.js' 'test/*.js' -- npm test -s", 55 | "preversion": "npm run clean -s; npm run build", 56 | "postversion": "git push && git push --tags" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/FunHttp.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createServer, 3 | Server, 4 | IncomingMessage, 5 | ServerResponse 6 | } from 'http'; 7 | import {ListenOptions} from 'net'; 8 | import {RouteHandler, NoHandlerError} from './interfaces'; 9 | import handleResponse from './handleResponse'; 10 | import {toPromise} from './Utils'; 11 | 12 | export default class FunHttp { 13 | private handlers: RouteHandler[] = []; 14 | private server: Server = null; 15 | 16 | // to be used directly for server.createServer 17 | get handler() { 18 | return this._handler.bind(this); 19 | } 20 | 21 | use(handler: RouteHandler) { 22 | this.handlers.push(handler); 23 | } 24 | 25 | listen(port: number, listeningListener?: Function): void; 26 | listen(path: string, listeningListener?: Function): void; 27 | listen(handle: any, listeningListener?: Function): void; 28 | listen(options: ListenOptions, listeningListener?: Function): void; 29 | listen(port: number, hostname?: string, listeningListener?: Function): void; 30 | listen(port: number, backlog?: number, listeningListener?: Function): void; 31 | listen(path: string, backlog?: number, listeningListener?: Function): void; 32 | listen(handle: any, backlog?: number, listeningListener?: Function): void; 33 | listen(port: number, hostname?: string, backlog?: number, listeningListener?: Function): void; 34 | listen(): void { 35 | this.server = createServer((req, res) => this.handler(req, res)); 36 | this.server.listen.apply(this.server, arguments); 37 | } 38 | 39 | private _handler(req: IncomingMessage, res: ServerResponse) { 40 | const next = (index: number): Promise => { 41 | if (index < this.handlers.length) { 42 | try { 43 | return toPromise(this.handlers[index](req, () => next(index + 1))); 44 | } catch (err) { 45 | return Promise.reject(err); 46 | } 47 | } else { 48 | return Promise.reject(new NoHandlerError()); 49 | } 50 | } 51 | 52 | next(0) 53 | .then((obj) => handleResponse(res, obj)) 54 | .catch((err) => handleResponse(res, err)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/FunHttp.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const supertest = require('supertest'); 5 | const FunHttp = require('../').FunHttp; 6 | 7 | test('return text content', (t) => { 8 | t.plan(3); 9 | 10 | const app = new FunHttp(); 11 | app.use(function () { 12 | return 'Hello, World!'; 13 | }); 14 | 15 | supertest(app.handler) 16 | .get('/') 17 | .end((err, res) => { 18 | t.notOk(err); 19 | t.equal(res.statusCode, 200); 20 | t.equal(res.text, 'Hello, World!'); 21 | }); 22 | }); 23 | 24 | test('return JSON content', (t) => { 25 | t.plan(3); 26 | 27 | const app = new FunHttp(); 28 | app.use(function () { 29 | return { 30 | hello: 'world' 31 | }; 32 | }); 33 | 34 | supertest(app.handler) 35 | .get('/') 36 | .end((err, res) => { 37 | t.notOk(err); 38 | t.equal(res.statusCode, 200); 39 | t.equal(res.text, JSON.stringify({hello: 'world'})); 40 | }); 41 | }); 42 | 43 | test('wait for promise to resolve', (t) => { 44 | t.plan(3); 45 | 46 | const app = new FunHttp(); 47 | app.use(function () { 48 | return Promise.resolve('Hello, World!'); 49 | }); 50 | 51 | supertest(app.handler) 52 | .get('/') 53 | .end((err, res) => { 54 | t.notOk(err); 55 | t.equal(res.statusCode, 200); 56 | t.equal(res.text, 'Hello, World!'); 57 | }); 58 | }); 59 | 60 | test('wait for array of promises to resolve', (t) => { 61 | t.plan(3); 62 | 63 | const app = new FunHttp(); 64 | app.use(function () { 65 | return [ 66 | Promise.resolve('Hello'), 67 | Promise.resolve('World'), 68 | ]; 69 | }); 70 | 71 | supertest(app.handler) 72 | .get('/') 73 | .end((err, res) => { 74 | t.notOk(err); 75 | t.equal(res.statusCode, 200); 76 | t.equal(res.text, JSON.stringify(['Hello', 'World'])); 77 | }); 78 | }); 79 | 80 | test('wait for nested promise objects to resolve', (t) => { 81 | t.plan(3); 82 | 83 | const app = new FunHttp(); 84 | app.use(function () { 85 | return { 86 | hello: Promise.resolve('world'), 87 | }; 88 | }); 89 | 90 | supertest(app.handler) 91 | .get('/') 92 | .end((err, res) => { 93 | t.notOk(err); 94 | t.equal(res.statusCode, 200); 95 | t.equal(res.text, JSON.stringify({hello: 'world'})); 96 | }); 97 | }); 98 | 99 | test('recognize custom status code', (t) => { 100 | t.plan(2); 101 | 102 | const app = new FunHttp(); 103 | app.use(function () { 104 | return { 105 | status: 404 106 | }; 107 | }); 108 | 109 | supertest(app.handler) 110 | .get('/') 111 | .end((err, res) => { 112 | t.notOk(err); 113 | t.equal(res.statusCode, 404); 114 | }); 115 | }); 116 | 117 | test('recognize custom headers', (t) => { 118 | t.plan(2); 119 | 120 | const app = new FunHttp(); 121 | app.use(function () { 122 | return { 123 | headers: { 124 | 'x-header-test': 'done' 125 | } 126 | }; 127 | }); 128 | 129 | supertest(app.handler) 130 | .get('/') 131 | .end((err, res) => { 132 | t.notOk(err); 133 | t.equal(res.headers['x-header-test'], 'done'); 134 | }); 135 | }); 136 | 137 | test('recognize json field', (t) => { 138 | t.plan(2); 139 | 140 | const app = new FunHttp(); 141 | app.use(function () { 142 | return { 143 | json: 'Hello World' 144 | }; 145 | }); 146 | 147 | supertest(app.handler) 148 | .get('/') 149 | .end((err, res) => { 150 | t.notOk(err); 151 | t.equal(res.text, JSON.stringify('Hello World')); 152 | }); 153 | }); 154 | 155 | test('recognize text field', (t) => { 156 | t.plan(2); 157 | 158 | const app = new FunHttp(); 159 | app.use(function () { 160 | return { 161 | text: 'Hello World' 162 | }; 163 | }); 164 | 165 | supertest(app.handler) 166 | .get('/') 167 | .end((err, res) => { 168 | t.notOk(err); 169 | t.equal(res.text, 'Hello World'); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :monkey_face: Fun(ctional) Http 2 | 3 | [![npm version](https://badge.fury.io/js/fun-http.svg)](https://badge.fury.io/js/fun-http) 4 | [![CircleCI](https://circleci.com/gh/d6u/fun-http/tree/master.svg?style=svg)](https://circleci.com/gh/d6u/fun-http/tree/master) 5 | 6 | _Possibly the easiest way to start a HTTP server in Node.js._ 7 | 8 | HTTP server should be as stateless as possible like functions. We should write request handlers in pure funtion. Let's treat return value as responses and use [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to wrap async operations. 9 | 10 | ## Usage 11 | 12 | **fun-http** supports Node >= 4. 13 | 14 | ### As a CLI 15 | 16 | 1. Install as global package 17 | 18 | ```sh 19 | $ npm install -g fun-http 20 | ``` 21 | 22 | 2. Define the server, only need to export a function. 23 | 24 | ```js 25 | // server.js 26 | export default async function (req) { 27 | return 'Hello, World!'; 28 | } 29 | ``` 30 | 31 | 3. Fire the server, and tada~ :tada: 32 | 33 | ```sh 34 | $ fun-http server.js 35 | $ curl -i localhost:3000 36 | 37 | HTTP/1.1 200 OK 38 | Date: Sun, 12 Jun 2016 22:12:41 GMT 39 | Connection: keep-alive 40 | Content-Length: 13 41 | 42 | Hello, World! 43 | ``` 44 | 45 | ### As a module 46 | 47 | ```sh 48 | npm install --save fun-http 49 | ``` 50 | 51 | ```js 52 | const FunHttp = require('fun-http').FunHttp; 53 | const app = new FunHttp(); 54 | 55 | app.use(function (req) { 56 | return 'Hello, World!'; 57 | }); 58 | 59 | app.listen(3000, () => { 60 | console.log('server started'); 61 | }); 62 | ``` 63 | 64 | Visit `localhost:3000` will display "Hello, World!". 65 | 66 | You can also return promise in request handlers, the resolved value will become the body of response. 67 | 68 | ```js 69 | app.use(function (req) { 70 | return Promise.resolve('Hello, World!'); 71 | }); 72 | ``` 73 | 74 | If returned value (or promise resolved value) is not a number or string, it will be parsed into JSON string using `JSON.stringify`. 75 | 76 | ## Customize Response 77 | 78 | **fun-http** will look for special structure on values returned from request handler to customize response's status and headers. 79 | 80 | ```js 81 | app.use(function () { 82 | return { 83 | status: 404, 84 | json: { 85 | reason: 'not found' 86 | } 87 | }; 88 | }); 89 | ``` 90 | 91 | ``` 92 | $ curl -i localhost:3000 93 | 94 | HTTP/1.1 404 Not Found 95 | Date: Fri, 10 Jun 2016 00:14:34 GMT 96 | Connection: keep-alive 97 | Content-Length: 22 98 | 99 | {"reason":"not found"} 100 | ``` 101 | 102 | For custom headers: 103 | 104 | ```js 105 | app.use(function (req) { 106 | return { 107 | headers: { 108 | 'x-really-awesome': 'yes!' 109 | } 110 | }; 111 | }); 112 | ``` 113 | 114 | ``` 115 | $ curl -i localhost:3000 116 | 117 | HTTP/1.1 200 OK 118 | x-really-awesome: yes! 119 | Date: Fri, 10 Jun 2016 00:14:02 GMT 120 | Connection: keep-alive 121 | Content-Length: 0 122 | ``` 123 | 124 | You can also force **fun-http** to return text: 125 | 126 | ```js 127 | app.use(function (req) { 128 | return { 129 | text: 'I just want to tell you...' 130 | }; 131 | }); 132 | ``` 133 | 134 | ## Middleware 135 | 136 | **fun-http** supports middleware similar to Koa. Calling `next` will invoke next middleware and return a promise wrapping the return value of that middleware. 137 | 138 | ```js 139 | app.use(function (req, next) { 140 | return next() 141 | .then(name => { 142 | return {hello: name}; // name === 'World' 143 | }); 144 | }); 145 | 146 | app.use(function () { 147 | return 'World'; 148 | }); 149 | ``` 150 | 151 | Paire with [co](https://www.npmjs.com/package/co), you can make everything cleaner. 152 | 153 | ```js 154 | const co = require('co'); 155 | 156 | app.use(co.wrap(function *(req, next) { 157 | const name = yield next(); 158 | return {hello: name}; 159 | })); 160 | 161 | app.use(function () { 162 | return 'World'; 163 | }); 164 | ``` 165 | 166 | Or you can even use it with async/await functions: 167 | 168 | ```js 169 | app.use(async function (req, next) { 170 | const name = await next(); 171 | return {hello: name}; 172 | }); 173 | 174 | app.use(function () { 175 | return 'World'; 176 | }); 177 | ``` 178 | 179 | _Of cause, Node.js doesn't currently support async/await functions, you will need to use transpiler like Babel to transpile the source._ 180 | 181 | ## Examples 182 | 183 | Check out [examples](./examples). 184 | 185 | ## TypeScript 186 | 187 | Did I tell you **fun-http** is written using [TypeScript](http://www.typescriptlang.org/)? 188 | --------------------------------------------------------------------------------