@lopatnov/express-reverse-proxy 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -0
- package/package.json +9 -3
- package/server.js +29 -0
package/README.md
CHANGED
|
@@ -20,11 +20,19 @@
|
|
|
20
20
|
- [CLI Options](#cli-options)
|
|
21
21
|
- [Configuration](#configuration)
|
|
22
22
|
- [port](#port)
|
|
23
|
+
- [logging](#logging)
|
|
24
|
+
- [hotReload](#hotreload)
|
|
25
|
+
- [compression](#compression)
|
|
26
|
+
- [helmet](#helmet)
|
|
27
|
+
- [cors](#cors)
|
|
28
|
+
- [favicon](#favicon)
|
|
29
|
+
- [responseTime](#responsetime)
|
|
23
30
|
- [headers](#headers)
|
|
24
31
|
- [folders](#folders)
|
|
25
32
|
- [proxy](#proxy)
|
|
26
33
|
- [unhandled](#unhandled)
|
|
27
34
|
- [host](#host)
|
|
35
|
+
- [ssl](#ssl)
|
|
28
36
|
- [Configuration Recipes](#configuration-recipes)
|
|
29
37
|
- [Docker & PM2](#docker--pm2)
|
|
30
38
|
- [Testing](#testing)
|
|
@@ -154,6 +162,15 @@ Static files always take priority over proxy rules. Proxies are checked only whe
|
|
|
154
162
|
|
|
155
163
|
## CLI Options
|
|
156
164
|
|
|
165
|
+
The package installs two equivalent commands — use whichever you prefer:
|
|
166
|
+
|
|
167
|
+
```shell
|
|
168
|
+
express-reverse-proxy [options]
|
|
169
|
+
lerp [options]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`lerp` is a short alias for **L**opatnov **E**xpress **R**everse **P**roxy.
|
|
173
|
+
|
|
157
174
|
| Option | Description |
|
|
158
175
|
| ------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
159
176
|
| `--help` | Print help and exit |
|
|
@@ -462,6 +479,108 @@ Paths are resolved **relative to the config file**, not the current working dire
|
|
|
462
479
|
|
|
463
480
|
> All site configs on the same port must either all have `ssl` or none — mixing is a startup error.
|
|
464
481
|
|
|
482
|
+
### compression
|
|
483
|
+
|
|
484
|
+
Enable gzip/deflate response compression. Reduces the size of HTML, CSS, JS, and JSON responses sent to the browser. Set to `true` for defaults, or pass an options object.
|
|
485
|
+
|
|
486
|
+
```json
|
|
487
|
+
{
|
|
488
|
+
"port": 8080,
|
|
489
|
+
"compression": true,
|
|
490
|
+
"folders": "www"
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
With custom options (see [compression docs](https://github.com/expressjs/compression#options)):
|
|
495
|
+
|
|
496
|
+
```json
|
|
497
|
+
{
|
|
498
|
+
"compression": { "level": 6, "threshold": 1024 }
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
> Compression is applied per-site. Assets that are already compressed (images, fonts, video) are not affected — the browser signals it accepts compressed responses via the `Accept-Encoding` header.
|
|
503
|
+
|
|
504
|
+
### helmet
|
|
505
|
+
|
|
506
|
+
Set security-related HTTP response headers. Protects against common web vulnerabilities by configuring headers such as `Content-Security-Policy`, `X-Frame-Options`, `Strict-Transport-Security`, and others.
|
|
507
|
+
|
|
508
|
+
```json
|
|
509
|
+
{
|
|
510
|
+
"port": 8080,
|
|
511
|
+
"helmet": true,
|
|
512
|
+
"folders": "www"
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Disable a specific header (see [helmet docs](https://helmetjs.github.io/) for all options):
|
|
517
|
+
|
|
518
|
+
```json
|
|
519
|
+
{
|
|
520
|
+
"helmet": { "contentSecurityPolicy": false }
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
> When `helmet: true` is set, the default helmet configuration is applied. This may block inline scripts and cross-origin resources. Adjust `contentSecurityPolicy` or other options as needed for your project.
|
|
525
|
+
|
|
526
|
+
### cors
|
|
527
|
+
|
|
528
|
+
Enable CORS (Cross-Origin Resource Sharing) headers and handle preflight `OPTIONS` requests automatically. Useful when your front-end on one origin calls an API on a different origin.
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
{
|
|
532
|
+
"port": 8080,
|
|
533
|
+
"cors": true,
|
|
534
|
+
"proxy": { "/api": "http://localhost:4000" }
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
Restrict to a specific origin (see [cors docs](https://github.com/expressjs/cors#configuration-options)):
|
|
539
|
+
|
|
540
|
+
```json
|
|
541
|
+
{
|
|
542
|
+
"cors": { "origin": "https://app.example.com" }
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
> The `cors` middleware handles `OPTIONS` preflight requests that the `headers` option cannot respond to. Use `cors` when you need to allow requests from JavaScript on a different domain — for example a React app calling this proxy's API routes.
|
|
547
|
+
|
|
548
|
+
### favicon
|
|
549
|
+
|
|
550
|
+
Serve a favicon file efficiently. The file is read into memory at startup and served from there on every `/favicon.ico` request — before static folder scanning or proxy rules run.
|
|
551
|
+
|
|
552
|
+
```json
|
|
553
|
+
{
|
|
554
|
+
"port": 8080,
|
|
555
|
+
"favicon": "./public/favicon.ico",
|
|
556
|
+
"folders": "www"
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
The path is resolved **relative to the config file**, consistent with the `ssl` option. Absolute paths are also accepted.
|
|
561
|
+
|
|
562
|
+
> If your favicon already lives inside a directory listed in `folders`, this option is not needed — `express.static` will serve it automatically.
|
|
563
|
+
|
|
564
|
+
### responseTime
|
|
565
|
+
|
|
566
|
+
Add an `X-Response-Time` header to every response, recording how long the server took to handle the request. Useful for performance monitoring and debugging.
|
|
567
|
+
|
|
568
|
+
```json
|
|
569
|
+
{
|
|
570
|
+
"port": 8080,
|
|
571
|
+
"responseTime": true,
|
|
572
|
+
"folders": "www"
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
With custom precision (see [response-time docs](https://github.com/expressjs/response-time#options)):
|
|
577
|
+
|
|
578
|
+
```json
|
|
579
|
+
{
|
|
580
|
+
"responseTime": { "digits": 0, "suffix": false }
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
465
584
|
---
|
|
466
585
|
|
|
467
586
|
## Configuration Recipes
|
|
@@ -530,6 +649,26 @@ node server.js --config server-config.json
|
|
|
530
649
|
|
|
531
650
|
---
|
|
532
651
|
|
|
652
|
+
### Production hardening (helmet + cors + compression)
|
|
653
|
+
|
|
654
|
+
Enable security headers, CORS, and response compression in one config:
|
|
655
|
+
|
|
656
|
+
```json
|
|
657
|
+
{
|
|
658
|
+
"port": 8080,
|
|
659
|
+
"compression": true,
|
|
660
|
+
"helmet": true,
|
|
661
|
+
"cors": { "origin": "https://app.example.com" },
|
|
662
|
+
"responseTime": true,
|
|
663
|
+
"folders": "www",
|
|
664
|
+
"proxy": {
|
|
665
|
+
"/api": "http://localhost:4000"
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
533
672
|
### CORS headers + rich error responses
|
|
534
673
|
|
|
535
674
|
```json
|
|
@@ -807,6 +946,11 @@ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before
|
|
|
807
946
|
- [Express](https://expressjs.com/) — HTTP server framework
|
|
808
947
|
- [express-http-proxy](https://github.com/villadora/express-http-proxy) — reverse proxy middleware
|
|
809
948
|
- [Morgan](https://github.com/expressjs/morgan) — HTTP request logger
|
|
949
|
+
- [compression](https://github.com/expressjs/compression) — gzip/deflate response compression
|
|
950
|
+
- [helmet](https://helmetjs.github.io/) — security HTTP headers
|
|
951
|
+
- [cors](https://github.com/expressjs/cors) — CORS headers and preflight handling
|
|
952
|
+
- [serve-favicon](https://github.com/expressjs/serve-favicon) — efficient favicon serving
|
|
953
|
+
- [response-time](https://github.com/expressjs/response-time) — X-Response-Time header
|
|
810
954
|
- [PM2](https://pm2.keymetrics.io/) — production process manager with clustering
|
|
811
955
|
- [Biome](https://biomejs.dev/) — fast linter and formatter (Rust-based)
|
|
812
956
|
- [Cypress](https://www.cypress.io/) — E2E testing framework
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lopatnov/express-reverse-proxy",
|
|
3
3
|
"email": "oleksandr@lopatnov.cv.ua",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"description": "Node.js CLI tool to serve static front-end files with a reverse proxy for back-end APIs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "lopatnov",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"./hot-reload-client": "./hot-reload-client.js"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
|
-
"express-reverse-proxy": "./server.js"
|
|
14
|
+
"express-reverse-proxy": "./server.js",
|
|
15
|
+
"lerp": "./server.js"
|
|
15
16
|
},
|
|
16
17
|
"engines": {
|
|
17
18
|
"node": ">=18.0.0"
|
|
@@ -67,10 +68,15 @@
|
|
|
67
68
|
"access": "public"
|
|
68
69
|
},
|
|
69
70
|
"dependencies": {
|
|
71
|
+
"compression": "^1.7.5",
|
|
72
|
+
"cors": "^2.8.5",
|
|
70
73
|
"express": "^5.2.1",
|
|
71
74
|
"express-http-proxy": "^2.1.2",
|
|
75
|
+
"helmet": "^8.0.0",
|
|
72
76
|
"morgan": "^1.10.1",
|
|
73
|
-
"pm2": "^6.0.14"
|
|
77
|
+
"pm2": "^6.0.14",
|
|
78
|
+
"response-time": "^2.3.2",
|
|
79
|
+
"serve-favicon": "^2.5.0"
|
|
74
80
|
},
|
|
75
81
|
"devDependencies": {
|
|
76
82
|
"@biomejs/biome": "^2.4.2",
|
package/server.js
CHANGED
|
@@ -4,9 +4,14 @@ import fs from 'node:fs';
|
|
|
4
4
|
import https from 'node:https';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import compression from 'compression';
|
|
8
|
+
import cors from 'cors';
|
|
7
9
|
import express from 'express';
|
|
8
10
|
import proxy from 'express-http-proxy';
|
|
11
|
+
import helmet from 'helmet';
|
|
9
12
|
import morgan from 'morgan';
|
|
13
|
+
import responseTime from 'response-time';
|
|
14
|
+
import favicon from 'serve-favicon';
|
|
10
15
|
|
|
11
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
17
|
const __dirname = path.dirname(__filename);
|
|
@@ -361,6 +366,30 @@ configsByPort.forEach((portConfigs, p) => {
|
|
|
361
366
|
|
|
362
367
|
console.log(`[host] ${siteHost} → :${p}`);
|
|
363
368
|
|
|
369
|
+
if (siteConfig.responseTime) {
|
|
370
|
+
const opts = typeof siteConfig.responseTime === 'object' ? siteConfig.responseTime : {};
|
|
371
|
+
router.use(responseTime(opts));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (siteConfig.cors) {
|
|
375
|
+
const opts = typeof siteConfig.cors === 'object' ? siteConfig.cors : {};
|
|
376
|
+
router.use(cors(opts));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (siteConfig.compression) {
|
|
380
|
+
const opts = typeof siteConfig.compression === 'object' ? siteConfig.compression : {};
|
|
381
|
+
router.use(compression(opts));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (siteConfig.helmet) {
|
|
385
|
+
const opts = typeof siteConfig.helmet === 'object' ? siteConfig.helmet : {};
|
|
386
|
+
router.use(helmet(opts));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (siteConfig.favicon) {
|
|
390
|
+
router.use(favicon(path.resolve(configDir, siteConfig.favicon)));
|
|
391
|
+
}
|
|
392
|
+
|
|
364
393
|
if (siteConfig.headers) {
|
|
365
394
|
router.use((_req, res, next) => {
|
|
366
395
|
for (const h of Object.keys(siteConfig.headers)) res.setHeader(h, siteConfig.headers[h]);
|