guide.md 7.1 KB
Newer Older
T
TJ Holowaychuk 已提交
1

T
docs  
TJ Holowaychuk 已提交
2 3
# Guide

Z
Zack Tanner 已提交
4
  This guide covers Koa topics that are not directly API related, such as best practices for writing middleware and application structure suggestions. In these examples we use async functions as middleware - you can also use commonFunction or generatorFunction which will be a little different.
T
docs  
TJ Holowaychuk 已提交
5

6 7
## Table of Contents

8 9 10 11 12 13 14 15 16 17
- [Guide](#guide)
  - [Table of Contents](#table-of-contents)
  - [Writing Middleware](#writing-middleware)
  - [Middleware Best Practices](#middleware-best-practices)
    - [Middleware options](#middleware-options)
    - [Named middleware](#named-middleware)
    - [Combining multiple middleware with koa-compose](#combining-multiple-middleware-with-koa-compose)
    - [Response Middleware](#response-middleware)
  - [Async operations](#async-operations)
  - [Debugging Koa](#debugging-koa)
18

T
TJ Holowaychuk 已提交
19 20
## Writing Middleware

P
pana 已提交
21
  Koa middleware are simple functions which return a `MiddlewareFunction` with signature (ctx, next). When
22
  the middleware is run, it must manually invoke `next()` to run the "downstream" middleware.
T
TJ Holowaychuk 已提交
23 24 25 26 27

  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
P
pana 已提交
28
async function responseTime(ctx, next) {
29
  const start = Date.now();
P
pana 已提交
30
  await next();
31
  const ms = Date.now() - start;
P
pana 已提交
32
  ctx.set('X-Response-Time', `${ms}ms`);
T
TJ Holowaychuk 已提交
33 34 35 36 37
}

app.use(responseTime);
```

P
pana 已提交
38 39
  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
T
docs  
TJ Holowaychuk 已提交
40 41
  to properly utilize stack flow to implement request and response flows:

42
![Koa middleware](/docs/middleware.gif)
T
docs  
TJ Holowaychuk 已提交
43

44
   1. Create a date to track response time
P
pana 已提交
45
   2. Await control to the next middleware
46
   3. Create another date to track duration
P
pana 已提交
47
   4. Await control to the next middleware
48 49 50 51 52 53
   5. Set the response body to "Hello World"
   6. Calculate duration time
   7. Output log line
   8. Calculate response time
   9. Set `X-Response-Time` header field
   10. Hand off to Koa to handle the response
T
docs  
TJ Holowaychuk 已提交
54 55

 Next we'll look at the best practices for creating Koa middleware.
T
TJ Holowaychuk 已提交
56 57 58

## Middleware Best Practices

T
docs  
TJ Holowaychuk 已提交
59 60 61 62 63
  This section covers middleware authoring best practices, such as middleware
  accepting options, named middleware for debugging, among others.

### Middleware options

64
  When creating a public middleware, it's useful to conform to the convention of
T
TJ Holowaychuk 已提交
65 66 67 68 69 70 71 72
  wrapping the middleware in a function that accepts options, allowing users to
  extend functionality. Even if your middleware accepts _no_ options, this is still
  a good idea to keep things uniform.

  Here our contrived `logger` middleware accepts a `format` string for customization,
  and returns the middleware itself:

```js
D
ocd  
Dmitry Mazuro 已提交
73
function logger(format) {
T
TJ Holowaychuk 已提交
74 75
  format = format || ':method ":url"';

R
Robin Pokorný 已提交
76
  return async function (ctx, next) {
P
pana 已提交
77
    const str = format
P
pana 已提交
78 79
      .replace(':method', ctx.method)
      .replace(':url', ctx.url);
T
TJ Holowaychuk 已提交
80

81
    console.log(str);
T
TJ Holowaychuk 已提交
82

P
pana 已提交
83
    await next();
R
Robin Pokorný 已提交
84
  };
T
TJ Holowaychuk 已提交
85 86 87 88 89 90
}

app.use(logger());
app.use(logger(':method :url'));
```

T
docs  
TJ Holowaychuk 已提交
91 92
### Named middleware

T
TJ Holowaychuk 已提交
93
  Naming middleware is optional, however it's useful for debugging purposes to assign a name.
T
docs  
TJ Holowaychuk 已提交
94 95

```js
D
ocd  
Dmitry Mazuro 已提交
96
function logger(format) {
R
Robin Pokorný 已提交
97
  return async function logger(ctx, next) {
98

R
Robin Pokorný 已提交
99
  };
T
docs  
TJ Holowaychuk 已提交
100 101 102
}
```

P
pana 已提交
103
### Combining multiple middleware with koa-compose
104

P
pana 已提交
105
  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)
106 107

```js
P
pana 已提交
108 109 110
const compose = require('koa-compose');

async function random(ctx, next) {
Q
qingming 已提交
111
  if ('/random' == ctx.path) {
R
Robin Pokorný 已提交
112
    ctx.body = Math.floor(Math.random() * 10);
113
  } else {
P
pana 已提交
114
    await next();
115 116 117
  }
};

P
pana 已提交
118
async function backwards(ctx, next) {
Q
qingming 已提交
119
  if ('/backwards' == ctx.path) {
P
pana 已提交
120
    ctx.body = 'sdrawkcab';
121
  } else {
P
pana 已提交
122
    await next();
123 124 125
  }
}

P
pana 已提交
126
async function pi(ctx, next) {
Q
qingming 已提交
127
  if ('/pi' == ctx.path) {
P
pana 已提交
128
    ctx.body = String(Math.PI);
129
  } else {
P
pana 已提交
130
    await next();
131 132 133
  }
}

R
Robin Pokorný 已提交
134
const all = compose([random, backwards, pi]);
135 136

app.use(all);
137 138
```

139 140 141
### Response Middleware

  Middleware that decide to respond to a request and wish to bypass downstream middleware may
P
pana 已提交
142
  simply omit `next()`. Typically this will be in routing middleware, but this can be performed by
143 144 145 146
  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
R
Robin Pokorný 已提交
147
app.use(async function (ctx, next) {
148
  console.log('>> one');
P
pana 已提交
149
  await next();
R
Robin Pokorný 已提交
150
  console.log('<< one');
151 152
});

R
Robin Pokorný 已提交
153
app.use(async function (ctx, next) {
154
  console.log('>> two');
P
pana 已提交
155 156
  ctx.body = 'two';
  await next();
157 158 159
  console.log('<< two');
});

R
Robin Pokorný 已提交
160
app.use(async function (ctx, next) {
161
  console.log('>> three');
P
pana 已提交
162
  await next();
163 164 165 166
  console.log('<< three');
});
```

P
pana 已提交
167
  The following configuration omits `next()` in the second middleware, and will still respond
168 169 170
  with "two", however the third (and any other downstream middleware) will be ignored:

```js
R
Robin Pokorný 已提交
171
app.use(async function (ctx, next) {
172
  console.log('>> one');
P
pana 已提交
173
  await next();
R
Robin Pokorný 已提交
174
  console.log('<< one');
175 176
});

R
Robin Pokorný 已提交
177
app.use(async function (ctx, next) {
178
  console.log('>> two');
P
pana 已提交
179
  ctx.body = 'two';
180 181 182
  console.log('<< two');
});

R
Robin Pokorný 已提交
183
app.use(async function (ctx, next) {
184
  console.log('>> three');
P
pana 已提交
185
  await next();
186 187 188 189
  console.log('<< three');
});
```

P
pana 已提交
190
  When the furthest downstream middleware executes `next();`, it's really yielding to a noop
191 192
  function, allowing the middleware to compose correctly anywhere in the stack.

T
docs  
TJ Holowaychuk 已提交
193
## Async operations
T
TJ Holowaychuk 已提交
194

P
pana 已提交
195
  Async function and promise forms Koa's foundation, allowing
T
docs  
TJ Holowaychuk 已提交
196 197 198 199 200
  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
201
const fs = require('mz/fs');
T
docs  
TJ Holowaychuk 已提交
202

R
Robin Pokorný 已提交
203
app.use(async function (ctx, next) {
P
pana 已提交
204 205
  const paths = await fs.readdir('docs');
  const files = await Promise.all(paths.map(path => fs.readFile(`docs/${path}`, 'utf8')));
T
docs  
TJ Holowaychuk 已提交
206

P
pana 已提交
207 208
  ctx.type = 'markdown';
  ctx.body = files.join('');
T
docs  
TJ Holowaychuk 已提交
209 210
});
```
T
TJ Holowaychuk 已提交
211

T
TJ Holowaychuk 已提交
212 213 214 215 216
## Debugging Koa

  Koa along with many of the libraries it's built with support the __DEBUG__ environment variable from [debug](https://github.com/visionmedia/debug) which provides simple conditional logging.

  For example
217
  to see all Koa-specific debugging information just pass `DEBUG=koa*` and upon boot you'll see the list of middleware used, among other things.
T
TJ Holowaychuk 已提交
218 219 220 221 222 223 224 225 226 227 228

```
$ DEBUG=koa* node --harmony examples/simple
  koa:application use responseTime +0ms
  koa:application use logger +4ms
  koa:application use contentLength +0ms
  koa:application use notfound +0ms
  koa:application use response +0ms
  koa:application listen +0ms
```

J
Jonathan Ong 已提交
229 230
  Since JavaScript does not allow defining function names at
  runtime, you can also set a middleware's name as `._name`.
A
Anton Harniakou 已提交
231
  This is useful when you don't have control of a middleware's name.
J
Jonathan Ong 已提交
232 233 234
  For example:

```js
P
pana 已提交
235
const path = require('path');
R
Robin Pokorný 已提交
236
const serve = require('koa-static');
J
Jonathan Ong 已提交
237

R
Robin Pokorný 已提交
238
const publicFiles = serve(path.join(__dirname, 'public'));
J
Jonathan Ong 已提交
239 240 241 242 243
publicFiles._name = 'static /public';

app.use(publicFiles);
```

R
Robin Pokorný 已提交
244
  Now, instead of just seeing "serve" when debugging, you will see:
J
Jonathan Ong 已提交
245 246 247 248

```
  koa:application use static /public +0ms
```