@increase21/simplenodejs 1.0.19 → 1.0.21

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
@@ -1,27 +1,28 @@
1
1
  # @increase21/simplenodejs
2
2
 
3
- **SimpleNodeJS** is a minimal, dependency-free Node.js framework built on top of Nodes native `http` and `https` module.
3
+ **SimpleNodeJS** is a minimal, dependency-free Node.js framework built on top of Node's native `http` and `https` modules.
4
4
  It provides controller-based routing, middleware, plugins, and security utilities with full TypeScript support.
5
5
 
6
6
  ---
7
7
 
8
- ## Features
8
+ ## Features
9
9
 
10
- - Native Node.js HTTP server (no Express/Fastify)
10
+ - Native Node.js HTTP/HTTPS server (no Express/Fastify)
11
11
  - Controller-based routing (file-system driven)
12
12
  - Middleware & error middleware
13
13
  - Plugin system
14
- - Built-in security middlewares
15
- - Rate limiting
16
- - CORS
17
- - body and Query parsing
18
- - HTML responses
14
+ - Individual security middlewares (CORS, HSTS, CSP, Helmet, etc.)
15
+ - Rate limiting with proxy support
16
+ - Cookie parsing & signed cookies
17
+ - IP whitelist/blacklist
18
+ - Request logging, timeouts, cache control, maintenance mode
19
+ - Body and query parsing
19
20
  - TypeScript-first
20
- - Reverse-proxy friendly (Nginx)
21
+ - Reverse-proxy friendly (Nginx, load balancers)
21
22
 
22
23
  ---
23
24
 
24
- ## 📦 Installation
25
+ ## Installation
25
26
 
26
27
  ```bash
27
28
  npm install @increase21/simplenodejs
@@ -29,159 +30,195 @@ npm install @increase21/simplenodejs
29
30
 
30
31
  ---
31
32
 
32
- ## 🚀 Quick Start
33
+ ## Quick Start
33
34
 
34
35
  ```ts
35
- import { CreateSimpleJsHttpServer } from "@increase21/simplenodejs";
36
+ import {
37
+ CreateSimpleJsHttpServer,
38
+ SetBodyParser,
39
+ SetHelmet,
40
+ SetCORS,
41
+ SetRateLimiter,
42
+ } from "@increase21/simplenodejs";
43
+
44
+ const app = CreateSimpleJsHttpServer({
45
+ controllersDir: process.cwd() + "/controllers",
46
+ });
47
+
48
+ app.use(SetCORS());
49
+ app.use(SetHelmet());
50
+ app.use(SetBodyParser({ limit: "2mb" }));
51
+ app.use(SetRateLimiter({ windowMs: 60_000, max: 100 }));
52
+
53
+ app.listen(3000, () => {
54
+ console.log("Server running on http://localhost:3000");
55
+ });
36
56
  ```
37
- ---
38
57
 
39
- ## ⚙️ CreateSimpleJsHttpServer(options)
58
+ ---
40
59
 
41
- Creates and returns the HTTP app instance.
60
+ ## CreateSimpleJsHttpServer(options)
42
61
 
43
- ### Parameters
62
+ Creates and returns an HTTP app instance.
44
63
 
45
64
  | Param | Type | Required | Description |
46
65
  |------|------|----------|-------------|
47
- | `controllersDir` | `string` | ✅ | Absolute path to your controllers directory |
48
- <!-- | `trustProxy` | `boolean` | ❌ | If `true`, uses `x-forwarded-for` for IP detection (for Nginx/load balancers) |
49
- | `globalPrefix` | `string` | ❌ | Prefix all routes, e.g. `/api` | -->
66
+ | `controllersDir` | `string` | ✅ | Path to your controllers directory |
50
67
 
51
- ### Example
68
+ ## CreateSimpleJsHttpsServer(options)
69
+
70
+ Creates and returns an HTTPS app instance.
71
+
72
+ | Param | Type | Required | Description |
73
+ |------|------|----------|-------------|
74
+ | `controllersDir` | `string` | ✅ | Path to your controllers directory |
75
+ | `tlsOpts` | `https.ServerOptions` | ✅ | TLS options (key, cert, etc.) |
52
76
 
53
77
  ```ts
