Backend Development 10 min read

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

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

The

target

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

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

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.

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

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

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

Reflect

ES6 introduces the

Reflect

API for object manipulation. To store routing metadata we use the

reflect-metadata

library, which provides:

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

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.

<code>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());
</code>

Controller

Define a

Controller

decorator to store a common base URL for a class.

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

Base HTTP Methods

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

<code>/**
 * 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);
    };
  };
}
</code>

Generate Router File

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

<code>/**
 * 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);
      }
    }
  }
}
</code>

Load All Controllers

Automatically require every controller file using

require-context

(or

require.context

in Webpack).

<code>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'));
</code>

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

node.jsRouterKoadecoratorreflect-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

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.