@increase21/simplenodejs 1.0.20 → 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 +418 -167
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -2
- package/dist/router.d.ts +1 -3
- package/dist/router.js +37 -51
- package/dist/server.d.ts +9 -5
- package/dist/server.js +38 -33
- package/dist/typings/general.d.ts +7 -18
- package/dist/typings/simpletypes.d.ts +10 -0
- package/dist/utils/helpers.d.ts +6 -3
- package/dist/utils/helpers.js +51 -3
- package/dist/utils/simpleController.d.ts +5 -7
- package/dist/utils/simpleController.js +4 -21
- package/dist/utils/simpleMiddleware.d.ts +32 -4
- package/dist/utils/simpleMiddleware.js +136 -51
- package/dist/utils/simplePlugins.d.ts +67 -4
- package/dist/utils/simplePlugins.js +200 -2
- package/package.json +1 -1
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 Node
|
|
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
|
-
##
|
|
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
|
-
-
|
|
15
|
-
- Rate limiting
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
33
|
+
## Quick Start
|
|
33
34
|
|
|
34
35
|
```ts
|
|
35
|
-
import {
|
|
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
|
-
|
|
58
|
+
---
|
|
40
59
|
|
|
41
|
-
|
|
60
|
+
## CreateSimpleJsHttpServer(options)
|
|
42
61
|
|
|
43
|
-
|
|
62
|
+
Creates and returns an HTTP app instance.
|
|
44
63
|
|
|
45
64
|
| Param | Type | Required | Description |
|
|
46
65
|
|------|------|----------|-------------|
|
|
47
|
-
| `controllersDir` | `string` | ✅ |
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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(
|
|
59
|
-
console.log("Server running on http://localhost:3000");
|
|
60
|
-
});
|
|
89
|
+
app.listen(443);
|
|
61
90
|
```
|
|
62
91
|
|
|
63
92
|
---
|
|
64
93
|
|
|
65
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
109
|
-
/vehicle-list
|
|
110
|
-
/vehicle-list/{id}
|
|
111
|
-
```
|
|
143
|
+
### Without __run
|
|
112
144
|
|
|
113
145
|
```ts
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
##
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
##
|
|
184
|
+
## RequestObject (req)
|
|
149
185
|
|
|
150
|
-
|
|
186
|
+
Extends Node's `IncomingMessage` with additional properties.
|
|
151
187
|
|
|
152
|
-
|
|
|
153
|
-
|
|
154
|
-
| `req.url` | `string` |
|
|
188
|
+
| Property | Type | Description |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `req.url` | `string` | Full request URL |
|
|
155
191
|
| `req.method` | `string` | HTTP method |
|
|
156
|
-
| `req.
|
|
157
|
-
| `req.
|
|
158
|
-
| `req.
|
|
159
|
-
| `req.
|
|
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
|
-
##
|
|
200
|
+
## ResponseObject (res)
|
|
164
201
|
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
-
|
|
232
|
+
next();
|
|
196
233
|
});
|
|
197
234
|
```
|
|
198
235
|
|
|
199
236
|
---
|
|
200
237
|
|
|
201
|
-
##
|
|
202
|
-
|
|
203
|
-
Registers a global error handler.
|
|
238
|
+
## app.useError(errorMiddleware)
|
|
204
239
|
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
251
|
+
## app.registerPlugin(plugin)
|
|
225
252
|
|
|
226
|
-
Registers a plugin.
|
|
253
|
+
Registers a plugin function.
|
|
227
254
|
|
|
228
|
-
|
|
255
|
+
```ts
|
|
256
|
+
type Plugin = (app: SimpleJsServer, opts?: any) => Promise<any> | void;
|
|
257
|
+
```
|
|
229
258
|
|
|
230
259
|
```ts
|
|
231
|
-
|
|
260
|
+
app.registerPlugin(app => SimpleJsSecurityPlugin(app, opt));
|
|
232
261
|
```
|
|
233
262
|
|
|
234
|
-
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
# Built-in Middlewares
|
|
235
266
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
|
241
|
-
|
|
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
|
-
|
|
304
|
+
## SetHelmet(options?)
|
|
246
305
|
|
|
247
|
-
|
|
306
|
+
Sets all security response headers in one call. Each header can be individually overridden or disabled.
|
|
248
307
|
|
|
249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
##
|
|
335
|
+
## Individual Security Headers
|
|
336
|
+
|
|
337
|
+
Each header is also available as a standalone middleware:
|
|
265
338
|
|
|
266
|
-
|
|
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
|
|
365
|
+
|---|---|---|---|
|
|
366
|
+
| `windowMs` | `number` | ✅ | Time window in milliseconds |
|
|
271
367
|
| `max` | `number` | ✅ | Max requests per window |
|
|
272
|
-
| `
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
##
|
|
285
|
-
SetBodyParser middleware must be set for controllers to receive all needed data to process.
|
|
286
|
-
### Options
|
|
400
|
+
## SimpleJsCookiePlugin + SignCookie
|
|
287
401
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
546
|
+
# Security Best Practices
|
|
299
547
|
|
|
300
|
-
- Always
|
|
301
|
-
-
|
|
302
|
-
-
|
|
303
|
-
|
|
304
|
-
-
|
|
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
|
-
##
|
|
559
|
+
## License
|
|
309
560
|
|
|
310
561
|
MIT
|
|
311
562
|
|
|
312
563
|
---
|
|
313
564
|
|
|
314
|
-
##
|
|
565
|
+
## Author
|
|
315
566
|
|
|
316
567
|
Increase
|