Take a quick look at the implementation of koa

Simple usage of koa

https://koajs.com/https://koajs.com/

// For a clear example, you can refer to this.

Create a Koa instance with new Koa()

const app = (module.exports = new Koa());

Defining middleware with app.use()

Example of middleware definition

const logger = require('koa-logger');
const Koa = require('koa');
const app = (module.exports = new Koa());
app.use(logger());

Example of middleware implementation (koa-lowercase as an example)

https://github.com/zacanger/koa-lowercase/blob/master/koa-lowercase.jshttps://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/

koa-lowercase.js
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();
};

Implementation (middleware registration)

lib/application.js
   * 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
  }

Implementation (middleware application)

The following code is where middleware is actually applied. The dependency is as follows:

  • callback
    • handleRequest

callback() is passed to node:http's createServer() as follows:

lib/application.js
  listen (...args) {
    debug('listen')
    const server = http.createServer(this.callback())
    return server.listen(...args)
  }
About `http.createServer()`

https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistenerhttps://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener

@types/node/http.d.ts
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.

@types/node/http.d.ts
    type RequestListener<
        Request extends typeof IncomingMessage = typeof IncomingMessage,
        Response extends typeof ServerResponse = typeof ServerResponse,
    > = (req: InstanceType<Request>, res: InstanceType<Response> & { req: InstanceType<Request> }) => void;
lib/application.js
  /**
   * 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
  }
lib/application.js
  /**
   * 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.

    • Use koa-compose to combine multiple middleware into one
    • Return handleRequest (= RequestListener)
      • Create context with createContext
      • Execute this.handleRequest(ctx, fn)
        • Execute middleware with fnMiddleware(ctx).then(handleResponse).catch(onerror) and define success and failure processing

Define routes with router.method

Example of route definition
https://github.com/koajs/examples/blob/3967136b133c9e7157cc030759b2266e005e15f1/blog/app.js#L22https://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#L274https://github.com/koajs/router/blob/638aa5356e0fd38af4de6532a03c18dc90813723/lib/router.js#L274

 routes() {
    return this.middleware();
  }

https://github.com/koajs/router/blob/638aa5356e0fd38af4de6532a03c18dc90813723/lib/router.js#L209https://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;
  }

About koa's request and response

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.

lib/application.js
  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
  }

Start the server with app.listen()

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);