54
- const app = CreateSimpleJsHttpServer({
55
- controllersDir: process.cwd()+ "/controllers",
78
+ import fs from "fs";
79
+ import { CreateSimpleJsHttpsServer } from "@increase21/simplenodejs";
80
+
81
+ const app = CreateSimpleJsHttpsServer({
82
+ controllersDir: process.cwd() + "/controllers",
83
+ tlsOpts: {
84
+ key: fs.readFileSync("key.pem"),
85
+ cert: fs.readFileSync("cert.pem"),
86
+ },
56
87
  });
57
88
 
58
- app.listen(3000, () => {
59
- console.log("Server running on http://localhost:3000");
60
- });
89
+ app.listen(443);
61
90
  ```
62
91
 
63
92
  ---
64
93
 
65
- ## 📁 Controllers
94
+ ## Controllers
66
95
 
67
- Controllers are auto-loaded from `controllersDir`.
68
- #### Running an endpoint using RunRequest
69
- ##### ./controllers/{servicefolder}/auth.ts
70
- #### or
71
- #### ./controllers/{servicefolder}/auth.js
96
+ Controllers are auto-loaded from `controllersDir` at startup. The file path maps directly to the URL.
72
97
 
73
- ```ts
74
- export default AuthControllers extends SimpleNodeJsController {
75
-
76
- async account(id:string) {
77
- return this.RunRequest({
78
- post: //....your post method handler,
79
- get://...
80
- put://...
81
- delete://....
82
- ...
83
- })
84
- }
85
- };
86
98
  ```
99
+ controllers/
100
+ users/
101
+ auth.ts → /users/auth
102
+ profile.ts → /users/profile
103
+ drivers/
104
+ vehicles.ts → /drivers/vehicles
105
+ accountProfiles → /drivers/account-profiles
106
+ ```
107
+
108
+ ### Using __run
87
109
 
88
- The above endpoint is accessible on http://baseURl/{servicefolder}/auth/account.
89
- The endpoint receives optional parameter (id:string) which can be passed in the url e.g http://baseURl/{servicefolder}/auth/account/{id}
110
+ `__run` dispatches to the correct handler based on the HTTP method and enforces method-level ID validation.
90
111
 
91
- #### Running an endpoint without RunRequest
92
112
  ```ts
93
- export default AuthControllers extends SimpleNodeJsController {
113
+ // controllers/drivers/auths.ts
114
+ import { SimpleNodeJsController, SimpleJsPrivateMethodProps } from "@increase21/simplenodejs";
115
+
116
+ export default class AuthController extends SimpleNodeJsController {
117
+ async login() {
118
+ return this.__run({
119
+ post: () => { // handle POST /drivers/auths/login
120
+ return { token: "..." };
121
+ },
122
+ });
123
+ }
94
124
 
95
- async login() {
96
- if(this.method !=="post") return this.res.status(405).json({error:"Method Not Allowed"})
97
-
98
- return YourHandler(SimpleJsPrivateMethodProps)
125
+ async account(id: string) {
126
+ return this.__run({
127
+ get: () => {
128
+ // handle GET /drivers/auths/account/:id
129
+ },
130
+ put: () => {
131
+ // handle PUT /drivers/auths/account/:id
132
+ },
133
+ delete: () => {
134
+ // handle DELETE /drivers/auths/account/:id
135
+ },
136
+ id:{get:"optional", delete:"required",put:"required"}
137
+ },
138
+ );
99
139
  }
100
- };
101
- ```
102
- ### Endpoint Naming
103
- Endpoints are defined using camelCase method names in controller files and are exposed as kebab-case in the URL path.
104
- ```ts
105
- async vehicleList(id:string) {}
140
+ }
106
141
  ```
107
142
 
108
- ```code
109
- /vehicle-list
110
- /vehicle-list/{id}
111
- ```
143
+ ### Without __run
112
144
 
113
145
  ```ts
114
- async vehicle() {}
115
- ```
146
+ export default class AuthController extends SimpleNodeJsController {
147
+ async login() {
148
+ if (this.method !== "post") return this.res.status(405).json({ error: "Method Not Allowed" });
116
149
 
117
- ```code
118
- /vehicle
150
+ const { email, password } = this.body;
151
+ // ... your logic
152
+ return this.res.status(200).json({ token: "..." });
153
+ }
154
+ }
119
155
  ```
120
156
 
121
- ---
157
+ ### Endpoint Naming
158
+
159
+ Controller methods use **camelCase** and are exposed as **kebab-case** URLs.
122
160
 
123
- ### Controller Object Params
124
- Each method defined in a controller file is exposed as an endpoint by SimpleNodeJsController.
125
- Methods can receive parameters, which are passed through the URL pathname. When using this.RunRequest(...), the handler receives SimpleJsPrivateMethodProps.
161
+ | Method name | URL |
162
+ |---|---|
163
+ | `async index()` | `/drivers/auths` |
164
+ | `async login()` | `/drivers/auths/login` |
165
+ | `async vehicleList(id)` | `/drivers/auths/vehicle-list` or `/drivers/auths/vehicle-list/:id` |
126
166
 
127
167
  ---
128
168
 
129
- ## 🧾 SimpleJsPrivateMethodProps
130
- ```ts
131
- {
132
- body: any // parsed payload.
133
- res: ResponseObject;
134
- req: RequestObject;
135
- query: JSON // parsed requests param.
136
- id?: string;
137
- customData?: any //any custom data attached to req._custom_data by middlewares
138
- idMethod?: {
139
- post?: 'required' | 'optional',
140
- get?: 'required' | 'optional',
141
- put?: 'required' | 'optional',
142
- delete?: 'required' | 'optional',
143
- patch?: 'required' | 'optional',
144
- }
145
- }
146
- ```
169
+ ## SimpleJsPrivateMethodProps
170
+
171
+ Properties available inside `__run` handlers.
172
+
173
+ | Property | Type | Description |
174
+ |---|---|---|
175
+ | `req` | `RequestObject` | Raw request object |
176
+ | `res` | `ResponseObject` | Raw response object |
177
+ | `body` | `object` | Parsed request body |
178
+ | `query` | `object` | Parsed query string |
179
+ | `id` | `string \| undefined` | URL path parameter |
180
+ | `customData` | `any` | Data attached by plugins/middlewares via `req._custom_data` |
181
+
182
+ ---
147
183
 
148
- ## 🧾 RequestObject (req)
184
+ ## RequestObject (req)
149
185
 
150
- Available on every controller.
186
+ Extends Node's `IncomingMessage` with additional properties.
151
187
 
152
- | Additional Properties | Type | Description |
153
- |---------|------|-------------|
154
- | `req.url` | `string` | Request URL |
188
+ | Property | Type | Description |
189
+ |---|---|---|
190
+ | `req.url` | `string` | Full request URL |
155
191
  | `req.method` | `string` | HTTP method |
156
- | `req.query` | `object` | Parsed query params |
157
- | `req.body` | `any` | Parsed request body |
158
- | `req.raw_body` | `any` | Parsed request body |
159
- | `req._custom_data` | `any` |
192
+ | `req.headers` | `object` | Request headers |
193
+ | `req.query` | `object` | Parsed query string parameters |
194
+ | `req.body` | `any` | Parsed request body (set by `SetBodyParser`) |
195
+ | `req.id` | `string` | Auto-generated UUID for the request (also sent as `X-Request-Id` header) |
196
+ | `req._custom_data` | `object` | Shared data bag written by plugins (payload, cookies, etc.) |
160
197
 
161
198
  ---
162
199
 
163
- ## 🧾 ResponseObject (res)
200
+ ## ResponseObject (res)
164
201
 
165
- | Additional Methods | Params | Description |
166
- |--------|--------|-------------|
167
- | `res.status(code)` | `number` | Set HTTP status |
168
- | `res.json(data)` | `any` | Send JSON response |
169
- | `res.text(data)` | `string | Buffer` | Send raw response |
170
- | `res.html(html)` | `string` | Send HTML response |
202
+ Extends Node's `ServerResponse` with helper methods.
171
203
 
172
- ### Example
204
+ | Method | Params | Description |
205
+ |---|---|---|
206
+ | `res.status(code)` | `number` | Set HTTP status code, chainable |
207
+ | `res.json(data)` | `object` | Send a JSON response |
208
+ | `res.text(data?)` | `string` | Send a plain text response |
173
209
 
174
210
  ```ts
175
211
  res.status(200).json({ success: true });
212
+ res.status(404).text("Not found");
176
213
  ```
177
214
 
178
215
  ---
179
216
 
180
- ## 🔌 app.use(middleware)
217
+ ## app.use(middleware)
181
218
 
182
- Registers a middleware that runs before controllers.
219
+ Registers a middleware that runs on every request before controllers.
183
220
 
184
- ### Middleware Signature
221
+ ### Middleware signature
185
222
 
186
223
  ```ts
187
224
  (req: RequestObject, res: ResponseObject, next: () => Promise<void> | void) => Promise<any> | void
@@ -192,125 +229,339 @@ Registers a middleware that runs before controllers.
192
229
  ```ts
193
230
  app.use((req, res, next) => {
194
231
  console.log(req.method, req.url);
195
- next();
232
+ next();
196
233
  });
197
234
  ```
198
235
 
199
236
  ---
200
237
 
201
- ## app.useError(errorMiddleware)
202
-
203
- Registers a global error handler.
238
+ ## app.useError(errorMiddleware)
204
239
 
205
- ### ErrorHandler
206
- app.useError() registers global error-handling middleware.
207
- It catches all errors thrown anywhere in the request lifecycle — including:
208
- • Errors thrown inside middlewares
209
- • Errors thrown inside controllers / route handlers
210
- • Async errors (throw or rejected promises)
211
- • Validation errors
212
- • Custom application errors
213
-
214
- This gives you a single, centralized place to log, format, and return consistent error responses across your entire system.
240
+ Registers a global error handler. Catches all errors thrown in middlewares, controllers, and async handlers.
215
241
 
216
242
  ```ts
217
243
  app.useError((err, req, res, next) => {
218
- // handle error
244
+ const status = err?.statusCode || 500;
245
+ res.status(status).json({ error: err.message });
219
246
  });
220
247
  ```
221
248
 
222
249
  ---
223
250
 
224
- ## 🧩 app.registerPlugin(app=>plugin(app,opts))
251
+ ## app.registerPlugin(plugin)
225
252
 
226
- Registers a plugin.
253
+ Registers a plugin function.
227
254
 
228
- ### Plugin Shape
255
+ ```ts
256
+ type Plugin = (app: SimpleJsServer, opts?: any) => Promise<any> | void;
257
+ ```
229
258
 
230
259
  ```ts
231
- type Plugin = (app: SimpleJsServer, opts?: any) => Promise<any>|void;
260
+ app.registerPlugin(app => SimpleJsSecurityPlugin(app, opt));
232
261
  ```
233
262
 
234
- ### Built-In Plugins
263
+ ---
264
+
265
+ # Built-in Middlewares
235
266
 
236
- | name | Description | Status |
237
- |-----------|-------------|
238
- | `SimpleJsSecurityPlugin` | CORS, RateLimit, Helmet | Available |
239
- | `SimpleJsJWTPlugin` | JWT protection| Coming soon |
240
- | `SimpleJsIPWhitelistPlugin` | Restricting IP addresses | Coming soon |
241
- | `SimpleJsCookiePlugin` | Cookies Plugin | Coming soon |
267
+ ## SetBodyParser(options)
268
+
269
+ Parses the request body. Must be registered before controllers access `this.body`.
270
+
271
+ | Param | Type | Description |
272
+ |---|---|---|
273
+ | `limit` | `string \| number` | Max body size (e.g. `"2mb"`, `"500kb"`, or bytes as number). Default: `"1mb"` |
274
+
275
+ ```ts
276
+ app.use(SetBodyParser({ limit: "2mb" }));
277
+ ```
278
+
279
+ > Multipart/form-data bodies are not buffered into memory — the size limit still applies to prevent oversized uploads.
280
+
281
+ ---
282
+
283
+ ## SetCORS(options?)
284
+
285
+ Sets `Access-Control-*` headers and handles OPTIONS preflight.
286
+
287
+ | Param | Type | Default | Description |
288
+ |---|---|---|---|
289
+ | `origin` | `string` | `"*"` | Allowed origin |
290
+ | `methods` | `string` | `"GET, POST, DELETE, PUT, PATCH"` | Allowed methods |
291
+ | `headers` | `string` | standard set | Allowed headers |
292
+ | `credentials` | `boolean` | `false` | Allow cookies/auth headers. Requires `origin` to be set to a specific domain |
293
+
294
+ ```ts
295
+ // Public API
296
+ app.use(SetCORS());
297
+
298
+ // Credentialed (cookies, Authorization header)
299
+ app.use(SetCORS({ origin: "https://myapp.com", credentials: true }));
300
+ ```
242
301
 
243
302
  ---
244
303
 
245
- # 🧱 Built-in Middlewares
304
+ ## SetHelmet(options?)
246
305
 
247
- ## 🔐 SetRequestCORS(options?)
306
+ Sets all security response headers in one call. Each header can be individually overridden or disabled.
248
307
 
249
- Adds security headers.
308
+ | Option | Header | Default |
309
+ |---|---|---|
310
+ | `hsts` | `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` |
311
+ | `csp` | `Content-Security-Policy` | `default-src 'none'` |
312
+ | `frameGuard` | `X-Frame-Options` | `DENY` |
313
+ | `noSniff` | `X-Content-Type-Options` | `nosniff` |
314
+ | `referrerPolicy` | `Referrer-Policy` | `no-referrer` |
315
+ | `permissionsPolicy` | `Permissions-Policy` | all features blocked |
316
+ | `coep` | `Cross-Origin-Embedder-Policy` | `require-corp` |
317
+ | `coop` | `Cross-Origin-Opener-Policy` | `same-origin` |
250
318
 
251
- ### Options (optional)
252
- All the standard http headers
319
+ Pass `false` to disable any individual header. Pass a string to override the value.
253
320
 
254
- ### Usage
255
321
  ```ts
256
- app.use(SetRequestCORS({
257
- "Access-Control-Allow-Origin": "*",
258
- "X-Frame-Options": "DENY",
322
+ // All defaults
323
+ app.use(SetHelmet());
324
+
325
+ // HTTP server — disable HSTS, relax CSP
326
+ app.use(SetHelmet({
327
+ hsts: false,
328
+ csp: "default-src 'self'",
329
+ coep: false,
259
330
  }));
260
331
  ```
261
332
 
262
333
  ---
263
334
 
264
- ## SetRateLimiter(options)
335
+ ## Individual Security Headers
336
+
337
+ Each header is also available as a standalone middleware:
265
338
 
266
- ### Options
339
+ | Function | Header |
340
+ |---|---|
341
+ | `SetHSTS(opts?)` | `Strict-Transport-Security` |
342
+ | `SetCSP(policy?)` | `Content-Security-Policy` |
343
+ | `SetFrameGuard(action?)` | `X-Frame-Options` |
344
+ | `SetNoSniff()` | `X-Content-Type-Options` |
345
+ | `SetReferrerPolicy(policy?)` | `Referrer-Policy` |
346
+ | `SetPermissionsPolicy(policy?)` | `Permissions-Policy` |
347
+ | `SetCOEP(value?)` | `Cross-Origin-Embedder-Policy` |
348
+ | `SetCOOP(value?)` | `Cross-Origin-Opener-Policy` |
349
+
350
+ ```ts
351
+ app.use(SetFrameGuard("SAMEORIGIN"));
352
+ app.use(SetCSP("default-src 'self'; img-src *"));
353
+ app.use(SetHSTS({ maxAge: 63072000, preload: true }));
354
+ ```
355
+
356
+ > `SetHSTS` is only meaningful on HTTPS. Browsers silently ignore it over plain HTTP.
357
+
358
+ ---
359
+
360
+ ## SetRateLimiter(options)
361
+
362
+ Limits repeated requests per client IP using an in-memory store.
267
363
 
268
364
  | Param | Type | Required | Description |
269
- |------|------|----------|-------------|
270
- | `windowMs` | `number` | ✅ | Time window in ms |
365
+ |---|---|---|---|
366
+ | `windowMs` | `number` | ✅ | Time window in milliseconds |
271
367
  | `max` | `number` | ✅ | Max requests per window |
272
- | `keyGenerator` | `(req) => string` | ❌ | Custom key generator |
368
+ | `trustProxy` | `boolean` | ❌ | If `true`, reads IP from `X-Forwarded-For` (for Nginx/load balancers). Default: `false` |
369
+ | `keyGenerator` | `(req) => string` | ❌ | Custom key function (e.g. by user ID instead of IP) |
273
370
 
274
371
  ```ts
275
- app.use(SetRateLimiter({
276
- windowMs: 60_000,
277
- max: 100,
278
- keyGenerator: (req) => req.ip
372
+ app.use(SetRateLimiter({ windowMs: 60_000, max: 100 }));
373
+
374
+ // Behind Nginx
375
+ app.use(SetRateLimiter({ windowMs: 60_000, max: 100, trustProxy: true }));
376
+ ```
377
+
378
+ > The store is in-memory and per-process. In clustered/multi-worker deployments each worker maintains its own counter. Use a custom `keyGenerator` with an external store for distributed rate limiting.
379
+
380
+ ---
381
+
382
+ # Plugins
383
+
384
+ ## SimpleJsSecurityPlugin
385
+
386
+ Convenience plugin combining CORS, Helmet, and rate limiting.
387
+
388
+ ```ts
389
+ import { SimpleJsSecurityPlugin } from "@increase21/simplenodejs";
390
+
391
+ app.registerPlugin(app => SimpleJsSecurityPlugin(app, {
392
+ cors: { origin: "https://myapp.com", credentials: true },
393
+ helmet: { hsts: false },
394
+ rateLimit: { windowMs: 60_000, max: 200 },
279
395
  }));
280
396
  ```
281
397
 
282
398
  ---
283
399
 
284
- ## 📥 SetBodyParser(options?)
285
- SetBodyParser middleware must be set for controllers to receive all needed data to process.
286
- ### Options
400
+ ## SimpleJsCookiePlugin + SignCookie
287
401
 
288
- | Param | Type | Description |
289
- |------|------|-------------|
290
- | `limit` | `string` or `number` | Max body size (e.g. "1mb")
402
+ Parses the `Cookie` header on every request. Cookies are available at `this._custom_data.cookies`.
403
+
404
+ If a `secret` is provided, signed cookies (prefixed with `s:`) are verified using HMAC-SHA256. Cookies with invalid signatures are silently dropped.
405
+
406
+ | Option | Type | Description |
407
+ |---|---|---|
408
+ | `secret` | `string` | Optional signing secret for verified cookies |
409
+ | `dataKey` | `string` | Key on `_custom_data`. Default: `"cookies"` |
291
410
 
292
411
  ```ts
293
- app.use(SetBodyParser({ limit: "2mb" }));
412
+ import { SimpleJsCookiePlugin, SignCookie } from "@increase21/simplenodejs";
413
+
414
+ // Register plugin
415
+ app.registerPlugin(app => SimpleJsCookiePlugin(app, {
416
+ secret: process.env.COOKIE_SECRET,
417
+ }));
418
+
419
+ // Set a signed cookie in a controller
420
+ const signed = SignCookie(sessionId, process.env.COOKIE_SECRET!);
421
+ this.res.setHeader("Set-Cookie", `session=${signed}; HttpOnly; Secure; SameSite=Strict`);
422
+
423
+ // Read cookie in any controller
424
+ const { session } = this._custom_data.cookies;
425
+ ```
426
+
427
+ ---
428
+
429
+ ## SimpleJsIPWhitelistPlugin
430
+
431
+ Allows or blocks requests by client IP address.
432
+
433
+ | Option | Type | Description |
434
+ |---|---|---|
435
+ | `ips` | `string[]` | List of IP addresses |
436
+ | `mode` | `"allow" \| "deny"` | `"allow"` = whitelist (only listed IPs pass). `"deny"` = blacklist (listed IPs are blocked). Default: `"allow"` |
437
+ | `trustProxy` | `boolean` | Read IP from `X-Forwarded-For`. Default: `false` |
438
+
439
+ ```ts
440
+ import { SimpleJsIPWhitelistPlugin } from "@increase21/simplenodejs";
441
+
442
+ // Only allow specific IPs (whitelist)
443
+ app.registerPlugin(app => SimpleJsIPWhitelistPlugin(app, {
444
+ ips: ["203.0.113.10", "198.51.100.5"],
445
+ mode: "allow",
446
+ }));
447
+
448
+ // Block known bad IPs (blacklist)
449
+ app.registerPlugin(app => SimpleJsIPWhitelistPlugin(app, {
450
+ ips: ["203.0.113.99"],
451
+ mode: "deny",
452
+ trustProxy: true,
453
+ }));
454
+ ```
455
+
456
+ ---
457
+
458
+ ## SimpleJsRequestLoggerPlugin
459
+
460
+ Logs every completed request with method, URL, status code, and duration.
461
+
462
+ | Option | Type | Description |
463
+ |---|---|---|
464
+ | `logger` | `(msg: string) => void` | Custom log function. Default: `console.log` |
465
+ | `format` | `"simple" \| "json"` | Log format. Default: `"simple"` |
466
+
467
+ ```ts
468
+ import { SimpleJsRequestLoggerPlugin } from "@increase21/simplenodejs";
469
+
470
+ // Simple text logs
471
+ app.registerPlugin(app => SimpleJsRequestLoggerPlugin(app));
472
+ // → [2025-01-01T00:00:00.000Z] GET /users/auth/login 200 12ms
473
+
474
+ // JSON logs (for log aggregators)
475
+ app.registerPlugin(app => SimpleJsRequestLoggerPlugin(app, {
476
+ format: "json",
477
+ logger: (msg) => process.stdout.write(msg + "\n"),
478
+ }));
479
+ // → {"time":"...","method":"GET","url":"/users/auth/login","status":200,"ms":12,"id":"uuid"}
480
+ ```
481
+
482
+ ---
483
+
484
+ ## SimpleJsTimeoutPlugin
485
+
486
+ Automatically closes requests that exceed the configured time limit with `503`.
487
+
488
+ | Option | Type | Description |
489
+ |---|---|---|
490
+ | `ms` | `number` | Timeout in milliseconds |
491
+ | `message` | `string` | Custom timeout message. Default: `"Request timeout"` |
492
+
493
+ ```ts
494
+ import { SimpleJsTimeoutPlugin } from "@increase21/simplenodejs";
495
+
496
+ app.registerPlugin(app => SimpleJsTimeoutPlugin(app, { ms: 10_000 }));
497
+ ```
498
+
499
+ ---
500
+
501
+ ## SimpleJsCachePlugin
502
+
503
+ Sets `Cache-Control` response headers globally.
504
+
505
+ | Option | Type | Description |
506
+ |---|---|---|
507
+ | `maxAge` | `number` | Max age in seconds |
508
+ | `private` | `boolean` | Mark as private (user-specific, not shared caches) |
509
+ | `noStore` | `boolean` | Disable all caching entirely |
510
+
511
+ ```ts
512
+ import { SimpleJsCachePlugin } from "@increase21/simplenodejs";
513
+
514
+ // Public cache for 5 minutes
515
+ app.registerPlugin(app => SimpleJsCachePlugin(app, { maxAge: 300 }));
516
+
517
+ // No caching (APIs with sensitive data)
518
+ app.registerPlugin(app => SimpleJsCachePlugin(app, { noStore: true }));
519
+ ```
520
+
521
+ ---
522
+
523
+ ## SimpleJsMaintenanceModePlugin
524
+
525
+ Returns `503` for all traffic when maintenance mode is on. Specific IPs (e.g. your office or CI server) can bypass.
526
+
527
+ | Option | Type | Description |
528
+ |---|---|---|
529
+ | `enabled` | `boolean` | Toggle maintenance mode |
530
+ | `message` | `string` | Custom response message |
531
+ | `allowIPs` | `string[]` | IPs that bypass maintenance mode |
532
+ | `trustProxy` | `boolean` | Read IP from `X-Forwarded-For`. Default: `false` |
533
+
534
+ ```ts
535
+ import { SimpleJsMaintenanceModePlugin } from "@increase21/simplenodejs";
536
+
537
+ app.registerPlugin(app => SimpleJsMaintenanceModePlugin(app, {
538
+ enabled: process.env.MAINTENANCE === "true",
539
+ message: "We are upgrading. Back soon.",
540
+ allowIPs: ["203.0.113.10"],
541
+ }));
294
542
  ```
295
543
 
296
544
  ---
297
545
 
298
- ## 🛡 Security Best Practices
546
+ # Security Best Practices
299
547
 
300
- - Always enable `SetSecurityHeaders`
301
- - Enable `SetRateLimiter` on public APIs
302
- - Validate request body
303
- <!-- - Use `trustProxy: true` only behind trusted proxies -->
304
- - Avoid leaking stack traces in production
548
+ - Always register `SetHelmet()` or individual header middlewares
549
+ - Use `SetRateLimiter` on all public endpoints
550
+ - Enable `credentials: true` in `SetCORS` only with a specific `origin` — never with a wildcard
551
+ - Only set `trustProxy: true` on `SetRateLimiter` or `SimpleJsIPWhitelistPlugin` when running behind a trusted reverse proxy (Nginx, etc.)
552
+ - Register `SetBodyParser` with a reasonable `limit` to prevent oversized payloads
553
+ - Use `app.useError` to handle errors uniformly — unhandled errors return `"Service unavailable"` with no internal details exposed
554
+ - Add `HttpOnly; Secure; SameSite=Strict` attributes when setting cookies via `Set-Cookie`
555
+ - On HTTPS deployments, register `SetHSTS()` or include it in `SetHelmet()`
305
556
 
306
557
  ---
307
558
 
308
- ## 📄 License
559
+ ## License
309
560
 
310
561
  MIT
311
562
 
312
563
  ---
313
564
 
314
- ## 👤 Author
565
+ ## Author
315
566
 
316
567
  Increase