UNPKG

7.65 kBMarkdownView Raw
1
2# Guide
3
4 This guide covers Baiji topics are not directly API related, such as best practices for writing middleware,
5 application structure suggestions.
6
7## Writing Middleware
8
9 Baiji middleware are simple functions which return a `GeneratorFunction`, and accept another. When
10 the middleware is run by an "upstream" middleware, it must manually `yield` to the "downstream" middleware.
11
12 For example if you wanted to track how long it takes for a request to propagate through Koa by adding an
13 `X-Response-Time` header field the middleware would look like the following:
14
15```js
16function *responseTime(next) {
17 var start = new Date;
18 yield next;
19 var ms = new Date - start;
20 this.set('X-Response-Time', ms + 'ms');
21}
22
23app.use(responseTime);
24```
25
26 Here's another way to write the same thing, inline:
27
28```js
29app.use(function *(next){
30 var start = new Date;
31 yield next;
32 var ms = new Date - start;
33 this.set('X-Response-Time', ms + 'ms');
34});
35```
36
37 If you're a front-end developer you can think any code before `yield next;` as the "capture" phase,
38 while any code after is the "bubble" phase. This crude gif illustrates how ES6 generators allow us
39 to properly utilize stack flow to implement request and response flows:
40
41![koa middleware](/docs/middleware.gif)
42
43 1. Create a date to track duration
44 2. Yield control to the next middleware
45 3. Create another date to track response time
46 4. Yield control to the next middleware
47 5. Yield immediately since `contentLength` only works with responses
48 6. Yield upstream to Koa's noop middleware
49 7. Ignore setting the body unless the path is "/"
50 8. Set the response to "Hello World"
51 9. Ignore setting `Content-Length` when no body is present
52 10. Set the field
53 11. Output log line
54 12. Set `X-Response-Time` header field before response
55 13. Hand off to Koa to handle the response
56
57
58Note that the final middleware (step __6__) yields to what looks to be nothing - it's actually
59yielding to a no-op generator within Koa. This is so that every middleware can conform with the
60same API, and may be placed before or after others. If you removed `yield next;` from the furthest
61"downstream" middleware everything would function appropriately, however it would no longer conform
62to this behaviour.
63
64 For example this would be fine:
65
66```js
67app.use(function *response(){
68 if ('/' != this.url) return;
69 this.body = 'Hello World';
70});
71```
72
73 Next we'll look at the best practices for creating Koa middleware.
74
75## Middleware Best Practices
76
77 This section covers middleware authoring best practices, such as middleware
78 accepting options, named middleware for debugging, among others.
79
80### Middleware options
81
82 When creating public middleware it's useful to conform to the convention of
83 wrapping the middleware in a function that accepts options, allowing users to
84 extend functionality. Even if your middleware accepts _no_ options, this is still
85 a good idea to keep things uniform.
86
87 Here our contrived `logger` middleware accepts a `format` string for customization,
88 and returns the middleware itself:
89
90```js
91function logger(format) {
92 format = format || ':method ":url"';
93
94 return function *(next){
95 var str = format
96 .replace(':method', this.method)
97 .replace(':url', this.url);
98
99 console.log(str);
100
101 yield next;
102 }
103}
104
105app.use(logger());
106app.use(logger(':method :url'));
107```
108
109### Named middleware
110
111 Naming middleware is optional, however it's useful for debugging purposes to assign a name.
112
113```js
114function logger(format) {
115 return function *logger(next){
116
117 }
118}
119```
120
121### Combining multiple middleware
122
123 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.
124
125```js
126function *random(next) {
127 if ('/random' == this.path) {
128 this.body = Math.floor(Math.random()*10);
129 } else {
130 yield next;
131 }
132};
133
134function *backwards(next) {
135 if ('/backwards' == this.path) {
136 this.body = 'sdrawkcab';
137 } else {
138 yield next;
139 }
140}
141
142function *pi(next) {
143 if ('/pi' == this.path) {
144 this.body = String(Math.PI);
145 } else {
146 yield next;
147 }
148}
149
150function *all(next) {
151 yield random.call(this, backwards.call(this, pi.call(this, next)));
152}
153
154app.use(all);
155```
156
157 This is exactly what [koa-compose](https://github.com/koajs/compose) does, which Koa internally uses to create and dispatch the middleware stack.
158
159### Response Middleware
160
161 Middleware that decide to respond to a request and wish to bypass downstream middleware may
162 simply omit `yield next`. Typically this will be in routing middleware, but this can be performed by
163 any. For example the following will respond with "two", however all three are executed, giving the
164 downstream "three" middleware a chance to manipulate the response.
165
166```js
167app.use(function *(next){
168 console.log('>> one');
169 yield next;
170 console.log('<< one');
171});
172
173app.use(function *(next){
174 console.log('>> two');
175 this.body = 'two';
176 yield next;
177 console.log('<< two');
178});
179
180app.use(function *(next){
181 console.log('>> three');
182 yield next;
183 console.log('<< three');
184});
185```
186
187 The following configuration omits `yield next` in the second middleware, and will still respond
188 with "two", however the third (and any other downstream middleware) will be ignored:
189
190```js
191app.use(function *(next){
192 console.log('>> one');
193 yield next;
194 console.log('<< one');
195});
196
197app.use(function *(next){
198 console.log('>> two');
199 this.body = 'two';
200 console.log('<< two');
201});
202
203app.use(function *(next){
204 console.log('>> three');
205 yield next;
206 console.log('<< three');
207});
208```
209
210 When the furthest downstream middleware executes `yield next;` it's really yielding to a noop
211 function, allowing the middleware to compose correctly anywhere in the stack.
212
213## Async operations
214
215 The [Co](https://github.com/visionmedia/co) forms Koa's foundation for generator delegation, allowing
216 you to write non-blocking sequential code. For example this middleware reads the filenames from `./docs`,
217 and then reads the contents of each markdown file in parallel before assigning the body to the joint result.
218
219
220```js
221var fs = require('co-fs');
222
223app.use(function *(){
224 var paths = yield fs.readdir('docs');
225
226 var files = yield paths.map(function(path){
227 return fs.readFile('docs/' + path, 'utf8');
228 });
229
230 this.type = 'markdown';
231 this.body = files.join('');
232});
233```
234
235## Debugging Koa
236
237 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.
238
239 For example
240 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.
241
242```
243$ DEBUG=koa* node --harmony examples/simple
244 koa:application use responseTime +0ms
245 koa:application use logger +4ms
246 koa:application use contentLength +0ms
247 koa:application use notfound +0ms
248 koa:application use response +0ms
249 koa:application listen +0ms
250```
251
252 Since JavaScript does not allow defining function names at
253 runtime, you can also set a middleware's name as `._name`.
254 This useful when you don't have control of a middleware's name.
255 For example:
256
257```js
258var path = require('path');
259var serve = require('koa-static');
260
261var publicFiles = serve(path.join(__dirname, 'public'));
262publicFiles._name = 'serve /public';
263
264app.use(publicFiles);
265```
266
267 Now, instead of just seeing "serve" when debugging, you will see:
268
269```
270 koa:application use serve /public +0ms
271```