Code Monkey home page Code Monkey logo

fastify-gateway's Introduction

k-fastify-gateway

A Node.js API gateway that just works!

Build Status NPM version Greenkeeper badge

Get started in two steps

Install required dependencies:

npm i fastify k-fastify-gateway

NOTE: From v2.x, fastify-reply-from is a direct dependency.

Launch your gateway πŸ”₯:

const fastify = require('fastify')({})

// required plugin for HTTP requests proxy
fastify.register(require('fastify-reply-from'))

// gateway plugin
fastify.register(require('k-fastify-gateway'), {

  middlewares: [
    require('cors')()
  ],

  routes: [{
    prefix: '/public',
    prefixRewrite: '',
    target: 'http://localhost:3000',
    middlewares: [],
    hooks: {
      // async onRequest (req, reply) {},
      // onResponse (req, reply, res) { reply.send(res) }
    }
  }, {
    prefix: '/admin',
    target: 'http://localhost:3001',
    middlewares: [
      require('basic-auth-connect')('admin', 's3cr3t-pass')
    ]
  }, {
    prefix: '/user',
    target: 'http://localhost:3001'
  }]
})

// start the gateway HTTP server
fastify.listen(8080).then((address) => {
  console.log(`API Gateway listening on ${address}`)
})

Introduction

Node.js API Gateway plugin for the fastify ecosystem, a low footprint implementation that uses the fastify-reply-from HTTP proxy library.

Yeap, this is a super fast gateway implementation!

Motivation

Creating fine grained REST microservices in Node.js is the easy part, difficult is to correctly integrate them as one single solution afterwards!

This gateway implementation is not only a classic HTTP proxy router, it is also a Node.js friendly cross-cutting concerns management solution. You don't have to:

  • repeat in/out middleware logic anymore (cors, authentication, authorization, caching, ...)
  • blame Node.js because the asynchronous post processing of proxied requests was hard to implement...
  • ...
  • or just learn Lua to extend nginx ;)

Configuration options explained

{
  // Optional global middlewares (https://www.fastify.io/docs/latest/Middlewares/). Default value: []
  middlewares: [],
  // Optional global value for routes "pathRegex". Default value: '/*'
  pathRegex: '/*',

  // HTTP proxy
  routes: [{
    // Optional path matching regex. Default value: '/*'
    // In order to disable the 'pathRegex' at all, you can use an empty string: ''
    pathRegex: '/*',
    // route prefix
    prefix: '/public',
    // Optional "prefix rewrite" before request is forwarded. Default value: ''
    prefixRewrite: '',
    // Optional body limit setting for fastify JSON body parser. Default value: 1048576 (1 MiB)
    bodyLimit: 1048576,
    // remote HTTP server URL to forward the request
    target: 'http://localhost:3000',
    // optional HTTP methods to limit the requests proxy to certain verbs only
    methods: ['GET', 'POST', ...], // any of supported HTTP methods: https://github.com/fastify/fastify/blob/master/docs/Routes.md#full-declaration
    // Optional route level middlewares. Default value: []
    middlewares: [],
    // Optional proxy lifecycle hooks. Default value: {}
    hooks: {
      async onRequest (req, reply) {
      //   // we can optionally reply from here if required
      //   reply.send('Hello World!')
      //
      //   return true // truthy value returned will abort the request forwarding
      },
      onResponse (req, reply, res) {  
        // do some post-processing here
        // ...
        // forward response to origin client once finished
        reply.send(res)
      }

      // other options allowed https://github.com/fastify/fastify-reply-from#replyfromsource-opts
    }
  }]
}

Gateway level caching

Why?

Because caching is the last mile for low latency distributed systems!

Enabling proper caching strategies at gateway level will drastically reduce the latency of your system, as it reduces network round-trips and remote services processing.
We are talking here about improvements in response times from X ms to ~2ms, as an example.

Setting up gateway cache

Single node cache (memory):

// cache plugin setup
const gateway = require('fastify')({})
gateway.register(require('k-fastify-gateway/src/plugins/cache'), {})

Recommended if there is only one gateway instance

Multi nodes cache (redis):

// redis setup
const CacheManager = require('cache-manager')
const redisStore = require('cache-manager-ioredis')
const redisCache = CacheManager.caching({
  store: redisStore,
  db: 0,
  host: 'localhost',
  port: 6379,
  ttl: 30
})

// cache plugin setup
const gateway = require('fastify')({})
gateway.register(require('k-fastify-gateway/src/plugins/cache'), {
  stores: [redisCache]
})

Required if there are more than one gateway instances

Enabling cache for service endpoints

Although API Gateway level cache aims as a centralized cache for all services behind the wall, are the services the ones who indicate the responses to be cached and for how long.

Cache entries will be created for all remote responses coming with the x-cache-timeout header:

res.setHeader('x-cache-timeout', '1 hour')

