@lopatnov/express-reverse-proxy 4.0.0 → 5.0.1

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 CHANGED
@@ -18,6 +18,10 @@
18
18
  - [Demo](#demo)
19
19
  - [How It Works](#how-it-works)
20
20
  - [CLI Options](#cli-options)
21
+ - [--config](#--config)
22
+ - [--init](#--init)
23
+ - [--cluster](#--cluster)
24
+ - [--cluster-config](#--cluster-config)
21
25
  - [Configuration](#configuration)
22
26
  - [port](#port)
23
27
  - [logging](#logging)
@@ -27,13 +31,31 @@
27
31
  - [cors](#cors)
28
32
  - [favicon](#favicon)
29
33
  - [responseTime](#responsetime)
34
+ - [rateLimit](#ratelimit)
35
+ - [basicAuth](#basicauth)
36
+ - [healthCheck](#healthcheck)
37
+ - [cgi](#cgi)
38
+ - [upload](#upload)
30
39
  - [headers](#headers)
40
+ - [redirects](#redirects)
31
41
  - [folders](#folders)
32
42
  - [proxy](#proxy)
33
43
  - [unhandled](#unhandled)
34
44
  - [host](#host)
35
45
  - [ssl](#ssl)
36
46
  - [Configuration Recipes](#configuration-recipes)
47
+ - [Static files + proxy fallback](#static-files-first-then-fall-back-to-back-end)
48
+ - [Static files + API on a specific path](#static-files--api-on-a-specific-path)
49
+ - [Hot reload dev server](#hot-reload-dev-server)
50
+ - [HTTPS with automatic HTTP redirect](#https-with-automatic-http-redirect)
51
+ - [HTTPS with a self-signed certificate](#https-with-a-self-signed-certificate-local-dev)
52
+ - [Production hardening](#production-hardening-helmet--cors--compression)
53
+ - [Protected admin area](#protected-admin-area)
54
+ - [URL migration](#url-migration-permanent-redirects)
55
+ - [Load-balanced API proxy](#load-balanced-api-proxy)
56
+ - [File upload server](#file-upload-server)
57
+ - [Health check for Docker / Kubernetes](#health-check-for-docker--kubernetes)
58
+ - [CORS headers + rich error responses](#cors-headers--rich-error-responses)
37
59
  - [Docker & PM2](#docker--pm2)
38
60
  - [Testing](#testing)
39
61
  - [Troubleshooting](#troubleshooting)
@@ -67,7 +89,13 @@ npx @lopatnov/express-reverse-proxy
67
89
 
68
90
  ## Quick Start
69
91
 
70
- 1. Create a `server-config.json` in your project root:
92
+ 1. Generate a `server-config.json` interactively (or create it manually):
93
+
94
+ ```shell
95
+ express-reverse-proxy --init
96
+ ```
97
+
98
+ Or create it manually:
71
99
 
72
100
  ```json
73
101
  {
@@ -139,24 +167,48 @@ Browser
139
167
 
140
168
  ## How It Works
141
169
 
142
- Every incoming request is processed in order:
170
+ Every incoming request passes through the middleware chain in this order:
143
171
 
144
172
  ```
145
173
  Request
146
174
 
147
- ├─▶ Custom headers applied (if configured)
175
+ ├─▶ Logging (morgan) — log to console or file
176
+ ├─▶ responseTime — measure and record latency
177
+ ├─▶ cors — CORS headers + preflight OPTIONS
178
+ ├─▶ compression — gzip/deflate response body
179
+ ├─▶ helmet — security HTTP headers
180
+ ├─▶ favicon — serve /favicon.ico from memory
181
+
182
+ ├─▶ healthCheck — GET /__health__ → {status, uptime}
183
+ │ └─▶ Path matches → respond immediately (bypasses auth below)
184
+
185
+ ├─▶ rateLimit — 429 if client over limit
186
+ ├─▶ basicAuth — 401 if credentials missing/wrong
187
+
188
+ ├─▶ Custom headers applied (headers)
189
+
190
+ ├─▶ Redirect rules checked (redirects)
191
+ │ └─▶ Path matches → 301/302 redirect
148
192
 
149
193
  ├─▶ Static files checked (folders, in order)
150
194
  │ └─▶ File found → serve it
151
195
 
196
+ ├─▶ CGI scripts checked (cgi)
197
+ │ └─▶ Path + extension matches → execute script → stream response
198
+
199
+ ├─▶ File upload handler (upload)
200
+ │ └─▶ POST path matches → save files, return JSON
201
+ │ GET path matches → serve uploaded file
202
+
152
203
  ├─▶ Reverse proxy rules checked (proxy)
153
204
  │ └─▶ Path matches → forward to back-end → return response
205
+ │ (round-robin if multiple targets configured)
154
206
 
155
207
  └─▶ No match → unhandled handler (by Accept header)
156
- └─▶ Return status + body or redirect
208
+ └─▶ Return status + body
157
209
  ```
158
210
 
159
- Static files always take priority over proxy rules. Proxies are checked only when no file matches.
211
+ Options that are not configured are skipped entirely. Static files always take priority over proxy rules.
160
212
 
161
213
  ---
162
214
 
@@ -175,14 +227,51 @@ lerp [options]
175
227
  | ------------------------- | ----------------------------------------------------------------------------------------------- |
176
228
  | `--help` | Print help and exit |
177
229
  | `--config <file>` | Path to the JSON configuration file. Default: `server-config.json` |
230
+ | `--init` | Interactively create a `server-config.json` in the current directory |
178
231
  | `--cluster [action]` | Manage the PM2 cluster. Action defaults to `start` when omitted |
179
232
  | `--cluster-config <file>` | Path to a custom PM2 ecosystem config file. Default: `ecosystem.config.cjs` next to `server.js` |
180
233
 
181
- ### --cluster actions
234
+ ### --config
235
+
236
+ Specify the path to the configuration file. Accepts a file path or a directory (in which case `server-config.json` inside that directory is used).
237
+
238
+ ```shell
239
+ express-reverse-proxy --config ./configs/prod.json
240
+ express-reverse-proxy --config ./configs/
241
+ ```
242
+
243
+ Default: `server-config.json` in the current working directory.
244
+
245
+ If the file is not found and `--config` was explicitly provided, the server exits with an error. If no `--config` is given and the default file is missing, the server starts with built-in defaults (`port: 8000`, `folders: "."`) and prints a warning.
246
+
247
+ ### --init
248
+
249
+ Interactively creates a `server-config.json` in the current working directory. Asks for port, static folder, optional proxy target, and hot reload preference.
250
+
251
+ ```shell
252
+ express-reverse-proxy --init
253
+ ```
254
+
255
+ Example session:
256
+
257
+ ```
258
+ Port [8000]: 8080
259
+ Static folder [.]: www
260
+ Proxy path (e.g. /api) [skip]: /api
261
+ Proxy target for /api: http://localhost:4000
262
+ Hot reload? [y/N]: y
263
+ [init] Created /your/project/server-config.json
264
+ ```
265
+
266
+ If `server-config.json` already exists, the command asks before overwriting.
267
+
268
+ ### --cluster
269
+
270
+ Manage the PM2 process cluster. Action defaults to `start` when omitted.
182
271
 
183
272
  | Action | Description |
184
273
  | --------- | ------------------------------------------------------ |
185
- | `start` | Start the PM2 cluster (default when action is omitted) |
274
+ | `start` | Start the cluster (default when action is omitted) |
186
275
  | `stop` | Stop all cluster instances |
187
276
  | `restart` | Restart all cluster instances |
188
277
  | `status` | Show PM2 process status table |
@@ -190,7 +279,7 @@ lerp [options]
190
279
  | `monitor` | Open the PM2 real-time monitor |
191
280
 
192
281
  ```shell
193
- express-reverse-proxy --cluster
282
+ express-reverse-proxy --cluster # same as --cluster start
194
283
  express-reverse-proxy --cluster start
195
284
  express-reverse-proxy --cluster stop
196
285
  express-reverse-proxy --cluster restart
@@ -199,25 +288,41 @@ express-reverse-proxy --cluster logs
199
288
  express-reverse-proxy --cluster monitor
200
289
  ```
201
290
 
202
- Pass a custom config to cluster workers with `--config`:
291
+ Pass `--config` to forward a custom config path to all cluster workers:
203
292
 
204
293
  ```shell
205
294
  express-reverse-proxy --cluster start --config ./configs/prod.json
206
295
  ```
207
296
 
208
- Use a custom PM2 ecosystem file with `--cluster-config`:
297
+ ### --cluster-config
298
+
299
+ Override the PM2 ecosystem config file used to start the cluster. Default: `ecosystem.config.cjs` in the same directory as `server.js`.
209
300
 
210
301
  ```shell
211
302
  express-reverse-proxy --cluster start --cluster-config ./my-ecosystem.config.cjs
212
303
  express-reverse-proxy --cluster restart --cluster-config /etc/myapp/ecosystem.config.cjs
213
304
  ```
214
305
 
306
+ Useful when you need custom PM2 settings such as a different number of instances, environment variables, or log file paths. See [PM2 ecosystem documentation](https://pm2.keymetrics.io/docs/usage/application-declaration/) for all options.
307
+
215
308
  ---
216
309
 
217
310
  ## Configuration
218
311
 
219
312
  All configuration lives in a single JSON file (default `server-config.json`).
220
313
 
314
+ ### IDE autocomplete (JSON Schema)
315
+
316
+ Add a `$schema` reference to your config file to get property autocomplete, descriptions, and type checking in VS Code and other editors:
317
+
318
+ ```json
319
+ {
320
+ "$schema": "https://unpkg.com/@lopatnov/express-reverse-proxy/server-config.schema.json",
321
+ "port": 8080,
322
+ "folders": "www"
323
+ }
324
+ ```
325
+
221
326
  ### Environment variables
222
327
 
223
328
  | Variable | Default | Description |
@@ -237,7 +342,7 @@ The port the server listens on. Defaults to `8000`. Can also be set via the `POR
237
342
 
238
343
  ### logging
239
344
 
240
- Controls HTTP request logging (Morgan). Enabled by default. Set to `false` to silence per-request log lines — useful in production behind another proxy, or to keep console output clean.
345
+ Controls HTTP request logging (Morgan). Enabled by default (`dev` format). Set to `false` to silence per-request log lines — useful in production behind another proxy, or to keep console output clean.
241
346
 
242
347
  ```json
243
348
  {
@@ -247,6 +352,23 @@ Controls HTTP request logging (Morgan). Enabled by default. Set to `false` to si
247
352
  }
248
353
  ```
249
354
 
355
+ **Object form** — write logs to a file and/or choose a different format:
356
+
357
+ ```json
358
+ {
359
+ "logging": { "format": "combined", "file": "./logs/access.log" }
360
+ }
361
+ ```
362
+
363
+ | Option | Default | Description |
364
+ | -------- | ------------ | -------------------------------------------------------------- |
365
+ | `format` | `"combined"` | Morgan format: `combined`, `common`, `dev`, `short`, or `tiny` |
366
+ | `file` | none | Path to log file (relative to config file). Appended if exists |
367
+
368
+ When `file` is set, logs are written to the file only (not to the console).
369
+
370
+ > Logging is applied per-site, so each virtual host can have its own format and log file.
371
+
250
372
  ### hotReload
251
373
 
252
374
  Watches the `folders` directories for file changes and automatically reloads connected browser tabs. Uses Server-Sent Events (SSE). Intended for local development only.
@@ -296,6 +418,42 @@ Add headers to every response — useful for CORS in development.
296
418
  }
297
419
  ```
298
420
 
421
+ ### redirects
422
+
423
+ Permanently or temporarily redirect URL paths to new destinations. Redirects are checked before static files and proxy rules.
424
+
425
+ **Object form** — map source paths to destinations:
426
+
427
+ ```json
428
+ {
429
+ "redirects": {
430
+ "/old-path": "/new-path",
431
+ "/legacy": "https://new.example.com",
432
+ "/temp": { "to": "/temporary-destination", "status": 302 }
433
+ }
434
+ }
435
+ ```
436
+
437
+ **Array form** — explicit entries with `from`, `to`, and optional `status`:
438
+
439
+ ```json
440
+ {
441
+ "redirects": [
442
+ { "from": "/old", "to": "/new" },
443
+ { "from": "/moved", "to": "https://example.com", "status": 301 },
444
+ { "from": "/temp", "to": "/somewhere", "status": 302 }
445
+ ]
446
+ }
447
+ ```
448
+
449
+ | Field | Default | Description |
450
+ | -------- | ------- | -------------------------------------------------------- |
451
+ | `from` | — | Source URL path *(array form only, required)* |
452
+ | `to` | — | Destination path or full URL *(required)* |
453
+ | `status` | `301` | HTTP redirect status: `301`, `302`, `307`, or `308` |
454
+
455
+ > `301` — Moved Permanently. `302` — Found (temporary). Use `301` for permanent URL changes and `302` for temporary ones.
456
+
299
457
  ### folders
300
458
 
301
459
  Serve static files. Supports three forms:
@@ -375,6 +533,18 @@ Forward requests to a back-end server. Supports three forms:
375
533
  }
376
534
  ```
377
535
 
536
+ **Load balancing** — pass an array of targets for a path to distribute requests in round-robin:
537
+
538
+ ```json
539
+ {
540
+ "proxy": {
541
+ "/api": ["http://backend1:3000", "http://backend2:3000", "http://backend3:3000"]
542
+ }
543
+ }
544
+ ```
545
+
546
+ Requests to `/api` are forwarded to the backends in turn: `backend1`, `backend2`, `backend3`, `backend1`, …
547
+
378
548
  ### unhandled
379
549
 
380
550
  Control responses when no static file or proxy rule matches. Rules are selected by the request's `Accept` header.
@@ -455,11 +625,12 @@ To use multi-site mode, make the config file an **array** instead of an object.
455
625
 
456
626
  Enable HTTPS on a port by adding an `ssl` object to any site config for that port. All sites sharing the same port use the same certificate.
457
627
 
458
- | Field | Type | Description |
459
- | ------ | -------- | -------------------------------------------------------- |
460
- | `key` | `string` | Path to the private key file (PEM format) |
461
- | `cert` | `string` | Path to the certificate file (PEM format) |
462
- | `ca` | `string` | *(optional)* Path to the CA bundle for client validation |
628
+ | Field | Type | Description |
629
+ | ---------- | --------- | -------------------------------------------------------- |
630
+ | `key` | `string` | Path to the private key file (PEM format) |
631
+ | `cert` | `string` | Path to the certificate file (PEM format) |
632
+ | `ca` | `string` | *(optional)* Path to the CA bundle for client validation |
633
+ | `redirect` | `integer` | *(optional)* HTTP port to redirect (301) to HTTPS |
463
634
 
464
635
  Paths are resolved **relative to the config file**, not the current working directory.
465
636
 
@@ -477,6 +648,22 @@ Paths are resolved **relative to the config file**, not the current working dire
477
648
  }
478
649
  ```
479
650
 
651
+ **Automatic HTTP → HTTPS redirect** — set `redirect` to the HTTP port to also listen on plain HTTP and redirect all traffic to HTTPS:
652
+
653
+ ```json
654
+ {
655
+ "port": 443,
656
+ "ssl": {
657
+ "key": "./certs/key.pem",
658
+ "cert": "./certs/cert.pem",
659
+ "redirect": 80
660
+ },
661
+ "folders": "./public"
662
+ }
663
+ ```
664
+
665
+ This starts an HTTPS server on port `443` and a tiny redirect-only HTTP server on port `80`. All `http://` requests are permanently redirected (301) to `https://`.
666
+
480
667
  > All site configs on the same port must either all have `ssl` or none — mixing is a startup error.
481
668
 
482
669
  ### compression
@@ -581,6 +768,243 @@ With custom precision (see [response-time docs](https://github.com/expressjs/res
581
768
  }
582
769
  ```
583
770
 
771
+ ### rateLimit
772
+
773
+ Limit the number of requests a client can make in a time window. Responds with `429 Too Many Requests` when the limit is exceeded. Useful when running without a dedicated reverse proxy.
774
+
775
+ ```json
776
+ {
777
+ "port": 8080,
778
+ "rateLimit": { "windowMs": 60000, "limit": 100 },
779
+ "folders": "www"
780
+ }
781
+ ```
782
+
783
+ | Option | Default | Description |
784
+ | ----------- | -------- | ------------------------------------------------ |
785
+ | `windowMs` | `60000` | Time window in milliseconds |
786
+ | `limit` | `5` | Maximum requests per client per window |
787
+ | `message` | built-in | Response body when limit is exceeded |
788
+
789
+ See [express-rate-limit docs](https://express-rate-limit.mintlify.app/reference/configuration) for all options.
790
+
791
+ > Rate limiting is applied per-site and per IP address. In production behind Nginx or Caddy, configure rate limiting there instead — it runs before Node.js and is more efficient.
792
+
793
+ ### basicAuth
794
+
795
+ Protect the site with HTTP Basic Authentication. All requests must include valid credentials or the server responds with `401 Unauthorized`.
796
+
797
+ ```json
798
+ {
799
+ "port": 8080,
800
+ "basicAuth": {
801
+ "users": { "admin": "s3cr3t" },
802
+ "challenge": true
803
+ },
804
+ "folders": "www"
805
+ }
806
+ ```
807
+
808
+ | Option | Default | Description |
809
+ | ----------- | ------- | ------------------------------------------------------------ |
810
+ | `users` | — | Object mapping username → password *(required)* |
811
+ | `challenge` | `false` | Send `WWW-Authenticate` header to trigger browser login dialog |
812
+ | `realm` | — | Realm string shown in the browser login dialog |
813
+
814
+ See [express-basic-auth docs](https://github.com/LionC/express-basic-auth#options) for all options.
815
+
816
+ > Passwords are compared in plain text. Do not use Basic Auth over plain HTTP in production — always combine with `ssl` or put behind a TLS-terminating proxy.
817
+
818
+ ### healthCheck
819
+
820
+ Expose a lightweight health check endpoint. Returns a JSON response with server status, uptime, and current timestamp. Useful for load balancers, monitoring systems, and container health checks.
821
+
822
+ ```json
823
+ {
824
+ "port": 8080,
825
+ "healthCheck": true,
826
+ "folders": "www"
827
+ }
828
+ ```
829
+
830
+ Default endpoint: `GET /__health__`
831
+
832
+ ```json
833
+ { "status": "ok", "uptime": 42.3, "timestamp": "2026-01-01T12:00:00.000Z" }
834
+ ```
835
+
836
+ Custom path:
837
+
838
+ ```json
839
+ {
840
+ "healthCheck": { "path": "/health" }
841
+ }
842
+ ```
843
+
844
+ | Option | Default | Description |
845
+ | ------ | -------------- | -------------------------------- |
846
+ | `path` | `"/__health__"` | URL path of the health endpoint |
847
+
848
+ > The health check endpoint is placed before rate limiting and basic auth — it is always publicly accessible regardless of other authentication settings.
849
+
850
+ ### cgi
851
+
852
+ Execute server-side scripts using the CGI (Common Gateway Interface) protocol. When a request matches the configured URL prefix and file extension, the script is spawned as a child process — HTTP headers become environment variables, the request body is piped to stdin, and the script's stdout is streamed back as the HTTP response.
853
+
854
+ ```json
855
+ {
856
+ "port": 8080,
857
+ "cgi": {
858
+ "path": "/cgi-bin",
859
+ "dir": "./cgi-bin",
860
+ "extensions": [".cgi", ".pl", ".py", ".sh"],
861
+ "interpreters": {
862
+ ".py": "python3",
863
+ ".sh": "sh",
864
+ ".pl": "perl"
865
+ }
866
+ }
867
+ }
868
+ ```
869
+
870
+ | Option | Default | Description |
871
+ | -------------- | ------------------------------------- | -------------------------------------------------------------------- |
872
+ | `path` | `"/cgi-bin"` | URL prefix that triggers CGI dispatch |
873
+ | `dir` | `"./cgi-bin"` | Local directory containing scripts (resolved relative to config file)|
874
+ | `extensions` | `[".cgi", ".pl", ".py", ".sh"]` | File extensions treated as executable CGI scripts |
875
+ | `interpreters` | `{}` | Map of file extension → interpreter command |
876
+
877
+ Shorthand — point directly to the script directory (all defaults apply):
878
+
879
+ ```json
880
+ {
881
+ "cgi": "./cgi-bin"
882
+ }
883
+ ```
884
+
885
+ CGI environment variables set for every request:
886
+
887
+ | Variable | Value |
888
+ | ----------------- | -------------------------------------------------------- |
889
+ | `REQUEST_METHOD` | HTTP method (`GET`, `POST`, …) |
890
+ | `QUERY_STRING` | URL query string (without `?`) |
891
+ | `CONTENT_TYPE` | `Content-Type` request header |
892
+ | `CONTENT_LENGTH` | `Content-Length` request header |
893
+ | `SCRIPT_FILENAME` | Absolute path to the script file |
894
+ | `SCRIPT_NAME` | URL path to the script (e.g. `/cgi-bin/hello.py`) |
895
+ | `SERVER_NAME` | Requested hostname |
896
+ | `SERVER_PORT` | Server listen port |
897
+ | `REMOTE_ADDR` | Client IP address |
898
+ | `HTTP_*` | All request headers (e.g. `HTTP_ACCEPT`, `HTTP_HOST`) |
899
+
900
+ A minimal Python example (`cgi-bin/hello.py`):
901
+
902
+ ```python
903
+ #!/usr/bin/env python3
904
+ print("Content-Type: text/plain")
905
+ print("Status: 200 OK")
906
+ print()
907
+ print("Hello from CGI!")
908
+ ```
909
+
910
+ > **Unix/macOS note:** Scripts must be executable: `chmod +x cgi-bin/hello.py`. Alternatively, configure an `interpreters` entry for the extension — no executable bit required when an interpreter is specified.
911
+
912
+ > **Windows note:** Scripts are not directly executable on Windows. You must configure `interpreters` for every extension you use; otherwise the request returns a `500` spawn error.
913
+
914
+ **Array form** — multiple independent CGI directories on the same site:
915
+
916
+ ```json
917
+ {
918
+ "cgi": [
919
+ {
920
+ "path": "/py-scripts",
921
+ "dir": "./py-scripts",
922
+ "extensions": [".py"],
923
+ "interpreters": { ".py": "python3" }
924
+ },
925
+ {
926
+ "path": "/node-scripts",
927
+ "dir": "./node-scripts",
928
+ "extensions": [".js"],
929
+ "interpreters": { ".js": "node" }
930
+ }
931
+ ]
932
+ }
933
+ ```
934
+
935
+ Each entry in the array sets up an independent CGI mount point with its own directory, URL prefix, extensions, and interpreters.
936
+
937
+ ### upload
938
+
939
+ Accept file uploads via `multipart/form-data` and save them to a local directory. Uploaded files can be retrieved immediately via `GET`.
940
+
941
+ ```json
942
+ {
943
+ "port": 8080,
944
+ "upload": {
945
+ "path": "/upload",
946
+ "dir": "./uploads",
947
+ "maxFileSize": 10485760,
948
+ "maxFiles": 10,
949
+ "allowedTypes": ["image/jpeg", "image/png", "application/pdf"],
950
+ "fieldName": "file"
951
+ }
952
+ }
953
+ ```
954
+
955
+ Shorthand — directory only (all defaults apply):
956
+
957
+ ```json
958
+ {
959
+ "upload": "./uploads"
960
+ }
961
+ ```
962
+
963
+ | Option | Default | Description |
964
+ | --------------- | -------------- | ------------------------------------------------------------------------ |
965
+ | `path` | `"/upload"` | URL prefix for the upload endpoint |
966
+ | `dir` | `"./uploads"` | Save directory (resolved relative to the config file) |
967
+ | `maxFileSize` | none | Maximum file size in bytes; responds with `413` when exceeded |
968
+ | `maxFiles` | none | Maximum number of files per request; responds with `400` when exceeded |
969
+ | `allowedTypes` | none | MIME type whitelist; responds with `400` when the type is not in the list|
970
+ | `fieldName` | any field | Accept only files uploaded in this specific form field |
971
+
972
+ **Array form** — multiple upload endpoints on the same site:
973
+
974
+ ```json
975
+ {
976
+ "upload": [
977
+ { "path": "/photos", "dir": "./photos", "allowedTypes": ["image/jpeg", "image/png"] },
978
+ { "path": "/docs", "dir": "./documents", "allowedTypes": ["application/pdf"], "maxFileSize": 5242880 }
979
+ ]
980
+ }
981
+ ```
982
+
983
+ **HTTP interface:**
984
+
985
+ | Method | URL | Description |
986
+ | ------ | ---------------- | ---------------------------------------- |
987
+ | `POST` | `<path>` | Upload files via `multipart/form-data` |
988
+ | `GET` | `<path>/<name>` | Retrieve a previously uploaded file |
989
+
990
+ `POST` success response (`200`):
991
+
992
+ ```json
993
+ {
994
+ "files": [
995
+ { "file": "photo-1700000000000-123456789.jpg", "size": 45678, "originalName": "photo.jpg" }
996
+ ]
997
+ }
998
+ ```
999
+
1000
+ Upload with curl:
1001
+
1002
+ ```shell
1003
+ curl -F "file=@photo.jpg" http://localhost:8080/upload
1004
+ ```
1005
+
1006
+ > The upload directory is created automatically at startup if it does not exist. Saved filenames include a timestamp and random suffix to avoid collisions.
1007
+
584
1008
  ---
585
1009
 
586
1010
  ## Configuration Recipes
@@ -618,6 +1042,65 @@ Only `/api/*` requests go to the back-end; everything else stays local.
618
1042
  - `GET /api/users` → proxied to `http://localhost:4000/users`
619
1043
  - `GET /missing` → 404 Not Found
620
1044
 
1045
+ ### Hot reload dev server
1046
+
1047
+ Local development setup: serve a front-end build folder, proxy API requests to a local back-end, and automatically reload the browser on file changes.
1048
+
1049
+ ```json
1050
+ {
1051
+ "port": 3000,
1052
+ "hotReload": true,
1053
+ "folders": "./dist",
1054
+ "proxy": {
1055
+ "/api": "http://localhost:4000"
1056
+ }
1057
+ }
1058
+ ```
1059
+
1060
+ Add the client script to your HTML (or import it in your bundler entry point):
1061
+
1062
+ ```html
1063
+ <script src="/__hot-reload__/client.js"></script>
1064
+ ```
1065
+
1066
+ The browser reconnects automatically after server restarts.
1067
+
1068
+ ---
1069
+
1070
+ ### HTTPS with automatic HTTP redirect
1071
+
1072
+ Serve the site over HTTPS and redirect all plain-HTTP traffic (port 80) to HTTPS (port 443) with a permanent 301 redirect.
1073
+
1074
+ ```shell
1075
+ mkdir certs
1076
+ openssl req -x509 -newkey rsa:2048 -keyout certs/key.pem -out certs/cert.pem \
1077
+ -days 365 -nodes -subj "/CN=example.com"
1078
+ ```
1079
+
1080
+ ```json
1081
+ {
1082
+ "port": 443,
1083
+ "ssl": {
1084
+ "key": "./certs/key.pem",
1085
+ "cert": "./certs/cert.pem",
1086
+ "redirect": 80
1087
+ },
1088
+ "folders": "./public",
1089
+ "proxy": {
1090
+ "/api": "http://localhost:4000"
1091
+ }
1092
+ }
1093
+ ```
1094
+
1095
+ The server logs two listeners on startup:
1096
+
1097
+ ```
1098
+ [listen] https://localhost:443
1099
+ [listen] http redirect :80 → https :443
1100
+ ```
1101
+
1102
+ ---
1103
+
621
1104
  ### HTTPS with a self-signed certificate (local dev)
622
1105
 
623
1106
  ```shell
@@ -643,7 +1126,7 @@ openssl req -x509 -newkey rsa:2048 -keyout certs/key.pem -out certs/cert.pem \
643
1126
  Start and open in browser (accept the self-signed cert warning):
644
1127
 
645
1128
  ```shell
646
- node server.js --config server-config.json
1129
+ express-reverse-proxy --config server-config.json
647
1130
  # [listen] https://localhost:8443
648
1131
  ```
649
1132
 
@@ -669,6 +1152,160 @@ Enable security headers, CORS, and response compression in one config:
669
1152
 
670
1153
  ---
671
1154
 
1155
+ ### Protected admin area
1156
+
1157
+ Protect a site with rate limiting and HTTP Basic Auth. Useful for internal tools or staging environments.
1158
+
1159
+ ```json
1160
+ {
1161
+ "port": 8080,
1162
+ "rateLimit": { "windowMs": 60000, "limit": 30 },
1163
+ "basicAuth": {
1164
+ "users": { "admin": "s3cr3t", "viewer": "readonly" },
1165
+ "challenge": true
1166
+ },
1167
+ "folders": "./admin",
1168
+ "proxy": {
1169
+ "/api": "http://localhost:4000"
1170
+ }
1171
+ }
1172
+ ```
1173
+
1174
+ - Requests without valid credentials → `401 Unauthorized` (browser shows login dialog)
1175
+ - More than 30 requests per minute from the same IP → `429 Too Many Requests`
1176
+
1177
+ > Always combine Basic Auth with `ssl` in production — credentials are transmitted in plain text otherwise.
1178
+
1179
+ ---
1180
+
1181
+ ### URL migration (permanent redirects)
1182
+
1183
+ Redirect old URLs to new ones after a site restructure, without breaking existing links or SEO rankings.
1184
+
1185
+ ```json
1186
+ {
1187
+ "port": 8080,
1188
+ "redirects": [
1189
+ { "from": "/about.html", "to": "/about", "status": 301 },
1190
+ { "from": "/products.html", "to": "/products", "status": 301 },
1191
+ { "from": "/blog/:slug", "to": "/posts/:slug", "status": 301 }
1192
+ ],
1193
+ "folders": "./public"
1194
+ }
1195
+ ```
1196
+
1197
+ Or as an object map for simple path-to-path redirects:
1198
+
1199
+ ```json
1200
+ {
1201
+ "redirects": {
1202
+ "/old-home": "/",
1203
+ "/old-about": "/about",
1204
+ "/legacy-api": "https://api.example.com"
1205
+ }
1206
+ }
1207
+ ```
1208
+
1209
+ ---
1210
+
1211
+ ### Load-balanced API proxy
1212
+
1213
+ Distribute API traffic across multiple back-end instances using round-robin load balancing. No external load balancer required.
1214
+
1215
+ ```json
1216
+ {
1217
+ "port": 8080,
1218
+ "folders": "./public",
1219
+ "proxy": {
1220
+ "/api": [
1221
+ "http://backend-1:3000",
1222
+ "http://backend-2:3000",
1223
+ "http://backend-3:3000"
1224
+ ]
1225
+ }
1226
+ }
1227
+ ```
1228
+
1229
+ Requests to `/api/*` are forwarded to the three back-ends in turn. If a back-end is down, its slot in the rotation still receives requests — add a health check at the application level or use a dedicated load balancer for automatic failover.
1230
+
1231
+ ---
1232
+
1233
+ ### File upload server
1234
+
1235
+ Accept file uploads from a web form or API client and serve them back over HTTP.
1236
+
1237
+ ```json
1238
+ {
1239
+ "port": 8080,
1240
+ "upload": [
1241
+ {
1242
+ "path": "/photos",
1243
+ "dir": "./storage/photos",
1244
+ "maxFileSize": 5242880,
1245
+ "allowedTypes": ["image/jpeg", "image/png", "image/webp"]
1246
+ },
1247
+ {
1248
+ "path": "/documents",
1249
+ "dir": "./storage/docs",
1250
+ "maxFileSize": 10485760,
1251
+ "allowedTypes": ["application/pdf"]
1252
+ }
1253
+ ]
1254
+ }
1255
+ ```
1256
+
1257
+ Upload a photo:
1258
+
1259
+ ```shell
1260
+ curl -F "file=@photo.jpg" http://localhost:8080/photos
1261
+ # {"files":[{"file":"photo-1700000000000-123456789.jpg","size":45678,"originalName":"photo.jpg"}]}
1262
+ ```
1263
+
1264
+ Retrieve it:
1265
+
1266
+ ```shell
1267
+ curl http://localhost:8080/photos/photo-1700000000000-123456789.jpg
1268
+ ```
1269
+
1270
+ ---
1271
+
1272
+ ### Health check for Docker / Kubernetes
1273
+
1274
+ Add a health check endpoint and write access logs to a file — a common pattern for containerized deployments.
1275
+
1276
+ ```json
1277
+ {
1278
+ "port": 8080,
1279
+ "healthCheck": { "path": "/health" },
1280
+ "logging": { "format": "combined", "file": "/var/log/app/access.log" },
1281
+ "compression": true,
1282
+ "folders": "./public",
1283
+ "proxy": {
1284
+ "/api": "http://backend:3000"
1285
+ }
1286
+ }
1287
+ ```
1288
+
1289
+ Docker `HEALTHCHECK`:
1290
+
1291
+ ```dockerfile
1292
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
1293
+ CMD curl -f http://localhost:8080/health || exit 1
1294
+ ```
1295
+
1296
+ Kubernetes liveness/readiness probe:
1297
+
1298
+ ```yaml
1299
+ livenessProbe:
1300
+ httpGet:
1301
+ path: /health
1302
+ port: 8080
1303
+ initialDelaySeconds: 10
1304
+ periodSeconds: 30
1305
+ ```
1306
+
1307
+ ---
1308
+
672
1309
  ### CORS headers + rich error responses
673
1310
 
674
1311
  ```json
@@ -951,6 +1588,10 @@ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before
951
1588
  - [cors](https://github.com/expressjs/cors) — CORS headers and preflight handling
952
1589
  - [serve-favicon](https://github.com/expressjs/serve-favicon) — efficient favicon serving
953
1590
  - [response-time](https://github.com/expressjs/response-time) — X-Response-Time header
1591
+ - [express-rate-limit](https://express-rate-limit.mintlify.app/) — request rate limiting
1592
+ - [express-basic-auth](https://github.com/LionC/express-basic-auth) — HTTP Basic Authentication
1593
+ - CGI support — built on Node.js `child_process.spawn` (no external dependency)
1594
+ - [multer](https://github.com/expressjs/multer) — multipart/form-data file upload handling
954
1595
  - [PM2](https://pm2.keymetrics.io/) — production process manager with clustering
955
1596
  - [Biome](https://biomejs.dev/) — fast linter and formatter (Rust-based)
956
1597
  - [Cypress](https://www.cypress.io/) — E2E testing framework