Benifits of Middleware

Flexible use of middleware mechanism can effectively extend the functions of the architecture. The main functions of middleware are interception, reorganization, injection

  1. interception:For example, judging user privileges through middleware and suspending subsequent execution if no privileges exist
  2. reorganization:For example, the data sent from the frontend is validated by middleware and transformed into the desired type
  3. injection:For example, injecting objects into ctx through middleware to provide the necessary basic functionality for subsequent code

Interception

Before executing the API Route, judge the request parameters. If they do not meet the expectations, reject this request

For example, middleware test determines the current running environment. If it is not a test environment, an exception will be thrown:

egg-born-backend/app/middleware/test.js

module.exports = () => {
  return async function test(ctx, next) {
    if (!ctx.app.meta.isTest) ctx.throw(403);
    // next
    await next();
  };
};

Reorganization

Before executing the API Route, adjust the request parameters to make them conform to the expected data type

For example, middleware validate verifies the form data sent by fronend and converts it to the expected data type

a-validation/backend/src/config/middleware/validate.js

module.exports = (options2, app) => {
  const moduleInfo = app.meta.mockUtil.parseInfoFromPackage(__dirname);
  return async function validate(ctx, next, options) {
    // must exists
    const validator = options.validator;
    if (!validator) ctx.throw.module(moduleInfo.relativeName, 1001);
    // params
    const module = options.module || ctx.module.info.relativeName;
    const schema = options.schema || (ctx.meta._validator && ctx.meta._validator.schema);
    const data = ctx.request.body[options.data || 'data'];
    // if error throw 422
    await ctx.meta.validation.validate({
      module,
      validator,
      schema,
      data,
    });
    // next
    await next();
  };
};

Injection

Before executing API Route, inject some object instances into the object of ctx

For example, middleware cachemem injects memory based cache object into ctx:

a-cache/backend/src/config/middleware/cachemem.js

const memFn = require('./adapter/mem.js');
const CACHE = Symbol('CTX#__CACHE');

module.exports = () => {
  return async function cachemem(ctx, next) {
    ctx.cache = ctx.cache || {};
    Object.defineProperty(ctx.cache, 'mem', {
      get() {
        if (ctx.cache[CACHE] === undefined) {
          ctx.cache[CACHE] = new (memFn(ctx))();
        }
        return ctx.cache[CACHE];
      },
    });

    // next
    await next();
  };
};

Types of Middleware

EggBornJS has three main sources of middleware:

  1. EggJS Middleware
  2. EggBornJS System Middleware
  3. EggBornJS Module Middleware

EggJS Middleware

The middlewares of EggJS ecosystem can be used directly in EggBornJS

See also: EggJS Middleware

EggBornJS System Middleware

To meet the application requirements of common scenarios, EggBornJS has three built-in system middleware: innertesttransaction

1. inner

The API Route can perform other API Route through ctx.performAction, which is called inner access

Specify the middleware inner in the API Route to restrict the route only to be inner access

{ method: 'post', path: 'version/update', controller: version, middlewares: 'inner' }

Middleware inner can also be regarded as one of the API authorization policies

2. test

Specify the middleware test in the API Route to restrict the route only to be performed in the test environment

{ method: 'post', path: 'version/test', controller: version, middlewares: 'test' }

Middleware test can also be regarded as one of the API authorization policies

3. transaction

Specify the middleware transaction in the API Route to enable database transaction for this route

{ method: 'post', path: 'test/echo', controller: test, middlewares: 'transaction'}

EggBornJS Module Middleware

EggBornJS allows middleware to be developed in modules, which can be used by the same module or the other modules

Development of Module Middleware

Here, through a virtual requirement, we use middleware to achieve the three functions: interception',reorganization’ and `injection’:

  1. Following the previous code, the frontend sends two parameters to the backend: message, markCount
  2. interception:The middleware determines whether message is empty, and if it is empty, terminates subsequent execution
  3. reorganization:Mandatory conversion of markCount to Integer type
  4. injection:Inject a method to be called in subsequent code

Declaration of Middleware

src/module/test-party/backend/src/config/config.js