Here we use the ms package to convert timeout to seconds. Please note that millisecond unit is not supported!

Example on remote service using restana:

service.get('/numbers', (req, res) => {
  res.setHeader('x-cache-timeout', '1 hour')

  res.send([
    1, 2, 3
  ])
})

Invalidating cache

Let's face it, gateway level cache invalidation was complex..., until now!

Remote services can also expire cache entries on demand, i.e: when the data state changes. Here we use the x-cache-expire header to indicate the gateway cache entries to expire using a matching pattern:

res.setHeader('x-cache-expire', '*/numbers')

Here we use the matcher package for matching patterns evaluation.

Example on remote service using restana:

service.patch('/numbers', (req, res) => {
  res.setHeader('x-cache-expire', '*/numbers')

  // ...
  res.send(200)
})

Custom cache keys

Cache keys are generated using: req.method + req.url, however, for indexing/segmenting requirements it makes sense to allow cache keys extensions.
Unfortunately, this feature can't be implemented at remote service level, because the gateway needs to know the entire lookup key when a request reaches the gateway.

For doing this, we simply recommend using middlewares on the service configuration:

routes: [{
  prefix: '/users',
  target: 'http://localhost:3000',
  middlewares: [(req, res, next) => {
    req.cacheAppendKey = (req) => req.user.id // here cache key will be: req.method + req.url + req.user.id
    return next()
  }]
}]

In this example we also distinguish cache entries by user.id, very common case!

Disable cache for custom endpoints

You can also disable cache checks for certain requests programmatically:

routes: [{
  prefix: '/users',
  target: 'http://localhost:3000',
  middlewares: [(req, res, next) => {
    req.cacheDisabled = true
    return next()
  }]
}]

Breaking changes

In v2.x the hooks.onResponse signature has changed from:

onResponse (res, reply)

to:

onResponse (req, reply, res)

More details: fastify/fastify-reply-from#43

Benchmarks

Version: 2.0.1
Node: 10.15.3
Machine: MacBook Pro 2016, 2,7 GHz Intel Core i7, 16 GB 2133 MHz LPDDR3
Gateway processes: 1
Service processes: 1

Running 30s test @ http://127.0.0.1:8080/service/hi
  8 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   841.58us  662.17us  35.22ms   98.66%
    Req/Sec     1.23k   130.62     1.29k    95.02%
  293897 requests in 30.10s, 42.60MB read
Requests/sec:   9763.61
Transfer/sec:      1.42MB

Want to contribute?

This is your repo ;)

Note: We aim to be 100% code coverage, please consider it on your pull requests.

Related projects

fastify-gateway's People

Contributors

dependabot[bot] avatar greenkeeper[bot] avatar i7326 avatar jkyberneees avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastify-gateway's Issues

Path parameters?

Thank you for this amazing library. I have two concerns. First is how do I map a path of my gateway to a totally different prefix in my service. Second, how do I specify path parameters?

Not Found

Hi,

I dont know if I am doing it right, but when I try to access any route, the gateway always returns 404 (not found). My code is this:

const gateway = require('fastify')({})
gateway.register(require('fastify-reply-from'))

gateway.register(require('k-fastify-gateway'), {

middlewares: [
require('cors')(), // https://www.npmjs.com/package/cors
require('helmet')() // https://helmetjs.github.io/
],

routes: [{
prefix: '/gmail',
prefixRewrite: '',
target: 'https://www.google.com',
middlewares: [],
hooks: {
// onResponse (res, reply) { reply.send(res) }
// async onRequest (req, reply) {},
onResponse (res, reply) { reply.send(res) }
}
},
{
prefix: '/admin',
prefixRewrite: '',
target: 'https://www.google.com',
middlewares: [],
hooks: {
onResponse (res, reply) { reply.send(res) }
}
}]
})

// start the gateway HTTP server
gateway.listen(8080).then((address) => {
console.log(API Gateway listening on ${address})
})

FST_ERR_CTP_INVALID_MEDIA_TYPE: Unsupported Media Type: multipart/form-data; boundary=----WebKitFormBoundaryDtgoTu7ABM7JcOnz

While accessing http://127.0.0.1:8080/{abc} POST route with request.content-type = 'multipart/form-data'

Port Exposed: 8080
POST /api/-------------route--------------- HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 412
Accept: application/json, text/plain, /
enctype: multipart/form-data
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAjpQdkhEmREOaAiB
Origin: http://127.0.0.1:8080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Referer: http://127.0.0.1:8080/-route-
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: ------------cookie----------

Cause error with status code: 415 Unsupported Media Type

FST_ERR_CTP_INVALID_MEDIA_TYPE: Unsupported Media Type: multipart/form-data; boundary=----WebKitFormBoundaryDtgoTu7ABM7JcOnz

Service groupings ?

