Simplify Koa and Egg Routing with Decorators: A Step-by-Step Guide

This article explains how to use JavaScript decorators to define routes for Koa and Egg frameworks, covering class and method decorators, Reflect metadata, automatic router generation, controller loading, and practical code examples for streamlined backend development.

WeDoctor Frontend Technology
WeDoctor Frontend Technology
WeDoctor Frontend Technology
Simplify Koa and Egg Routing with Decorators: A Step-by-Step Guide

Preface

Many developers using Koa or Egg encounter the tedious pattern of defining a route in a controller and then repeating the same definition in a separate router file. This article shows how to avoid that duplication by using decorators.

The same approach works for both Koa and Egg, allowing you to keep the original style while progressively enhancing it with decorator‑based routing.

Decorator Usage

Decorators are syntactic sugar: a regular function invoked with @functionName placed before a class or method definition.

Class Decorator

@testable
class MyTestableClass {
  // ...
}
function testable(target) {
  target.isTestable = true;
}
MyTestableClass.isTestable // true
// Code adapted from Ruan Yifeng’s ES6 tutorial

The target argument receives the class itself; if the decorator returns a value, it replaces the class.

@decorator
class A {}
// equivalent to
class A {}
A = decorator(A) || A;

Method Decorator

Method decorators resemble Object.defineProperty and receive three arguments: target: the prototype of the class, whose constructor points to the class. name: the name of the decorated property. descriptor: the property descriptor, identical to the one used by Object.defineProperty.

Descriptor details can be found in the MDN documentation.

function foo(target, name, descriptor) {
  // custom logic
}

When a decorator needs parameters, create a higher‑order function that returns the actual decorator.

function foo(url) {
  return function (target, name, descriptor) {
    console.log(url);
  };
}
class Bar {
  @foo('/test')
  baz() {}
}

Reflect

ES6 introduces the Reflect API for object manipulation. To store routing metadata we use the reflect-metadata library, which provides:

// set metadata
Reflect.defineMetadata(metadataKey, metadataValue, target);
// get metadata
let result = Reflect.getMetadata(metadataKey, target);

Implement Decorator Routing

Implementation Idea

The goal is to generate a router configuration for Koa (or Egg) automatically. By attaching metadata (path, HTTP method) to each method via decorators, we can later register all routes in a single step.

const app = new Koa();
const router = new Router();
router.get('/user/info', UserInfoController);
router.get('/user/list', UserListController);
router.post('/user/create', UserCreateController);
app.use(router.routes());

Controller

Define a Controller decorator to store a common base URL for a class.

/**
 * Controller decorator
 * @param {string} [baseUrl=''] Class base path
 */
Controller(baseUrl = '') {
  return (target) => {
    Reflect.defineMetadata(BASE_URL, baseUrl, target);
  };
}

Base HTTP Methods

Because Koa‑router’s methods share the same signature, we create a generic factory that produces method‑specific decorators.

/**
 * Utility to generate method decorators
 */
createMethodDecorator(method) {
  return (url) => {
    return (target, name, decorator) => {
      // store controller for later processing
      this.controllerList.add(target);
      Reflect.defineMetadata(METHOD, method, decorator.value);
      Reflect.defineMetadata(METHOD_URL, url || name, decorator.value);
    };
  };
}

Generate Router File

Iterate over stored controllers, read their metadata, and register each route on the router.

/**
 * Register routes on a Koa router
 */
registerRouter(router) {
  for (const controller of this.controllerList) {
    const controllerCtor = controller.constructor;
    const baseUrl = Reflect.getMetadata(BASE_URL, controllerCtor) || '';
    const allProps = Object.getOwnPropertyNames(controller);
    for (const prop of allProps) {
      const handle = controller[prop];
      if (typeof handle !== 'function') continue;
      const method = Reflect.getMetadata(METHOD, handle);
      const url = Reflect.getMetadata(METHOD_URL, handle);
      if (method && url && router[method]) {
        const completeUrl = this.prefix + baseUrl + url;
        router[method](completeUrl, handle);
      }
    }
  }
}

Load All Controllers

Automatically require every controller file using require-context (or require.context in Webpack).

import requireContext from 'require-context';
export const load = function (path) {
  const ctx = requireContext(path, true, /\.js$/);
  ctx.keys().forEach(key => ctx(key));
};
// usage: load(path.resolve(__dirname, './controller'));

Extension

Beyond automatic route registration, decorators can handle authentication, middleware, dependency injection, validation, logging, and more.

Conclusion

We have built a decorator‑based routing system for Koa (and similarly for Egg or Express). By adapting the registerRouter function, existing projects can gradually adopt decorator syntax without rewriting all routes.

The demo code is available at Koa-Decorator-Demo .

References

descriptor: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#%E6%8F%8F%E8%BF%B0

reflect-metadata: https://rbuckton.github.io/reflect-metadata/

Koa-Decorator-Demo: https://github.com/xluos/Koa-Decorator-Demo

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

KoaDecoratorreflect-metadataEgg
WeDoctor Frontend Technology
Written by

WeDoctor Frontend Technology

Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.

0 followers
Reader feedback

How this landed with the community

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.