Debugging an error when using koa-router module that multiple routes were invoked on a single HTTP request:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var app = require('koa')();
var router = require('koa-router')();
router.use(function *(next){
this.body = {};
yield next;
});
router.get('/users', function *(next){
this.body['/users'] = true;
yield next;
});
router.get('/users/:id?', function *(next){
this.body['/users/:id?'] = true;
yield next;
});
app.use(router.routes());
app.listen(3000);
When making a request to get a list of users, it will match both routes:
1
2
3
4
5
6
7
8
9
10
$ http :3000/users
HTTP/1.1200 OK
Connection: keep-alive
Content-Length: 34
Content-Type: application/json; charset=utf-8
{
"/users": true,
"/users/:id?": true
}
Because in the second route :id is optional, and since there is yield next statement in the first route, the second route will be executed subsequently.
The cause of the bug is the design of the routing GET /users/:id?. The correct routes are:
GET /users: Retrieve a list (array) of users.
GET /users/:id: Retrieve a single user object.
The question mark should not be appended to the end of id parameter, which effectively marking the route to perform both duties. It’s better to keep them simple and keep them separated.
Koa has a context object created during every request/response cycle. The context is referenced via this in the middleware. Much information are captured in the context, such as req and res. In order to avoid polluting the context and colliding with other middleware, the information from your application should be encapsulated into this.state property, a namespace for passing information through middleware during request/response cycle:
It is simple to know the request URL path, it’s one (this.url in Koa) used by routing, but if you are looking to get the complete URL, it’s a little bit complicated. Luckily, there is a quick way to re-assemble the requested URL in Koa, if proxy is not a concern:
1
var url = this.request.protocol + '://' + this.request.host + this.request.originalUrl;
Or a shorter version:
1
var url = this.protocol + '://' + this.host + this.originalUrl;
The port number is included in this.host, for example: localhost:3000.
Assume that you have a server running on the host www.example.com with SSL enabled. If you make a request to the server with the URL: https://www.example.com/path, this URL will be echoed back in the response by the above script.
In the example section of SuperTest (v0.15.0), it mentions something about ephemeral port:
You may pass an http.Server, or a Function to request() - if the server is not already listening for connections then it is bound to an ephemeral port for you so there is no need to keep track of ports. - https://github.com/visionmedia/supertest#example
Let’s dissect this sentence. There are a few key concepts to grasp:
The request function will accept either a function or an http.Server object.
When does the server not listening for connections?
What is an ephemeral port?
Taking a peek at the index.js file in SuperTest source code, it is easy to see that it accepts both function and http.Server object, but prefer latter:
1
2
3
4
5
6
7
8
9
10
11
// See `index.js` in [SuperTest] source code.
if ('function' == typeof app) app = http.createServer(app);
Only when initiating an Express app, it returns a function, not an object. And follows up on lib/test.js, when SuperTest detects the created server is yet to bind to any port number, it will invoke app.listen(0), so called ephemeral port. In fact, it is just a random port.
When something is ephemeral, it last for a very short time. When allowing a server to accept connections, we usually do is setting the server to listen on a specific port:
1
app.listen(3001);
What if setting this to 0 like above or omit this port number?
If the port number is evaluated to be falsy, such as 0, null, undefined or an empty string, a random port will be assigned.
Begin accepting connections on the specified port and hostname. If the hostname is omitted, the server will accept connections on any IPv6 address (::) when IPv6 is available, or any IPv4 address (0.0.0.0) otherwise. A port value of zero will assign a random port. - https://iojs.org/api/http.html#http_server_listen_port_hostname_backlog_callback
We can use command line tool such as netstat to find out the port number:
1
2
$ netstat -ltpn | grep node
tcp6 00 :::44055 :::* LISTEN 5624/node
However, it will be very convenient without relying on external tools.
Luckily, we can find out the port number by using the address method:
1
require('http').createServer().listen().address()
The result will be similar to:
1
{ address: '::', family: 'IPv6', port: 44055 }
Furthermore, it works with all popular libraries that create an http.Server instance:
Taking the first spin on Koa, a new web framework designed by the team behind Express.
Requirements
Node v0.11.9 or higher is required for generator support.
--harmony flag must be used when running Node.
Without the --harmony flag, genertor function cannot be defined:
function *fn() {
^
SyntaxError: Unexpected token *
at exports.runInThisContext (vm.js:69:16)
at Module._compile (module.js:432:25)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:349:32)
at Function.Module._load (module.js:305:12)
at Function.Module.runMain (module.js:490:10)
at startup (node.js:123:16)
at node.js:1031:3
So, set the --harmony flag in shell alias:
$ alias node='node --harmony'
To know more:
$ man node
--harmony (enable all harmony features (except typeof))
type: booldefault: false
Harmony is the codename of ECMAScript 6, the next version of the standard.
Install
$ npm install koa
The footprint of the framework is small. Therefore, there is no executable binary like Express, and we do not need to install it globally.
Koa provides a traceback via generator, each unit is capable of working on the request twice instead of once as previous two designs. It is stack alike, where the first generator will also be the last one prior to responding to request:
app.use requires a generator function or function *(). Koa middleware uses this (context) instead of req and res in Express. It also encapsulates Node’s request and response objects. For example, this or context has the following properties:
When the body is populated, status is automatically updated, probably via the nextTick method. So, we can use it as the flag to design a response such as a custom not found response.
Not Found
Create a custom Not Found in JSON content type:
app.use(function *(next){
yield next; // The operation is yet to start.if (this.status) { return; }
this.status = 404;
this.body = { message: 'Cannot find what you are looking for :(' };
});
app.use(function *(){}); // Do nothing to simulate not found.
where ctx is a context instance. But you cannot modify the response body. This is more for server side logging and debugging purpose. The 500 Internal Server Error will be returned to the client.
We can use try..catch to define a custom error handling middleware:
Make sure to emit the error event and let the server log the error.
All non-operational middleware (e.g., not found or error) should be defined first, so it can be traced back as the last guards before sending response back.
Routing
Out of the box, Koa does not support routing, but it can be achieved via routing middleware, see examples.
Examples
Define an app with only one middleware generator function:
1
2
3
4
5
function *foo(next){
console.log('foo');
this.status = 204;
}
require('koa')().use(foo).listen(3000);
Instead of using status code 200, we use 204 here, because the response contains no body. If using 200, the server does not know the total content length, will result in Transfer-Encoding: chunked header instead of Content-Length. See chunked transfer encoding.
This app simply prints out foo on the server side, and respond to a client with status code 204 and an empty body. We can even omit the next parameter, since we are not using it.
Now let’s use two middleware generator functions:
1
2
3
4
5
6
7
8
// Response: foo
function *foo(next){
this.body = 'foo';
}
function *bar(next){
this.body = 'bar';
}
require('koa')().use(foo).use(bar).listen(3000);
Since generator foo sets the body, generator bar will never be reached. We can change that by adding the yield:
1
2
3
4
5
6
7
8
9
10
// Response: bar
function *foo(next){
this.body = 'foo';
yield next;
}
function *bar(next){
this.body = 'bar';
yield next;
}
require('koa')().use(foo).use(bar).listen(3000);
This looks alot like Express, yeild next is similar to next(). Request travels through the middleware system first via foo then bar. But actually by using generator, it also returns back to foo as foo->bar->foo:
1
2
3
4
5
6
7
8
9
10
11
// Response: foo again
function *foo(next){
this.body = 'foo';
yield next;
this.body = 'foo again';
}
function *bar(next){
this.body = 'bar';
yield next;
}
require('koa')().use(foo).use(bar).listen(3000);
This is similar to a stack alike request model, where the first middleware should also be the last middleware to touch the request. There is a no-op middleware in the end to catch the last yield next produced by bar middleware generator and traces back the stack.
Debugging
Set environment variable DEBUG=koa* to enable Koa specific debugging information in development environment:
$ DEBUG=koa* node app.js
You will see a list of middleware. If a middleware is an anonymous function, we can set ._name to define a custom name: