diff --git a/Readme.md b/Readme.md index ca78a8a..ff1c2c1 100644 --- a/Readme.md +++ b/Readme.md @@ -5,7 +5,7 @@ [![build status][travis-image]][travis-url] [![Test coverage][coveralls-image]][coveralls-url] - Expressive HTTP middleware for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-like manner, allowing you to perform actions downstream then filter and manipulate the response upstream. Koa's use of generators also greatly increases the readability and robustness of your application. + Expressive HTTP middleware framework for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-like manner, allowing you to perform actions downstream then filter and manipulate the response upstream. Only methods that are common to nearly all HTTP servers are integrated directly into Koa's small ~570 SLOC codebase. This includes things like content negotiation, normalization of node inconsistencies, redirection, and a few others. @@ -13,12 +13,130 @@ Koa is not bundled with any middleware. ## Installation +Koa requires __node v4.0.0__ or higher for (partial) ES2015 support. ``` -$ npm install koa +$ npm install koa@next ``` - Koa requires __node v4.0.0__ or higher for (partial) ES2015 support. +## Hello koa + +```js +const Koa = require('koa'); +const app = new Koa(); + +// response +app.use(ctx => { + ctx.body = 'Hello Koa'; +}); + +app.listen(3000); +``` + +## Getting started + + - [Kick-Off-Koa](https://github.com/koajs/kick-off-koa) - An intro to koa via a set of self-guided workshops. + - [Workshop](https://github.com/koajs/workshop) - A workshop to learn the basics of koa, Express' spiritual successor. + - [Introduction Screencast](http://knowthen.com/episode-3-koajs-quickstart-guide/) - An introduction to installing and getting started with Koa + + +## Middleware +Koa is an middleware framework, it can take 3 different kind function as middleware: + + * common function + * async function + * generatorFunction + +Here we write an logger middleware with different function. + +### Common function +```js + +// Middleware normally take two parameters (ctx, next), ctx is the context for one request, +// next is an function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion. + +app.use((ctx, next) => { + const start = new Date; + return next().then(() => { + const ms = new Date - start; + console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); + }); +}); + +``` + +### ___async___ functions (Babel required) + +```js + +app.use(async (ctx, next) => { + const start = new Date; + await next(); + const ms = new Date - start; + console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); +}); + +``` + +### GeneratorFunction + +To use generator functions, you must use a wrapper such as [co](https://github.com/tj/co) that is no longer supplied with Koa. + +```js + +app.use(co.wrap(function *(ctx, next){ + const start = new Date; + yield next(); + const ms = new Date - start; + console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); +})); + +``` + +### Old signature middleware (v1.x) + +If you want to use old signature or be compatible with old middleware, you must use [koa-convert](https://github.com/gyson/koa-convert) to convert legacy generator middleware to promise middleware. + +```js +const convert = require('koa-convert') + +app.use(convert(function *(next){ + const start = new Date; + yield next; + const ms = new Date - start; + console.log(`${this.method} ${this.url} - ${ms}ms`); +})); + +``` + + +## Babel setup +For Node 4.0 and Babel 6.0 you can setup like this + +```bash +// install babel and required presets +$ npm install babel-core --save +$ npm install babel-preset-es2015-node5 --save +$ npm install babel-preset-stage-3 --save +``` + +```js +// set babel in entry file +require("babel-core/register")({ + presets: ['es2015-node5', 'stage-3'] +}); +``` + + +## Running tests + +``` +$ make test +``` + +## Authors + +See [AUTHORS](AUTHORS). ## Community @@ -35,123 +153,6 @@ $ npm install koa - [中文文档](https://github.com/turingou/koa-guide) - __[#koajs]__ on freenode -## Getting started - - - [Kick-Off-Koa](https://github.com/koajs/kick-off-koa) - An intro to koa via a set of self-guided workshops. - - [Workshop](https://github.com/koajs/workshop) - A workshop to learn the basics of koa, Express' spiritual successor. - - [Introduction Screencast](http://knowthen.com/episode-3-koajs-quickstart-guide/) - An introduction to installing and getting started with Koa - -## Example -```js -const Koa = require('koa'); -const app = new Koa(); - -// logger - -app.use((ctx, next) => { - const start = new Date; - return next().then(() => { - const ms = new Date - start; - console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); - }); -}); - -// response - -app.use(ctx => { - ctx.body = 'Hello World'; -}); - -app.listen(3000); -``` - -## Example with ___async___ functions (Babel required) - -```js -const Koa = require('koa'); -const app = new Koa(); - -// logger - -app.use(async (ctx, next) => { - const start = new Date; - await next(); - const ms = new Date - start; - console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); -}); - -// response - -app.use(ctx => { - ctx.body = 'Hello World'; -}); - -app.listen(3000); -``` - -## Example with generator - -To use generator functions, you must use a wrapper such as [co](https://github.com/tj/co) that is no longer supplied with Koa. - -```js -const Koa = require('koa'); -const app = new Koa(); -const co = require('co'); - -// logger - -app.use(co.wrap(function *(ctx, next){ - const start = new Date; - yield next(); - const ms = new Date - start; - console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); -})); - -// response - -app.use(ctx => { - ctx.body = 'Hello World'; -}); - -app.listen(3000); -``` - -## Example with old signature - -If you want to use old signature or be compatible with old middleware, you must use [koa-convert](https://github.com/gyson/koa-convert) to convert legacy generator middleware to promise middleware. - -```js -const Koa = require('koa'); -const app = new Koa(); -const convert = require('koa-convert') - -// logger - -app.use(convert(function *(next){ - const start = new Date; - yield next; - const ms = new Date - start; - console.log(`${this.method} ${this.url} - ${ms}ms`); -})); - -// response - -app.use(ctx => { - ctx.body = 'Hello World'; -}); - -app.listen(3000); -``` - -## Running tests - -``` -$ make test -``` - -## Authors - -See [AUTHORS](AUTHORS). # License diff --git a/docs/api/index.md b/docs/api/index.md index 9135337..6fc2a7b 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,90 +1,15 @@ # Installation - Koa is supported in all versions of [iojs](https://iojs.org) without any flags. + Koa requires __node v4.0.0__ or higher for (partial) ES2015 support. - To use Koa with node, you must be running __node 0.11.16__ or higher for generator and promise support, and must run node(1) - with the `--harmony-generators` or `--harmony` flag. - - You can quickly install a supposed version of node/iojs with your favorite version manager: + You can quickly install a supposed version of node with your favorite version manager: ```bash -$ nvm install iojs +$ nvm install v4.0.0 $ npm i koa $ node my-koa-app.js ``` -# Application - - A Koa application is an object containing an array of middleware generator functions - which are composed and executed in a stack-like manner upon request. Koa is similar to many - other middleware systems that you may have encountered such as Ruby's Rack, Connect, and so on - - however a key design decision was made to provide high level "sugar" at the otherwise low-level - middleware layer. This improves interoperability, robustness, and makes writing middleware much - more enjoyable. - - This includes methods for common tasks like content-negotiation, cache freshness, proxy support, and redirection - among others. Despite supplying a reasonably large number of helpful methods Koa maintains a small footprint, as - no middleware are bundled. - - The obligatory hello world application: - -```js -const Koa = require('koa'); -const app = new Koa(); - -app.use(function *(){ - this.body = 'Hello World'; -}); - -app.listen(3000); -``` - -## Cascading - - Koa middleware cascade in a more traditional way as you may be used to with similar tools - - this was previously difficult to make user friendly with node's use of callbacks. - However with generators we can achieve "true" middleware. Contrasting Connect's implementation which - simply passes control through series of functions until one returns, Koa yields "downstream", then - control flows back "upstream". - - The following example responds with "Hello World", however first the request flows through - the `x-response-time` and `logging` middleware to mark when the request started, then continue - to yield control through the response middleware. When a middleware invokes `yield next` - the function suspends and passes control to the next middleware defined. After there are no more - middleware to execute downstream, the stack will unwind and each middleware is resumed to perform - its upstream behaviour. - -```js -const Koa = require('koa'); -const app = new Koa(); - -// x-response-time - -app.use(function *(next){ - const start = new Date; - yield next; - const ms = new Date - start; - this.set('X-Response-Time', `${ms}ms`); -}); - -// logger - -app.use(function *(next){ - const start = new Date; - yield next; - const ms = new Date - start; - console.log(`${this.method} ${this.url} - ${ms}`); -}); - -// response - -app.use(function *(){ - this.body = 'Hello World'; -}); - -app.listen(3000); -``` - ## Async Functions with Babel To use `async` functions in Koa, we recommend using [babel's require hook](http://babeljs.io/docs/usage/require/). @@ -106,7 +31,79 @@ For example, in your `.babelrc` file, you should have: } ``` -Currently, you could also use the [stage-3 preset](http://babeljs.io/docs/plugins/preset-stage-3/). +You can also use the [stage-3 preset](http://babeljs.io/docs/plugins/preset-stage-3/) instead. + +# Application + + A Koa application is an object containing an array of middleware functions + which are composed and executed in a stack-like manner upon request. Koa is similar to many + other middleware systems that you may have encountered such as Ruby's Rack, Connect, and so on - + however a key design decision was made to provide high level "sugar" at the otherwise low-level + middleware layer. This improves interoperability, robustness, and makes writing middleware much + more enjoyable. + + This includes methods for common tasks like content-negotiation, cache freshness, proxy support, and redirection + among others. Despite supplying a reasonably large number of helpful methods Koa maintains a small footprint, as + no middleware are bundled. + + The obligatory hello world application: + +```js +const Koa = require('koa'); +const app = new Koa(); + +app.use(ctx => { + ctx.body = 'Hello World'; +}); + +app.listen(3000); +``` + +## Cascading + + Koa middleware cascade in a more traditional way as you may be used to with similar tools - + this was previously difficult to make user friendly with node's use of callbacks. + However with async functions we can achieve "true" middleware. Contrasting Connect's implementation which + simply passes control through series of functions until one returns, Koa invoke "downstream", then + control flows back "upstream". + + The following example responds with "Hello World", however first the request flows through + the `x-response-time` and `logging` middleware to mark when the request started, then continue + to yield control through the response middleware. When a middleware invokes `next()` + the function suspends and passes control to the next middleware defined. After there are no more + middleware to execute downstream, the stack will unwind and each middleware is resumed to perform + its upstream behaviour. + +```js +const Koa = require('koa'); +const app = new Koa(); + +// x-response-time + +app.use(async function (ctx, next){ + const start = new Date; + await next(); + const ms = new Date - start; + ctx.set('X-Response-Time', `${ms}ms`); +}); + +// logger + +app.use(async function (ctx, next){ + const start = new Date; + await next(); + const ms = new Date - start; + console.log(`${ctx.method} ${ctx.url} - ${ms}`); +}); + +// response + +app.use((ctx) => { + ctx.body = 'Hello World'; +}); + +app.listen(3000); +``` ## Settings diff --git a/docs/faq.md b/docs/faq.md index f18b63f..81a033d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -16,16 +16,10 @@ ## Does Koa replace Connect? No, just a different take on similar functionality - now that generators allow us to write code with less + now that async functions allow us to write code with less callbacks. Connect is equally capable, and some may still prefer it, it's up to what you prefer. -## Do generators decrease performance? - - Barely - check out the benchmarks in our readme, the numbers - are more than fine, and there's no substitute for proper - horizontal scaling. - ## Does Koa include routing? No - out of the box Koa has no form of routing, however @@ -41,15 +35,15 @@ ## What custom properties do the Koa objects have? - Koa uses its own custom objects: `this`, `this.request`, and `this.response`. + Koa uses its own custom objects: `ctx`, `ctx.request`, and `ctx.response`. These objects abstract node's `req` and `res` objects with convenience methods and getters/setters. Generally, properties added to these objects must obey the following rules: - They must be either very commonly used and/or must do something useful - If a property exists as a setter, then it will also exist as a getter, but not vice versa -Many of `this.request` and `this.response`'s properties are delegated to `this`. +Many of `ctx.request` and `ctx.response`'s properties are delegated to `ctx`. If it's a getter/setter, then both the getter and the setter will strictly -correspond to either `this.request` or `this.response`. +correspond to either `ctx.request` or `ctx.response`. Please think about these rules before suggesting additional properties. diff --git a/docs/guide.md b/docs/guide.md index 5c870d6..70e46e2 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -1,51 +1,39 @@ # Guide - This guide covers Koa topics are not directly API related, such as best practices for writing middleware, - application structure suggestions. + This guide covers Koa topics that are not directly API related, such as best practices for writing middleware, application structure suggestions, here we use async function as middleware you can also you commonFunction or generatorFunction which will be a little different. ## Writing Middleware - Koa middleware are simple functions which return a `GeneratorFunction`, and accept another. When - the middleware is run by an "upstream" middleware, it must manually `yield` to the "downstream" middleware. + Koa middleware are simple functions which return a `MiddlewareFunction` with signature (ctx, next). When + the middleware is run by an "upstream" middleware, it must manually invoke `next()` to run the "downstream" middleware. For example if you wanted to track how long it takes for a request to propagate through Koa by adding an `X-Response-Time` header field the middleware would look like the following: ```js -function *responseTime(next) { +async function responseTime(ctx, next) { const start = new Date; - yield next; + await next(); const ms = new Date - start; - this.set('X-Response-Time', `${ms}ms`); + ctx.set('X-Response-Time', `${ms}ms`); } app.use(responseTime); ``` - Here's another way to write the same thing, inline: - -```js -app.use(function *(next){ - const start = new Date; - yield next; - const ms = new Date - start; - this.set('X-Response-Time', `${ms}ms`); -}); -``` - - If you're a front-end developer you can think any code before `yield next;` as the "capture" phase, - while any code after is the "bubble" phase. This crude gif illustrates how ES6 generators allow us + If you're a front-end developer you can think any code before `next();` as the "capture" phase, + while any code after is the "bubble" phase. This crude gif illustrates how async function allow us to properly utilize stack flow to implement request and response flows: ![koa middleware](/docs/middleware.gif) 1. Create a date to track duration - 2. Yield control to the next middleware + 2. Await control to the next middleware 3. Create another date to track response time - 4. Yield control to the next middleware - 5. Yield immediately since `contentLength` only works with responses - 6. Yield upstream to Koa's noop middleware + 4. Await control to the next middleware + 5. Await immediately since `contentLength` only works with responses + 6. Await upstream to Koa's noop middleware 7. Ignore setting the body unless the path is "/" 8. Set the response to "Hello World" 9. Ignore setting `Content-Length` when no body is present @@ -55,18 +43,18 @@ app.use(function *(next){ 13. Hand off to Koa to handle the response -Note that the final middleware (step __6__) yields to what looks to be nothing - it's actually -yielding to a no-op generator within Koa. This is so that every middleware can conform with the -same API, and may be placed before or after others. If you removed `yield next;` from the furthest +Note that the final middleware (step __6__) await to what looks to be nothing - it's actually +yielding to a no-op promise within Koa. This is so that every middleware can conform with the +same API, and may be placed before or after others. If you removed `next();` from the furthest "downstream" middleware everything would function appropriately, however it would no longer conform to this behaviour. For example this would be fine: ```js -app.use(function *response(){ +app.use(async function response(ctx, next){ if ('/' != this.url) return; - this.body = 'Hello World'; + ctx.body = 'Hello World'; }); ``` @@ -91,14 +79,14 @@ app.use(function *response(){ function logger(format) { format = format || ':method ":url"'; - return function *(next){ + return async function (ctx, next){ const str = format - .replace(':method', this.method) - .replace(':url', this.url); + .replace(':method', ctx.method) + .replace(':url', ctx.url); console.log(str); - yield next; + await next(); } } @@ -112,121 +100,120 @@ app.use(logger(':method :url')); ```js function logger(format) { - return function *logger(next){ + return async function logger(ctx, next){ } } ``` -### Combining multiple middleware +### Combining multiple middleware with koa-compose - Sometimes you want to "compose" multiple middleware into a single middleware for easy re-use or exporting. To do so, you may chain them together with `.call(this, next)`s, then return another function that yields the chain. + Sometimes you want to "compose" multiple middleware into a single middleware for easy re-use or exporting. You can use [koa-compose](https://github.com/koajs/compose) ```js -function *random(next) { +const compose = require('koa-compose'); + +async function random(ctx, next) { if ('/random' == this.path) { - this.body = Math.floor(Math.random()*10); + ctx.body = Math.floor(Math.random()*10); } else { - yield next; + await next(); } }; -function *backwards(next) { +async function backwards(ctx, next) { if ('/backwards' == this.path) { - this.body = 'sdrawkcab'; + ctx.body = 'sdrawkcab'; } else { - yield next; + await next(); } } -function *pi(next) { +async function pi(ctx, next) { if ('/pi' == this.path) { - this.body = String(Math.PI); + ctx.body = String(Math.PI); } else { - yield next; + await next(); } } -function *all(next) { - yield random.call(this, backwards.call(this, pi.call(this, next))); -} +const all = compose([random, backwards, pi]) app.use(all); ``` - This is exactly what [koa-compose](https://github.com/koajs/compose) does, which Koa internally uses to create and dispatch the middleware stack. + ### Response Middleware Middleware that decide to respond to a request and wish to bypass downstream middleware may - simply omit `yield next`. Typically this will be in routing middleware, but this can be performed by + simply omit `next()`. Typically this will be in routing middleware, but this can be performed by any. For example the following will respond with "two", however all three are executed, giving the downstream "three" middleware a chance to manipulate the response. ```js -app.use(function *(next){ +app.use(async function (ctx, next){ console.log('>> one'); - yield next; - console.log('<< one'); + await next(); + console.log('<< one'); }); -app.use(function *(next){ +app.use(async function (ctx, next){ console.log('>> two'); - this.body = 'two'; - yield next; + ctx.body = 'two'; + await next(); console.log('<< two'); }); -app.use(function *(next){ +app.use(async function (ctx, next){ console.log('>> three'); - yield next; + await next(); console.log('<< three'); }); ``` - The following configuration omits `yield next` in the second middleware, and will still respond + The following configuration omits `next()` in the second middleware, and will still respond with "two", however the third (and any other downstream middleware) will be ignored: ```js -app.use(function *(next){ +app.use(async function (ctx, next){ console.log('>> one'); - yield next; - console.log('<< one'); + await next(); + console.log('<< one'); }); -app.use(function *(next){ +app.use(async function (ctx, next){ console.log('>> two'); - this.body = 'two'; + ctx.body = 'two'; console.log('<< two'); }); -app.use(function *(next){ +app.use(async function (ctx, next){ console.log('>> three'); - yield next; + await next(); console.log('<< three'); }); ``` - When the furthest downstream middleware executes `yield next;` it's really yielding to a noop + When the furthest downstream middleware executes `next();`, it's really yielding to a noop function, allowing the middleware to compose correctly anywhere in the stack. ## Async operations - The [Co](https://github.com/visionmedia/co) forms Koa's foundation for generator delegation, allowing + Async function and promise forms Koa's foundation, allowing you to write non-blocking sequential code. For example this middleware reads the filenames from `./docs`, and then reads the contents of each markdown file in parallel before assigning the body to the joint result. ```js -const fs = require('co-fs'); +const fs = require('fs-promise'); -app.use(function *(){ - const paths = yield fs.readdir('docs'); +app.use(async function (ctx, next){ + const paths = await fs.readdir('docs'); + const files = await Promise.all(paths.map(path => fs.readFile(`docs/${path}`, 'utf8'))); - const files = yield paths.map(path => fs.readFile(`docs/${path}`, 'utf8')); - - this.type = 'markdown'; - this.body = files.join(''); + ctx.type = 'markdown'; + ctx.body = files.join(''); }); ``` diff --git a/docs/koa-vs-express.md b/docs/koa-vs-express.md index ecd65b4..d549740 100644 --- a/docs/koa-vs-express.md +++ b/docs/koa-vs-express.md @@ -21,7 +21,7 @@ THIS DOCUMENT IS IN PROGRESS. THIS PARAGRAPH SHALL BE REMOVED WHEN THIS DOCUMENT Thus, if you'd like to be closer to node.js and traditional node.js-style coding, you probably want to stick to Connect/Express or similar frameworks. - If you want to dive into the land of generators, use Koa. + If you want to get rid of callbacks, use Koa. As result of this different philosophy is that traditional node.js "middleware", i.e. functions of the form `(req, res, next)`, are incompatible with Koa. Your application will essentially have to be rewritten from the ground, up. diff --git a/lib/request.js b/lib/request.js index f7ed45c..7dcccf9 100644 --- a/lib/request.js +++ b/lib/request.js @@ -11,6 +11,7 @@ const parse = require('parseurl'); const qs = require('querystring'); const typeis = require('type-is'); const fresh = require('fresh'); +const only = require('only'); /** * Prototype. @@ -618,10 +619,10 @@ module.exports = { */ toJSON() { - return { - method: this.method, - url: this.url, - header: this.header - }; + return only(this, [ + 'method', + 'url', + 'header' + ]); } }; diff --git a/lib/response.js b/lib/response.js index 903f555..2ab0c2b 100644 --- a/lib/response.js +++ b/lib/response.js @@ -15,9 +15,9 @@ const typeis = require('type-is').is; const statuses = require('statuses'); const destroy = require('destroy'); const assert = require('assert'); -const path = require('path'); +const extname = require('path').extname; const vary = require('vary'); -const extname = path.extname; +const only = require('only'); /** * Prototype. @@ -516,10 +516,10 @@ module.exports = { */ toJSON() { - return { - status: this.status, - message: this.message, - header: this.header - }; + return only(this, [ + 'status', + 'message', + 'header' + ]); } };