Backend Development 14 min read

Designing a Microservice Node.js Framework Based on Koa

This article explores Koa’s core AOP‑based architecture, demonstrates minimal implementations using koa‑compose and Node.js http, and proposes a microservice‑oriented framework that extends Koa to support HTTP, Thrift, WebSocket and other services, complete with abstract server designs and example code.

TikTok Frontend Technology Team
TikTok Frontend Technology Team
TikTok Frontend Technology Team
Designing a Microservice Node.js Framework Based on Koa

Koa is a lightweight web framework created by the original Express team, emphasizing async/await, a minimal core, and an extensible middleware system. Its design follows the AOP (Aspect‑Oriented Programming) principle, allowing developers to add or remove functionality as independent "aspects" without affecting the core logic.

The framework can be expressed as Koa = Node.js native http server + koa‑compose middleware engine . By composing middleware with Promise and async/await , Koa implements an onion‑model where each middleware wraps the next, enabling features such as response‑time measurement, logging, and authentication.

Example Koa demo:

const Koa = require('koa');
const app = new Koa();

// x-response-time
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// logger
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

// response
app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

The core of Koa’s middleware composition is the koa‑compose function, which validates the middleware stack and returns a composed function that dispatches each middleware in sequence:

function compose(middleware) {
  if (!Array.isArray(middleware)) {
    throw new TypeError('Middleware stack must be an array!');
  }
  for (const fn of middleware) {
    if (typeof fn !== 'function') {
      throw new TypeError('Middleware must be composed of functions!');
    }
  }
  return function (context, next) {
    let index = -1;
    return dispatch(0);
    function dispatch(i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'));
      index = i;
      let fn = middleware[i];
      if (i === middleware.length) fn = next;
      if (!fn) return Promise.resolve();
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err);
      }
    }
  };
}

A simplified view of the composed execution shows a nested Promise chain that mirrors the onion model.

const [fn1, fn2, fn3] = stack;
const fnMiddleware = function (context) {
  return Promise.resolve(
    fn1(context, function next() {
      return Promise.resolve(
        fn2(context, function next() {
          return Promise.resolve(
            fn3(context, function next() {
              return Promise.resolve();
            })
          );
        })
      );
    })
  );
};

Building on these concepts, a minimal Koa‑like implementation can be created using Node.js http , events , and koa‑compose :

const http = require('http');
const EventEmitter = require('events');
const compose = require('koa-compose');

/** common context */
const context = {
  _body: null,
  get body() { return this._body; },
  set body(val) { this._body = val; this.res.end(this._body); }
};

class MiniKoa extends EventEmitter {
  constructor() {
    super();
    this.middleware = [];
    this.context = Object.create(context);
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  use(fn) { this.middleware.push(fn); }
  callback() {
    if (this.listeners('error').length === 0) this.on('error', this.onerror);
    const handleRequest = (req, res) => {
      let ctx = this.createContext(req, res);
      const { middleware } = this;
      compose(middleware)(ctx).catch(err => this.onerror(err));
    };
    return handleRequest;
  }
  onerror(err) { console.log(err); }
  createContext(req, res) {
    let ctx = Object.create(this.context);
    ctx.req = req; ctx.res = res;
    return ctx;
  }
}

const app = new MiniKoa();
const PORT = 3001;
app.use(async ctx => { ctx.body = 'hello'; });
app.listen(PORT, () => { console.log(`started at port ${PORT}`); });

To support services beyond plain HTTP (e.g., Thrift, WebSocket), the article proposes wrapping Koa’s middleware model in an abstract server layer. The abstract AbstractServer defines a generic lifecycle—middleware registration, context creation, request handling, and response finalisation—while concrete subclasses such as HttpServer implement protocol‑specific details.

import compose from 'koa-compose';
import http from 'http';

export abstract class AbstractServer extends EventEmitter {
  public middlewares: any[];
  public context;
  public request;
  public response;

  constructor(options) {
    super();
    this.middlewares = [];
    this.context = Object.create(options.context);
    this.request = Object.create(options.request);
    this.response = Object.create(options.response);
  }

  public listen(...args) {
    const server = this.createServer(this.callback());
    return server.listen(...args);
  }

  public use(fn): this {
    if (typeof fn !== 'function') {
      throw new Error('middleware must be a function!');
    }
    this.middlewares.push(fn);
    return this;
  }

  public callback() {
    const fn = compose(this.middlewares);
    return (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };
  }

  public handleRequest(ctx, fn): Promise
{
    return fn(ctx)
      .then(() => this.handleResponse(ctx))
      .catch(err => ctx.onerror(err));
  }

  public createContext(req, res) {
    const context = Object.create(this.context);
    const request = Object.create(this.request);
    const response = Object.create(this.response);
    context.app = this;
    context.request = request;
    context.response = response;
    context.req = req;
    context.res = res;
    context.state = {};
    request.app = this;
    request.ctx = context;
    request.req = req;
    request.res = res;
    request.response = response;
    response.app = this;
    response.ctx = context;
    response.req = req;
    response.res = res;
    response.request = request;
    return context;
  }

  public abstract onerror(err: Error): void;
  public abstract createServer(callback, options?): any;
  public abstract handleResponse(ctx): void;
}

The concrete HttpServer subclass implements the HTTP‑specific parts while reusing the generic middleware pipeline:

export class HttpServer extends AbstractServer {
  constructor(options) {
    const { context, request, response } = options;
    super({ context, request, response });
  }
  handleRequest(ctx, fn) {
    // additional HTTP‑specific logic can be added here
    return super.handleRequest(ctx, fn);
  }
  createContext(req, res) {
    const context = super.createContext(req, res);
    // HTTP‑specific context augmentation
    return context;
  }
  handleResponse(ctx) {
    let { body } = ctx;
    const { res } = ctx;
    const code = ctx.status;
    // additional response handling (e.g., headers, status)
    body = JSON.stringify(body);
    return res.end(body);
  }
  onerror(err) { super.onerror(err); }
  createServer(callback, options?) {
    // more detail…
    return http.createServer(callback) as any;
  }
}

Finally, the article summarises that Koa’s AOP‑driven onion model can be extended to a generic microservice framework, allowing developers to write middleware for various protocols while keeping the same compositional semantics.

Reference: Koa design patterns – https://chenshenhai.github.io/koajs-design-note/

backendmicroservicesAOPmiddlewareNode.jsKoa
TikTok Frontend Technology Team
Written by

TikTok Frontend Technology Team

We are the TikTok Frontend Technology Team, serving TikTok and multiple ByteDance product lines, focused on building frontend infrastructure and exploring community technologies.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.