Skip to content
 
 

Latest commit

 

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HearthJS

HearthJS is made to build NodeJS server faster. It gives many tools to increase your productivity and let you focus only on your real functionnality.

Here is a list of tools that HearthJS manage

  • Translations
  • Logger
  • Migrations
  • API declaration
  • Cron
  • ...

You can find the complete documentation here: www.hearthjs.io

Rate limiting

Off by default. In-memory token bucket, per process, no external store: max is the allowed burst, refilled over window seconds (sustained rate = max/window per second). A rejected request gets 429, Retry-After and Cache-Control: no-store headers and the standard { success: false, data: {}, message } body — before cookie parsing, body parsing, addons and middleware ever run.

There are two independent switches — the global per-IP net, and the per-route limits (the rateLimit schema keys). Set these in the environment or the project config file:

key default meaning
APP_RATE_LIMIT_GLOBAL false turn on the global per-IP net
APP_RATE_LIMIT_GLOBAL_MAX 100 global net: burst per key
APP_RATE_LIMIT_GLOBAL_WINDOW 60 global net: seconds to refill MAX tokens
APP_RATE_LIMIT_GLOBAL_SKIP global net: comma-separated path prefixes never limited, e.g. /health,/api/webhooks — matched on the decoded path, on segment boundaries (/api/webhooks skips /api/webhooks/stripe, not /api/webhooksX)
APP_RATE_LIMIT_GLOBAL_MAX_KEYS 100000 global net: cap on tracked keys (~125 B each); above it, new keys are limited collectively through 256 shared buckets
APP_RATE_LIMIT_GLOBAL_HEADERS false global net: emit RateLimit-* headers on every response
APP_RATE_LIMIT_ROUTE true are the per-route limits active? Set false (e.g. in the test config) to turn every rateLimit: schema limit off. Warns at startup

The two are independent: the per-route limits (below) run whether or not the global net is on, and APP_RATE_LIMIT_ROUTE=false turns them off without touching the global net. Only true turns a switch on: any other value (1, yes, on) warns and stays off. An invalid number warns and uses the default — never a truncated parse.

The key is req.ip: the socket address, or the rightmost X-Forwarded-For hop (the one your proxy appended) when APP_TRUST_PROXY is true. Change it, or skip requests, with hearthjs.rateLimit.configure({ key, skip, onLimit }) in beforeInit. Key functions are synchronous and return a string: one that returns nothing (an async function's Promise included) falls back to the client address and logs a warning.

One route — the rateLimit schema key:

schemas: {
  login: {
    rateLimit: { max: 10, window: 300 },   // 10 burst, then 2/min per IP
    function: loginHandler
  }
}

Several routes sharing one budget — declare a profile in beforeInit, reference it by name:

hearthjs.rateLimit.define('email-send', {
  max: 10,
  window: 1800,
  key: (req) => req.token ? 'a' + req.token.idAccount : req.ip
})
schemas: {
  sendInvitation: { rateLimit: 'email-send', ... },
  resetPassword:  { rateLimit: 'email-send', ... }   // same bucket per key
}

scope: 'route' gives each route its own bucket instead. An unknown profile name is reported at startup and the route is not served.

Options: max, window, key, scope, dryRun, onLimit, message, maxKeys, headers. Roll out safely with dryRun: true: would-be rejections are logged (warn, aggregated), nothing is blocked. Set headers: true to emit the RateLimit-* headers on that route (off by default, like the global net's APP_RATE_LIMIT_GLOBAL_HEADERS). Limits are per process: with N instances behind a proxy, size them for ~N× the intended rate.

Testing. Define your limits normally — no dryRun seam in production code — and set APP_RATE_LIMIT_ROUTE=false in the test config so per-route limits never interfere with the rest of the suite. One dedicated test flips them on for a real endpoint and asserts the 429:

// test config: "APP_RATE_LIMIT_ROUTE": false
describe('rate limit (login)', () => {
  before(() => hearthjs.rateLimit.enable())     // per-route limits on
  afterEach(() => hearthjs.rateLimit.reset())   // clear buckets between cases
  after(() => hearthjs.rateLimit.disable())     // back off for other tests

  it('returns 429 after N attempts from one IP', /* hit /api/login N+1 times */)
})

enable() / disable() flip the APP_RATE_LIMIT_ROUTE switch at runtime; reset() clears every limiter's bucket state (keeping the profiles) so one test's requests never spill into the next. Vary the key between cases by sending a different X-Forwarded-For (with APP_TRUST_PROXY=true) when you need distinct buckets.

Where the logs go

APP_LOG_OUTPUT, in the environment or in the project config file, decides the destinations. It takes one of four values:

value logs go to
file (default) the daily file in server/logs, nothing on stdout
stdout stdout only, and no logs directory is ever created
both the daily file and stdout
none nowhere; a warning says so once at startup

stdout is the one to use in a container: writing log files inside an image is an anti pattern, and the filesystem may well be read only. both is the one for a systemd service, so journalctl shows the application logs.

The request log line prints whole query strings by default. Set APP_LOG_REDACT_QUERY=true to redact the value of sensitive query parameters (token, code, api_key, state, …) so a secret passed in a URL never reaches the log in the clear — recommended in production.

APP_LOG_STDOUT is deprecated. When APP_LOG_OUTPUT is not set, true still means both and anything else means file, so existing deployments keep the output they have. When both are set, APP_LOG_OUTPUT wins and the startup warns that APP_LOG_STDOUT is ignored.

About

A simple server framework to reate API rest

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages