Since Koa is a bare bones web framework, we installed koa-static and koa-router along with koa and use them in our index.js file.
To understand how Koa works at a basic level, we need a fundamental understanding of JavaScript promises (in particular ES 2015 promises) and of async/await syntax. See https://medium.com/@bluepnume/eb148164a9c8 for an excellent article on how these abstractions interact.
The koa-static module returns a Koa middleware function, which is passed into app.use. Koa middleware accepts a context object (often called ctx) and a next function. The next function always returns a promise.
The koa-static middleware attempts to locate files as per our defined path (the public folder), then creates a write stream from a file (if found) to the ctx.request object, which is Node's core http request object (an instance of http.IncomingMessage often called req). If the file isn't found it passes on control to the next piece of middleware.
The koa-router middleware is superficially similar to the Router utility in Express. However, we register other router instances with our main router instance (our router object in index.js) by calling router.use. This allows us to set mount points for a particular set of routes. Then we pass the main router instance into app.use.
In routes/index.js we load koa-router and call it as a function (the same as we do in index.js) to create another router instance. We can call methods on the router object that correspond to HTTP verbs (such as get, post, delete, put, and so on).
We register the / route with router.get, meaning we've set up a GET route that will respond to requests to the / path.
We've taken a contrived approach to handling this route, in order to demonstrate how control flow works in Koa.
We supply two functions to the router.get call, both of them prefixed with the async keyword. An async function always returns a promise, which Koa is expecting.
Our first function immediately calls the next function with the await keyword (await next()). This means the execution of the function pauses until the promise returned from next is resolved.
The next function will (indirectly) call whichever piece of middleware is next in the stack. In our case, it's the route-specific middleware, that is, the third argument passed to router.get, which is also an async function.
This second function simply sets ctx.state to an object containing a title property. Since it's an async function it returns a promise, which our first function waits to be resolved because await next() in this case relates to the resolution of the next route middleware (the third function supplied to router.get).
The line following await next() then assigns the title constant based on the contents of ctx.state. In this case title will now equal 'Koa'. Then we set ctx.body to our HTML content with the title constant interpolated.
This asynchronous dance is completely unnecessary, we could have just set the title constant directly in the first function and not bothered with the second function. However, the point of supplying this example in this recipe was to showcase Koa's control flow behavior in action.
This declarative fine grained control over where a function should defer to subsequent middleware before the function continues to execute is what makes Koa special in comparison to Express and Hapi.
This book has taken a light touch approach to promises, since their usage on the server side is a matter of some contention. While the concept of promises is excellent, their specification in the language have unfortunate drawbacks the detailing of which is far too in depth and out of scope but primarily concerns traceability and analysis that has an effect on certain production diagnostics, such as post mortems. However, Koa is a futuristic framework (which is on the brink of being modern), and promise adoption continues to increase, so these issues will hopefully be resolved (or at least alternative approaches will arise) in the future.