> A light Nodejs middle layer framework based on Koa2, written in typescript.


License
MIT
Install
npm install fary@1.4.1

Documentation

fary

A light Nodejs middle layer framework based on Koa2, written in typescript.

NPM version build status

Note: this framework only works directly in Node 7.6+(async/await), or you can use babel to transform the source code.

Nodejs middle layer

Recent years, there is a kind of application consist with a Nodejs server(can handle the static files, especially for SPA) for the frontend and a data(maybe developed by Java, PHP, Python and so on) server for the backend. The application data is fetched from client(browser or mobile app) to Nodejs, then Nodejs make one or multi data request(s) to the data server, Nodejs can restructure these data results, make a new and best result the client needed. The role of Nodejs server above is called middle layer, which is between the client and data server.

Install

npm i fary

Usage

const createApp = require('fary')

createApp({
  middlewares: [
    async (ctx, next) => {
      ctx.body = 'Hello fary!'
    },
  ]
}, () => {
  // fary server has been started!
})

Put a fary.config.js in your root of project, the content like:

module.exports = {
  port: 9008,
  proxy: {
    '/api': 'http://192.168.100.1:8001',
  },
}

Note: with this simple example, no request will be proxied, because all requests will be responded Hello fary!

Features

  1. request/response log with koa-logger
  2. compress with koa-compress
  3. you can pass your middlewares to do whatever you want
  4. http proxy with koa-better-http-proxy
  5. hot loaded proxy config in development
  6. dynamic proxy config interception for proxy target debugging
  7. proxy load balance and health check

createApp(options)

options:

  • midlewares?: Array<Koa.middleware>: default []; the array of koa middleware
  • port?: number: default 9001; the listen port of koa, if not set, port in the fary.config.js will be used
  • skipListen?: boolean: default false; whether auto start; if true, you should invoke app.listen(port) yourself
  • preProxy? (proxyReqOpts: koaHttpProxy.IRequestOption, ctx: Koa.Context) => (koaHttpProxy.IRequestOption | Promise<koaHttpProxy.IRequestOption>): default (proxyRes, d) => d; before the proxy request sending, you can do something
  • postProxy?: (proxyRes: http.IncomingMessage, proxyResData: Buffer, ctx: Koa.Context) => (Buffer | Promise<Buffer>): default ``; after the proxy request responsed, you can do something

Proxy

Support multiple proxy targets. Assume the fary.config.js is:

module.exports = {
  port: 9008,
  proxy: {
    '/api/backend1': 'http://192.168.100.1:8001',
    '/api/backend2': 'http://192.168.100.2:8001',
  },
}

The entry:

require('fary')({
  middlewares: [
    // Some middlewares(for example: routes), but do not match `/api/backend1` or `/api/backend2` requests!
    // Because the built-in proxy middleware is the last middleware.
  ]
})

Then /api/backend1/explore will be proxied to http://192.168.100.1:8001/explore, /api/backend2/run will be proxied to http://192.168.100.1:8001/run. That is: remove the first matched proxy key(here is /api/backend1 or /api/backend2) with which the current request url starts, then append the remaining to the target.

Note:

  • Make sure the request can reach downstreamly the proxy middleware(the last one).
  • For duplicated proxy reason, if the proxy target is domain form, the removal will not happen. For example, if the fary.config.js is like:
module.exports = {
  port: 9008,
  proxy: {
    '/api': 'https://some.domain.com',
  },
}

Then a request like /api/test will be proxied to https://some.domain.com/api/test

Hot loaded proxy config in development

There may be multiple proxy target for the same proxy key prefix when development. For example, there is a fary.config.js:

module.exports = {
  port: 9008,
  proxy: {
    '/api/users': 'http://192.168.100.2:8001', // it is backend developer's local host
    //'/api/users': 'http://192.168.100.100:8001', // it is the test integration environment host
  },
}

You may want to switch proxy target for the /api/users from one to the other one when the server is running. By default, Nodejs will cache every module once Nodejs has required it, which means if you want to switch proxy target, you must restart the server. Even worse, if the restarting spend long time, it is terrible!

With this feature, you can modify the fary.config.js whenever without restarting the server!

But it works only for proxy config.

Dynamic proxy config interception for proxy target debugging.

Imagine you have a test integration environment of your application.One day your test team find out a tricky bug which is hard to reproduce in local environment. An unhandy way is that, the related Java programmer add some logging code to the Java project, then commit, redeploy the Java server of the test integration environment. If lucky, the programmer can figure it out the reason for the first time. But if not too lucky, you will repeat add more logging code => redeploy => can not found the reason many times, which is so annoying!

Another way is to do a breakpoint debugging on the test server, but this will block other's request.

However, by this Dynamic proxy feature, you can turn the request to any proxy target you want without changing any code! How to do this? Assume the request url of the tricky bug is /api/java/a/tricky/bug, and the proxy config is:

module.exports = {
  port: 9008,
  proxy: {
    '/api/java': 'http://192.168.100.100:8080', // 192.168.100.100 is a host in test integration environment
  },
}

The only thing you should do is that: Replay the request and add a http head like:

// 192.168.30.30 is the Java programmer's computer ip
proxyconfig: {"/api/java": "http://192.168.30.30:8080"}

You can do this by some client proxy tools like Charles with the request breakpoint function. Or you can do some detection code in the client, for example in the browser, you can detect the localstorage, if there is a value by key proxyconfig(any you like), then write the header to the request.

After this, the fary proxy middleware will detect the header proxyconfig, then merge the dynamic proxy config to the server proxy config(actually by Object.assign). The request /api/java/a/tricky/bug will be proxied to the Java programmer's local server, which means he/she can do a exciting breakpoint debugging locally without blocking others' access!

Proxy load balance

You can set a proxy mapping value with an array, fary will randomly pick a proxy target for every proxy request. For example fary.config.js:

module.exports = {
  port: 9008,
  proxy: {
    '/api/java': [
      'http://192.168.100.100:8080',
      'http://192.168.100.101:8090',
    ]
  },
}

Proxy target server health check

It is default disabled. To enable it, and some default options in fary.config.js:

module.exports = {
  port: 9008,
  proxy: {
    '/api/java': [
      'http://192.168.100.100:8080',
      'http://192.168.100.101:8090',
    ]
  },
  proxyLoadBalanceOptions: {
    // enable health check or not
    enableHealthCheck: true,
    healthCheckPath: '/admin/health',
    healthCheckTimeout: 1000,
    healthCheckInterval: 1000 * 60,
  },
}

Fary will make a quest to every target by GET ${proxyTarget}/${healthCheckPath}. Targets which responses non 200 or no response will be removed from the healthy targets list, and will not match next target picking until the targets recover and added back to the healthy target list.