// For a clear example, you can refer to this.
const app = (module.exports = new Koa());
const logger = require('koa-logger');
const Koa = require('koa');
const app = (module.exports = new Koa());
app.use(logger());
https://github.com/zacanger/koa-lowercase/blob/master/koa-lowercase.js
ctx
and next
are received, and then next()
is called.
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.
https://koajs.com/
const lowercase = async (ctx, next) => {
const { origin, path, querystring } = ctx.request;
const op = `${origin}${path}`;
if (
/[A-Z]/.test(op) &&
!['POST', 'HEAD', 'PUT', 'DELETE'].includes(ctx.method)
) {
const ld = `${op.toLowerCase()}${querystring ? '?' + querystring : ''}`;
ctx.status = 301;
ctx.redirect(ld);
return;
}
return next();
};
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {(context: Context) => Promise<any | void>} fn
* @return {Application} self
* @api public
*/
use (fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
this.middleware.push(fn)
return this
}
The following code is where middleware is actually applied. The dependency is as follows:
callback()
is passed to node:http
's createServer()
as follows:
listen (...args) {
debug('listen')
const server = http.createServer(this.callback())
return server.listen(...args)
}
https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener
function createServer<
Request extends typeof IncomingMessage = typeof IncomingMessage,
Response extends typeof ServerResponse = typeof ServerResponse,
>(
requestListener?: RequestListener<Request, Response>
): Server<Request, Response>;
function createServer<
Request extends typeof IncomingMessage = typeof IncomingMessage,
Response extends typeof ServerResponse = typeof ServerResponse,
>(
options: ServerOptions<Request, Response>,
requestListener?: RequestListener<Request, Response>
): Server<Request, Response>;
In this case, it receives RequestListener
and returns Server
.
RequestListener
is a function that is automatically added to the 'request' event.
The requestListener is a function which is automatically added to the 'request' event.
type RequestListener<
Request extends typeof IncomingMessage = typeof IncomingMessage,
Response extends typeof ServerResponse = typeof ServerResponse,
> = (req: InstanceType<Request>, res: InstanceType<Response> & { req: InstanceType<Request> }) => void;
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback () {
const fn = this.compose(this.middleware)
if (!this.listenerCount('error')) this.on('error', this.onerror)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
if (!this.ctxStorage) {
return this.handleRequest(ctx, fn)
}
return this.ctxStorage.run(ctx, async () => {
return await this.handleRequest(ctx, fn)
})
}
return handleRequest
}
/**
* Handle request in callback.
*
* @api private
*/
handleRequest (ctx, fnMiddleware) {
const res = ctx.res
res.statusCode = 404
const onerror = err => ctx.onerror(err)
const handleResponse = () => respond(ctx)
onFinished(res, onerror)
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
the process is as follows:
Execute middleware
Define success and failure processing
callback()
returns a RequestListener
function to node:http.createServer
.
handleRequest
(= RequestListener
)
createContext
this.handleRequest(ctx, fn)
fnMiddleware(ctx).then(handleResponse).catch(onerror)
and define success and failure processingExample of route definitionhttps://github.com/koajs/examples/blob/3967136b133c9e7157cc030759b2266e005e15f1/blog/app.js#L22
router
uses @koa/router.
/**
* Post listing.
*/
async function list(ctx) {
await ctx.render('list', { posts: posts });
}
/**
* Show creation form.
*/
async function add(ctx) {
await ctx.render('new');
}
/**
* Show post :id.
*/
async function show(ctx) {
const id = ctx.params.id;
const post = posts[id];
if (!post) ctx.throw(404, 'invalid post id');
await ctx.render('show', { post: post });
}
/**
* Create a post.
*/
async function create(ctx) {
const post = ctx.request.body;
const id = posts.push(post) - 1;
post.created_at = new Date();
post.id = id;
ctx.redirect('/');
}
router
.get('/', list)
.get('/post/new', add)
.get('/post/:id', show)
.post('/post', create);
app.use(router.routes());
Finally, app.use(router.routes())
is called, passing router.routes()
as an argument to app.use
.
https://github.com/koajs/router/blob/638aa5356e0fd38af4de6532a03c18dc90813723/lib/router.js#L274
routes() {
return this.middleware();
}
https://github.com/koajs/router/blob/638aa5356e0fd38af4de6532a03c18dc90813723/lib/router.js#L209
/**
* Returns router middleware which dispatches a route matching the request.
*
* @returns {Function}
*/
middleware() {
const router = this;
const dispatch = (ctx, next) => {
debug('%s %s', ctx.method, ctx.path);
const hostMatched = router.matchHost(ctx.host);
if (!hostMatched) {
return next();
}
const path =
router.opts.routerPath ||
ctx.newRouterPath ||
ctx.path ||
ctx.routerPath;
const matched = router.match(path, ctx.method);
if (ctx.matched) {
ctx.matched.push.apply(ctx.matched, matched.path);
} else {
ctx.matched = matched.path;
}
ctx.router = router;
if (!matched.route) return next();
const matchedLayers = matched.pathAndMethod;
const mostSpecificLayer = matchedLayers[matchedLayers.length - 1];
ctx._matchedRoute = mostSpecificLayer.path;
if (mostSpecificLayer.name) {
ctx._matchedRouteName = mostSpecificLayer.name;
}
const layerChain = (
router.exclusive ? [mostSpecificLayer] : matchedLayers
).reduce((memo, layer) => {
memo.push((ctx, next) => {
ctx.captures = layer.captures(path, ctx.captures);
ctx.request.params = layer.params(path, ctx.captures, ctx.params);
ctx.params = ctx.request.params;
ctx.routerPath = layer.path;
ctx.routerName = layer.name;
ctx._matchedRoute = layer.path;
if (layer.name) {
ctx._matchedRouteName = layer.name;
}
return next();
});
return [...memo, ...layer.stack];
}, []);
return compose(layerChain)(ctx, next);
};
dispatch.router = this;
return dispatch;
}
/**
* Create and register a route.
*
* @param {String} path Path string.
* @param {Array.<String>} methods Array of HTTP verbs.
* @param {Function} middleware Multiple middleware also accepted.
* @returns {Layer}
* @private
*/
register(path, methods, middleware, opts = {}) {
const router = this;
const { stack } = this;
// support array of paths
if (Array.isArray(path)) {
for (const curPath of path) {
router.register.call(router, curPath, methods, middleware, opts);
}
return this;
}
// create route
const route = new Layer(path, methods, middleware, {
end: opts.end === false ? opts.end : true,
name: opts.name,
sensitive: opts.sensitive || this.opts.sensitive || false,
strict: opts.strict || this.opts.strict || false,
prefix: opts.prefix || this.opts.prefix || '',
ignoreCaptures: opts.ignoreCaptures
});
if (this.opts.prefix) {
route.setPrefix(this.opts.prefix);
}
// add parameter middleware
for (let i = 0; i < Object.keys(this.params).length; i++) {
const param = Object.keys(this.params)[i];
route.param(param, this.params[param]);
}
stack.push(route);
return route;
}
ctx
includes request
and response
objects.
A Koa Context encapsulates node's request and response objects into a single object which provides many helpful methods for writing web applications and APIs.
createContext (req, res) {
/** @type {Context} */
const context = Object.create(this.context)
/** @type {KoaRequest} */
const request = context.request = Object.create(this.request)
/** @type {KoaResponse} */
const response = context.response = Object.create(this.response)
context.app = request.app = response.app = this
context.req = request.req = response.req = req
context.res = request.res = response.res = res
request.ctx = response.ctx = context
request.response = response
response.request = request
context.originalUrl = request.originalUrl = req.url
context.state = {}
return context
}
The app.listen(...) method is simply sugar for the following:
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);