@increase21/simplenodejs
v2.0.4
Published
Lightweight Node.js HTTP framework with middlewares and plugins
Downloads
843
Maintainers
Readme
@increase21/simplenodejs
SimpleNodeJS is a minimal, dependency-free Node.js framework built on top of Node's native http and https modules.
It provides controller-based routing, middleware, plugins, and security utilities with full TypeScript support.
Features
- Native Node.js HTTP/HTTPS server (no Express/Fastify)
- Controller-based routing (file-system driven)
- Middleware & error middleware
- Plugin system
- Individual security middlewares (CORS, HSTS, CSP, Helmet, etc.)
- Rate limiting with proxy support
- Cookie parsing & signed cookies
- IP whitelist/blacklist
- Request logging, timeouts, cache control, maintenance mode
- Body and query parsing
- TypeScript-first
- Reverse-proxy friendly (Nginx, load balancers)
Installation
npm install @increase21/simplenodejsQuick Start
import {
CreateSimpleJsHttpServer,
SetBodyParser,
SetHelmet,
SetCORS,
SetRateLimiter,
} from "@increase21/simplenodejs";
const app = CreateSimpleJsHttpServer({
controllersDir: process.cwd() + "/controllers",
});
app.use(SetCORS());
app.use(SetHelmet());
app.use(SetBodyParser({ limit: "2mb" }));
app.use(SetRateLimiter({ windowMs: 60_000, max: 100 }));
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});CreateSimpleJsHttpServer(options)
Creates and returns an HTTP app instance.
| Param | Type | Required | Description |
|------|------|----------|-------------|
| controllersDir | string | ✅ | Path to your controllers directory |
CreateSimpleJsHttpsServer(options)
Creates and returns an HTTPS app instance.
| Param | Type | Required | Description |
|------|------|----------|-------------|
| controllersDir | string | ✅ | Path to your controllers directory |
| tlsOpts | https.ServerOptions | ✅ | TLS options (key, cert, etc.) |
import fs from "fs";
import { CreateSimpleJsHttpsServer } from "@increase21/simplenodejs";
const app = CreateSimpleJsHttpsServer({
controllersDir: process.cwd() + "/controllers",
tlsOpts: {
key: fs.readFileSync("key.pem"),
cert: fs.readFileSync("cert.pem"),
},
});
app.listen(443);Controllers
Controllers are auto-loaded from controllersDir at startup. The file path maps directly to a URL. Controllers must be exported as the default export; otherwise, requests to that controller will return a 404 error.
controllers/
drivers/
auths.ts → /drivers/auths
customers/
auths.ts → /customers/auths
accounts → /customers/accountsControllers are plain classes — no base class required. Each method represents an endpoint and returns a SimpleJsEndpoint (an array of SimpleJsEndpointDescriptor objects) that declares which HTTP methods are supported and which handler to call.
Every controller receives the request context (SimpleJsCtx) via its constructor and it is also available as this.ctx anywhere inside the class — in endpoint methods, descriptor handlers, and private helpers.
// controllers/drivers/auths.ts
import { SimpleJsCtx, SimpleJsEndpoint } from "@increase21/simplenodejs";
export default class AuthController {
ctx: SimpleJsCtx;
// single HTTP method — access this.ctx directly, no descriptor needed
async login(): Promise<void> {
if (this.ctx.method !== "post") return this.ctx.res.status(405).json({ error: "Method Not Allowed" });
// other logic...
}
// multiple HTTP methods — return a SimpleJsEndpoint array
async vehicleList(id?: string): Promise<SimpleJsEndpoint> {
return [
{ method: "get", id: "optional", handler: getVehicles },
{ method: "put", id: "required", handler: updateVehicle },
{ method: "delete", id: "required", handler: deleteVehicle },
];
}
}
this.ctxis injected automatically by the router on every request. You do not need to pass it around manually — it is always available anywhere in the class.
Endpoint Naming
Controller methods use camelCase and are exposed as kebab-case URLs.
| Method name | URL |
|---|---|
| async index() | /drivers/auths |
| async login() | /drivers/auths/login |
| async vehicleList() | /drivers/auths/vehicle-list |
ID Parameters
Declare id in the endpoint method signature to indicate it accepts an ID segment. Use the descriptor's id field to enforce whether it is required or optional at the routing level.
// GET /drivers/auths/vehicle-list → id is optional
// GET /drivers/auths/vehicle-list/123 → id = "123"
// PUT /drivers/auths/vehicle-list/123 → required, 404 if missing
// DELETE /drivers/auths/vehicle-list/123 → required, 404 if missingSimpleJsCtx
The context object passed to every endpoint method and handler. Accepts an optional generic type T for customData.
| Property | Type | Description |
|---|---|---|
| req | RequestObject | Raw request object |
| res | ResponseObject | Raw response object |
| body | object | Parsed request body |
| query | object | Parsed query string |
| method | HttpMethod | HTTP method of the request ("get", "post", etc.) |
| customData | T (default any) | Data attached by plugins/middlewares via req._custom_data |
// Typed customData
const cookies = (ctx as SimpleJsCtx<{ cookies: Record<string, string> }>).customData.cookies;SimpleJsEndpoint
SimpleJsEndpoint is the return type for controller endpoint methods. It is equivalent to SimpleJsEndpointDescriptor[].
SimpleJsEndpointDescriptor
Each object in the SimpleJsEndpoint array describes one HTTP verb handler.
| Property | Type | Required | Description |
|---|---|---|---|
| method | HttpMethod | ✅ | HTTP verb: "get", "post", "put", "patch", "delete" |
| handler | (ctx, id?) => any | ✅ | Method reference to call for this HTTP verb |
| id | "required" \| "optional" | ❌ | ID routing rule. Omit if the endpoint never uses an ID |
| middleware | Middleware[] | ❌ | Array of middlewares to run before the handler |
RequestObject (req)
Extends Node's IncomingMessage with additional properties.
| Property | Type | Description |
|---|---|---|
| req.query | object | Parsed query string parameters |
| req.body | any | Parsed request body (set by SetBodyParser) |
| req.id | string | Auto-generated UUID for the request (also sent as X-Request-Id header) |
| req._custom_data | object | Shared data bag written by plugins (payload, cookies, etc.) |
ResponseObject (res)
Extends Node's ServerResponse with helper methods.
| Method | Params | Description |
|---|---|---|
| res.status(code) | number | Set HTTP status code, chainable |
| res.json(data) | object | Send a JSON response |
| res.text(data?) | string | Send a plain text response |
res.status(200).json({ success: true });
res.status(404).text("Not found");app.use(middleware)
Registers a middleware that runs on every request before controllers.
Middleware signature
(req: RequestObject, res: ResponseObject, next: () => Promise<void> | void) => Promise<any> | voidExample
app.use((req, res, next) => {
console.log(req.method, req.url);
next();
});app.useError(errorMiddleware)
Registers a global error handler. Catches all errors thrown in middlewares, controllers, and async handlers.
app.useError((err, req, res, next) => {
const status = err?.statusCode || 500;
res.status(status).json({ error: err.message });
});app.registerPlugin(plugin)
Registers a plugin function.
type Plugin = (app: SimpleJsServer, opts?: any) => Promise<any> | void;app.registerPlugin(app => SimpleJsSecurityPlugin(app, opt));Built-in Middlewares
SetBodyParser(options)
Parses the request body. Must be registered before controllers access ctx.body.
| Param | Type | Description |
|---|---|---|
| limit | string \| number | Max body size (e.g. "2mb", "500kb", or bytes as number). Default: "1mb" |
| ignoreStream | {url:string, method:string, type: exact or prefex}[] \| (req) => boolean | Skip stream reading and pass the raw stream to the handler for matching requests. Accepts a list of path prefixes and their http menthods or a predicate function. The type field determines whether the URL should be matched exactly (exact) or as a prefix (prefix) |
app.use(SetBodyParser({ limit: "2mb" }));ignoreStream — file upload / raw stream endpoints
For context where you need direct stream access (e.g. passing the request to a library like formidable), use ignoreStream:
// Path-prefix list — skip body parsing for any URL under /upload
app.use(SetBodyParser({
limit: "10mb",
ignoreStream: [
{url:"/files/", method:"post", type:"prefix"},
{url:"/files/profile-picture", method:"post", type:"exact"}
]
}));
// Predicate function — full control over which requests are skipped
app.use(SetBodyParser({
limit: "10mb",
ignoreStream: (req) => req.url.startsWith("/upload"),
}));When a request is ignored, next() is called immediately with the stream untouched. Your handler is then responsible for consuming it:
SetCORS(options?)
Sets Access-Control-* headers and handles OPTIONS preflight.
| Param | Type | Default | Description |
|---|---|---|---|
| origin | string | "*" | Allowed origin |
| methods | string | "GET, POST, DELETE, PUT, PATCH" | Allowed methods |
| headers | string | standard set | Allowed headers |
| credentials | boolean | false | Allow cookies/auth headers. Requires origin to be set to a specific domain |
// Public API
app.use(SetCORS());
// Credentialed (cookies, Authorization header)
app.use(SetCORS({ origin: "https://myapp.com", credentials: true }));SetHelmet(options?)
Sets all security response headers in one call. Each header can be individually overridden or disabled.
| Option | Header | Default |
|---|---|---|
| hsts | Strict-Transport-Security | max-age=31536000; includeSubDomains |
| csp | Content-Security-Policy | default-src 'none' |
| frameGuard | X-Frame-Options | DENY |
| noSniff | X-Content-Type-Options | nosniff |
| referrerPolicy | Referrer-Policy | no-referrer |
| permissionsPolicy | Permissions-Policy | all features blocked |
| coep | Cross-Origin-Embedder-Policy | require-corp |
| coop | Cross-Origin-Opener-Policy | same-origin |
Pass false to disable any individual header. Pass a string to override the value.
// All defaults
app.use(SetHelmet());
// HTTP server — disable HSTS, relax CSP
app.use(SetHelmet({
hsts: false,
csp: "default-src 'self'",
coep: false,
}));Individual Security Headers
Each header is also available as a standalone middleware:
| Function | Header |
|---|---|
| SetHSTS(opts?) | Strict-Transport-Security |
| SetCSP(policy?) | Content-Security-Policy |
| SetFrameGuard(action?) | X-Frame-Options |
| SetNoSniff() | X-Content-Type-Options |
| SetReferrerPolicy(policy?) | Referrer-Policy |
| SetPermissionsPolicy(policy?) | Permissions-Policy |
| SetCOEP(value?) | Cross-Origin-Embedder-Policy |
| SetCOOP(value?) | Cross-Origin-Opener-Policy |
app.use(SetFrameGuard("SAMEORIGIN"));
app.use(SetCSP("default-src 'self'; img-src *"));
app.use(SetHSTS({ maxAge: 63072000, preload: true }));
SetHSTSis only meaningful on HTTPS. Browsers silently ignore it over plain HTTP.
SetRateLimiter(options)
Limits repeated requests per client IP using an in-memory store.
| Param | Type | Required | Description |
|---|---|---|---|
| windowMs | number | ✅ | Time window in milliseconds |
| max | number | ✅ | Max requests per window |
| trustProxy | boolean | ❌ | If true, reads IP from X-Forwarded-For (for Nginx/load balancers). Default: false |
| keyGenerator | (req) => string | ❌ | Custom key function (e.g. by user ID instead of IP) |
app.use(SetRateLimiter({ windowMs: 60_000, max: 100 }));
// Behind Nginx
app.use(SetRateLimiter({ windowMs: 60_000, max: 100, trustProxy: true }));The store is in-memory and per-process. In clustered/multi-worker deployments each worker maintains its own counter. Use a custom
keyGeneratorwith an external store for distributed rate limiting.
Plugins
SimpleJsSecurityPlugin
Convenience plugin combining CORS, Helmet, and rate limiting.
import { SimpleJsSecurityPlugin } from "@increase21/simplenodejs";
app.registerPlugin(app => SimpleJsSecurityPlugin(app, {
cors: { origin: "https://myapp.com", credentials: true },
helmet: { hsts: false },
rateLimit: { windowMs: 60_000, max: 200 },
}));SimpleJsCookiePlugin + SignCookie
Parses the Cookie header on every request. Cookies are available at this._custom_data.cookies.
If a secret is provided, signed cookies (prefixed with s:) are verified using HMAC-SHA256. Cookies with invalid signatures are silently dropped.
| Option | Type | Description |
|---|---|---|
| secret | string | Optional signing secret for verified cookies |
| dataKey | string | Key on _custom_data. Default: "cookies" |
import { SimpleJsCookiePlugin, SignCookie } from "@increase21/simplenodejs";
// Register plugin
app.registerPlugin(app => SimpleJsCookiePlugin(app, {
secret: process.env.COOKIE_SECRET,
}));
// Set a signed cookie in a handler
const signed = SignCookie(sessionId, process.env.COOKIE_SECRET!);
ctx.res.setHeader("Set-Cookie", `session=${signed}; HttpOnly; Secure; SameSite=Strict`);
// Read cookie in any handler
const { session } = ctx.customData.cookies;SimpleJsIPWhitelistPlugin
Allows or blocks requests by client IP address.
| Option | Type | Description |
|---|---|---|
| ips | string[] | List of IP addresses |
| mode | "allow" \| "deny" | "allow" = whitelist (only listed IPs pass). "deny" = blacklist (listed IPs are blocked). Default: "allow" |
| trustProxy | boolean | Read IP from X-Forwarded-For. Default: false |
import { SimpleJsIPWhitelistPlugin } from "@increase21/simplenodejs";
// Only allow specific IPs (whitelist)
app.registerPlugin(app => SimpleJsIPWhitelistPlugin(app, {
ips: ["203.0.113.10", "198.51.100.5"],
mode: "allow",
}));
// Block known bad IPs (blacklist)
app.registerPlugin(app => SimpleJsIPWhitelistPlugin(app, {
ips: ["203.0.113.99"],
mode: "deny",
trustProxy: true,
}));SimpleJsRequestLoggerPlugin
Logs every completed request with method, URL, status code, and duration.
| Option | Type | Description |
|---|---|---|
| logger | (msg: string) => void | Custom log function. Default: console.log |
| format | "simple" \| "json" | Log format. Default: "simple" |
import { SimpleJsRequestLoggerPlugin } from "@increase21/simplenodejs";
// Simple text logs
app.registerPlugin(app => SimpleJsRequestLoggerPlugin(app));
// → [2025-01-01T00:00:00.000Z] GET /users/auth/login 200 12ms
// JSON logs (for log aggregators)
app.registerPlugin(app => SimpleJsRequestLoggerPlugin(app, {
format: "json",
logger: (msg) => process.stdout.write(msg + "\n"),
}));
// → {"time":"...","method":"GET","url":"/users/auth/login","status":200,"ms":12,"id":"uuid"}SimpleJsTimeoutPlugin
Automatically closes requests that exceed the configured time limit with 503.
| Option | Type | Description |
|---|---|---|
| ms | number | Timeout in milliseconds |
| message | string | Custom timeout message. Default: "Request timeout" |
import { SimpleJsTimeoutPlugin } from "@increase21/simplenodejs";
app.registerPlugin(app => SimpleJsTimeoutPlugin(app, { ms: 10_000 }));SimpleJsCachePlugin
Sets Cache-Control response headers globally.
| Option | Type | Description |
|---|---|---|
| maxAge | number | Max age in seconds |
| private | boolean | Mark as private (user-specific, not shared caches) |
| noStore | boolean | Disable all caching entirely |
import { SimpleJsCachePlugin } from "@increase21/simplenodejs";
// Public cache for 5 minutes
app.registerPlugin(app => SimpleJsCachePlugin(app, { maxAge: 300 }));
// No caching (APIs with sensitive data)
app.registerPlugin(app => SimpleJsCachePlugin(app, { noStore: true }));SimpleJsMaintenanceModePlugin
Returns 503 for all traffic when maintenance mode is on. Specific IPs (e.g. your office or CI server) can bypass.
| Option | Type | Description |
|---|---|---|
| enabled | boolean | Toggle maintenance mode |
| message | string | Custom response message |
| allowIPs | string[] | IPs that bypass maintenance mode |
| trustProxy | boolean | Read IP from X-Forwarded-For. Default: false |
import { SimpleJsMaintenanceModePlugin } from "@increase21/simplenodejs";
app.registerPlugin(app => SimpleJsMaintenanceModePlugin(app, {
enabled: process.env.MAINTENANCE === "true",
message: "We are upgrading. Back soon.",
allowIPs: ["203.0.113.10"],
}));Security Best Practices
- Always register
SetHelmet()or individual header middlewares - Use
SetRateLimiteron all public endpoints - Enable
credentials: trueinSetCORSonly with a specificorigin— never with a wildcard - Only set
trustProxy: trueonSetRateLimiterorSimpleJsIPWhitelistPluginwhen running behind a trusted reverse proxy (Nginx, etc.) - Register
SetBodyParserwith a reasonablelimitto prevent oversized payloads - Use
app.useErrorto handle errors uniformly — unhandled errors return"Service unavailable"with no internal details exposed - Add
HttpOnly; Secure; SameSite=Strictattributes when setting cookies viaSet-Cookie - On HTTPS deployments, register
SetHSTS()or include it inSetHelmet()
License
MIT
Author
Increase