I think it will be better from an architectural standpoint to give services endpoints with each service having its own configuration . Then instead of assigning endpoints in each route we can assign service name as key . Also it'd be great if we can have the provision for flexible service endpoints which will get rid of the 1:1 mapping and make the gateway more robust . Any thoughts ? :)

An in-range update of supertest is breaking the build 🚨

The devDependency supertest was updated from 3.3.0 to 3.4.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

supertest is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).

Release Notes for v3.4.0
  • PR-532 - Packages updated, pipeline more explicit, documentation updated (thanks @rimiti)
  • PR-517 - Documentation updated (thanks @oprogramador)
  • PR-513 - Use more robust Array instance check (thanks @rubendg)
Commits

The new version differs by 14 commits.

  • 5640ac9 3.4.0
  • 60f8a9e Merge pull request #532 from visionmedia/v3.4.0
  • 43bfae1 doc(History.md) changelog updated
  • fc1568d doc(README.md) cookie example added
  • 3192d96 chore(package-lock.json) file updated
  • b3d271f chore(package.json) blocks reorganized, nock removed
  • aeae0f3 chore(.travis.yml) pipeline more explicit
  • 53feddc chore(test/supertest.js) obscure test removed
  • 50c59d6 Merge pull request #519 from oprogramador/patch-3
  • 6ca3897 Merge pull request #517 from oprogramador/patch-2
  • 0146d81 auth info in README
  • d853b37 fix typo in README
  • 8158979 Merge pull request #513 from rubendg/use-isarray
  • 550613b Use more robust Array instance check

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

5+ seconds response on some status codes < 200ms on others

When I proxy a request and the target immediately returns a 200 or a 404 (no work) the request doesn't take much longer than without the proxy.

When I proxy a request where the target returns a 204 (with no work, immediately reply, and some other status codes) it always takes at least 5+ seconds.

When I don't proxy a request where the target returns a 204 (no work, immediate reply) it takes a few milliseconds. This is the same for all other status codes with no proxy and immediate reply (no work).

Not sure this is a gateway issue but all projects I'm proxing against are using fastify, the test project I tested against is literally a single file with dumb routes/handlers which immediately return a code and response.

Any help would be great! Want to return an appropriate status code.

Error on fastify 4.13.0

log error

{"level":30,"time":1685823837047,"pid":41165,"hostname":"Andis-MacBook-Pro.local","reqId":"req-1","req":{"method":"GET","url":"/health/time","hostname":"localhost:3001","remoteAddress":"::1","remotePort":53825},"msg":"incoming request"}
{"level":50,"time":1685823837048,"pid":41165,"hostname":"Andis-MacBook-Pro.local","reqId":"req-1","req":{"method":"GET","url":"/health/time","hostname":"localhost:3001","remoteAddress":"::1","remotePort":53825},"res":{"statusCode":500},"err":{"type":"TypeError","message":"Cannot read properties of undefined (reading 'url')","stack":"TypeError: Cannot read properties of undefined (reading 'url')\n at Object. (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/k-fastify-gateway/index.js:6:35)\n at preHandlerCallback (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/fastify/lib/handleRequest.js:128:37)\n at preValidationCallback (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/fastify/lib/handleRequest.js:112:5)\n at handler (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/fastify/lib/handleRequest.js:76:7)\n at handleRequest (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/fastify/lib/handleRequest.js:24:5)\n at runPreParsing (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/fastify/lib/route.js:537:5)\n at Object.routeHandler [as handler] (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/fastify/lib/route.js:484:7)\n at Router.callHandler (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/find-my-way/index.js:398:14)\n at Router.lookup (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/find-my-way/index.js:376:17)\n at Server.preRouting (/Users/andi-dans/Dans/Labs/nx-fastify-microservice-base/node_modules/fastify/fastify.js:792:14)"},"msg":"Cannot read properties of undefined (reading 'url')"}
{"level":30,"time":1685823837056,"pid":41165,"hostname":"Andis-MacBook-Pro.local","reqId":"req-1","res":{"statusCode":500},"responseTime":8.463874999433756,"msg":"request completed"}

Cant send post body

I am trying to send a post request by it returns an error saying "Unsupported Media Type" . What could I be doing wrong ?

feature :

Hello,

it could be good if we could declare the "rate-limit" directly from the route declaration.

it could a optional parameter set to false by default if not use.

Thanks

is it stable enough to be used in production?

I'm looking for a gateway for nodejs / typescript that allows me handle vaeious concerns not directly related to microservices for instance authentication, authorization etc.

i see that it's been a long time there has been no activity happening. i want to know if it is stable enough to be used in production or was it just a gateway experiment.

there is express-gateway also but it has also been abondoned a long time ago

Trailing slash problem

When the trailing slash is present in my url , it works fine . The moment its gone ,404 . Possible bug ?

http methods

It would be nice to also have the possibility to define http methods for routes, like

{
    prefix: '/public',
    prefixRewrite: '',
    target: 'http://localhost:3000',
    methods: ['GET']
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.