// middlewares
config.middlewares = {
  testInterception: {
    global: false,
    dependencies: 'instance',
  },
  testRestructuring: {
    global: false,
    dependencies: 'instance',
  },
  testInjection: {
    global: false,
    dependencies: 'instance',
  },
};
Name Description
global Whether global middleware or not, the global middleware will be loaded automatically, and the local middleware needs to be specified on the API route manually
dependencies Indicate which middlewares this middleware relies on to load after those middlewares. Generally, it depends on middleware instance, because middleware instance provides the basic logic of multi-instance

Definition of Middleware

src/module/test-party/backend/src/config/middleware/interception.js

module.exports = () => {
  return async function interception(ctx, next) {
    const { a, b } = ctx.request.body;
    if (a === undefined || b === undefined) return ctx.throw(1002); // 1002: 'Incomplete Parameters'
    // next
    await next();
  };
};

src/module/test-party/backend/src/config/middleware/restructuring.js

module.exports = () => {
  return async function restructuring(ctx, next) {
    const { a, b } = ctx.request.body;
    ctx.request.body.a = parseInt(a);
    ctx.request.body.b = parseInt(b);
    // next
    await next();
  };
};

src/module/test-party/backend/src/config/middleware/injection.js

module.exports = () => {
  return async function injection(ctx, next) {
    ctx.meta.__plus = (a, b) => {
      return a + b;
    };
    // next
    await next();
  };
};

Reference Middleware

src/module/test-party/backend/src/config/middlewares.js

const interception = require('./middleware/interception.js');
const restructuring = require('./middleware/restructuring.js');
const injection = require('./middleware/injection.js');

module.exports = {
  testInterception: interception,
  testRestructuring: restructuring,
  testInjection: injection,
};

Usage of Middleware

Because testInterceptiontestRestructuring and testInjection are all local middlewares, you need to specify it manually on the API route

src/module/test-party/backend/src/routes.js

// test/feat/middleware
{ method: 'post', path: 'test/feat/middleware/interception', controller: testFeatMiddleware, middlewares: 'test,testInterception' },
{ method: 'post', path: 'test/feat/middleware/restructuring', controller: testFeatMiddleware, middlewares: 'test,testInterception,testRestructuring' },
{ method: 'post', path: 'test/feat/middleware/injection', controller: testFeatMiddleware, middlewares: 'test,testInterception,testRestructuring,testInjection' },

Backend Logic

src/module/test-party/backend/src/controller/test/feat/middleware.js

module.exports = app => {

  class TestController extends app.Controller {

    async interception() {
      const { a, b } = this.ctx.request.body;
      const c = parseInt(a) + parseInt(b);
      this.ctx.success(c);
    }

    async restructuring() {
      const { a, b } = this.ctx.request.body;
      const c = a + b;
      this.ctx.success(c);
    }

    async injection() {
      const { a, b } = this.ctx.request.body;
      const c = this.ctx.meta.__plus(a, b);
      this.ctx.success(c);
    }

  }

  return TestController;
};

Usage of Middleware

Global Middleware

The global middleware is loaded automatically and does not need to be specified in the API route

Local Middleware

The local middleware need to be explicitly specified in the API route

{ method: 'post', path: 'version/update', controller: version, middlewares: 'inner' }

Middleware Parameters

You can specify the middleware parameters through the meta of the API route

src/module/test-party/backend/src/routes.js

const testKitchensinkGuide = require('./controller/kitchen-sink/guide.js');

module.exports = app => {
  const routes = [
    { 
      method: 'post', 
      path: 'kitchen-sink/guide/echo9', 
      controller: testKitchensinkGuide,
      action: 'echo9',
      middlewares: 'test,transaction'
      meta: {
        right: {
          type: 'function',
          name: 'kitchenSink',
        },
      },
    },
  ];
  return routes;
};
Name Description
meta.right parameters of the global middleware right, which be used to verify whether the current user has access to the API route by the specified type and name

Disable Middleware

Middleware can be disabled in two ways:

1. config

Disable middleware for specified API Routes by the attribute ignore of config

a-instance/backend/src/config/config.js

// middlewares
config.middlewares = {
  instance: {
    global: true,
    dependencies: 'cachemem',
    ignore: /(\/version\/(update))/,
  },
};

2. API Route

Disable middleware directly in the API route through the attribute enable

a-base/backend/src/routes.js

{ method: 'post', path: 'auth/echo', controller: auth,
  meta: {
    auth: { 
      enable: false
    }
  } 
}
Name Description
auth.enable Disable the global middleware auth

When a middleware is disabled, other middlewares dependent on the middleware are also automatically disabled