@gamiumgamers/packetron
v1.0.21
Published
A simple to use clustered backend framework designed to utilize multiprocessing to improve throughput for nodejs servers while minimizing overhead.
Readme
Packetron
A lean, high-throughput Node.js web framework built directly on the http module. Packetron's design centers on three ideas:
- Multiprocessing first. A primary process forks workers via Node's
clustermodule and lets the OS round-robin connections across them, giving near-linear scaling with CPU count. - Bitwise flags everywhere. Configuration is encoded as integer bit fields rather than option objects, so every per-request control-flow decision is a single
&operation. - Pay-for-what-you-use. Optional features (rate limiting, body size caps, multipart parsing, custom file storage) cost effectively zero when unused — they're guarded by a single truthy check that the branch predictor pins as never-taken.
Table of Contents
- Installation
- Quick Start
- Server:
new Packetron(options) - Routing:
handle() - Static Files:
serve()/serveFile()/unserve() - Request API (
PacketronRequest) - Response API (
PacketronResponse) - Body Parsing Modes
- Multipart / File Uploads
- Rate Limiting & Request Size Limits
- Plugin System
- Built-in Plugins
- Global Data (Cross-Worker State)
- Flag Reference
- HTTP Response Codes
- Caveats & Known Limitations
Installation
npm install @gamiumgamers/packetronPacketron's only runtime dependencies are busboy, bson, and fast-xml-parser. Clustering uses Node's built-in cluster module — no package required. Node 18+ recommended (uses ?? and private class fields).
Benchmarks
A reproducible benchmark suite (Packetron vs. plain Node http vs. Fastify, single-process) lives in benchmark/. The latest results are committed to benchmark/results/BENCHMARKS.md. Run it yourself with:
npm run benchmarkThe comparison runs single-process (cluster disabled) for fairness; Packetron's design goal is to scale that per-process work across CPU cores via its built-in clustering.
Quick Start
const { Packetron, PacketronServerFlags, PacketronRequestMethod, PacketronHandlerFlags } = require("@gamiumgamers/packetron");
const server = new Packetron({
port: 3000,
maxThreads: 4,
flags: PacketronServerFlags.START_IMMEDIATELY
});
server.handle({
method: PacketronRequestMethod.GET,
routePath: "api/status",
handler: (request, response) => {
response.sendJson({ status: "ok" });
}
});
server.handle({
method: PacketronRequestMethod.POST,
routePath: "api/echo",
flags: PacketronHandlerFlags.JSON_BODY,
handler: async (request, response) => {
const body = await request.getBody();
response.sendJson({ received: body });
}
});Server: new Packetron(options)
| Option | Type | Default | Description |
|---|---|---|---|
| port | number | 3000 | TCP port to bind. |
| host | string | "0.0.0.0" | Bind address. |
| maxThreads | number | os.cpus().length | Worker process count (1 = single-process mode, no fork). |
| flags | number | 0 | Bitwise PacketronServerFlags. |
| plugins | PacketronPlugin[] | [] | Global plugins applied to every request. |
PacketronServerFlags
| Flag | Effect |
|---|---|
| START_IMMEDIATELY | Call .start() from the constructor. |
| STOP_ON_ERROR | Stop the server on any logged error. Use with care — a single bad request can take a worker down. |
| TRY_NEXT_PORT | If the port is in use, increment and retry. |
| SILENCE_INTERNAL_LOGS | Suppress Packetron's own log() output. |
| NO_GLOBAL_DATA | Disable cross-worker global data file. |
| RAISE_PROCESS_PRIORITY | Bump the OS process priority on worker start. |
| SKIP_FIREWALL | Skip the best-effort firewall rule the server adds on start (useful in containers, behind a managed firewall, or for local benchmarking). |
Lifecycle
await server.start(); // forks workers if maxThreads > 1
server.stop(); // workers ask the primary to broadcast shutdownOn Windows, start() attempts to add a firewall rule for the port (requires UAC the first time). On Linux it tries iptables/ufw with sudo. Both are best-effort and non-fatal.
Routing: handle()
server.handle({
method, // PacketronRequestMethod.*
routePath, // exact path string, no leading slash needed
handler, // (request, response) => any | Promise<any>
plugins, // PacketronPlugin[] (route-local)
flags, // PacketronHandlerFlags bitfield
multipartOptions, // see Multipart section
maxRequestsPerSecond, // optional throttle (per worker)
maxRequestSizeInBytes // optional payload cap (Content-Length + on-stream)
});Returns a PacketronRoute that can be removed later with .remove().
Routes are matched by exact string equality on the normalized path. There is currently no support for path parameters (:id), wildcards, or regex routes.
PacketronRequestMethod
GET POST PUT DELETE HEAD OPTIONS — integer constants used internally as array indices. PacketronRequestMethod.toName(method) returns the canonical uppercase method name (used when serializing methods into headers).
PacketronHandlerFlags
| Flag | Body becomes... |
|---|---|
| JSON_BODY | parsed JSON (or null on parse error) |
| FORM_DATA | flat object of fields from application/x-www-form-urlencoded or multipart/form-data (fields only — files are drained and discarded) |
| MULTIPART_FORM_DATA | full multipart parsing — fields in getBody(), files in getFiles() |
| FILE_UPLOAD | legacy file-only mode — getFiles() returns { fieldname: pathString }; getBody() returns "" |
| XML_BODY | parsed XML via fast-xml-parser |
| PLAIN_TEXT_BODY | raw string |
| DONT_USE_BODY | skip body parsing entirely |
| LOCAL_PLUGINS_ONLY | bypass global plugins for this route (reserved — partial wiring) |
Body parsing is chosen at registration time, not dispatched by Content-Type. A route flagged JSON_BODY will not auto-fall-back to text if the client sends something else.
Static Files: serve() / serveFile() / unserve()
server.serve({
directory: path.join(__dirname, "public"),
pathPrefix: "static",
flags: PacketronFileRouterFlags.NON_RECURSIVE,
plugins: [],
maxRequestsPerSecond: 1000,
maxRequestSizeInBytes: null
});
server.serveFile({
routePath: "robots.txt",
filePath: "./robots.txt"
});
server.unserve({ directory: "./public", pathPrefix: "static" });Each file becomes its own endpoint. Per-endpoint rate limits and size caps apply per-file when configured at the serve() / serveFile() level.
PacketronFileRouterFlags
| Flag | Effect |
|---|---|
| NON_RECURSIVE | serve() skips subdirectories. |
| NO_DIRECTORY_STRUCTURE | Flatten — files are reachable by basename under pathPrefix, ignoring nested folders. |
| USER_SPECIFIED_FILE | Set internally by serveFile(). |
| LOCAL_PLUGINS_ONLY | Bypass global plugins for static routes. |
| SILENT | Suppress the per-file Added … / Removed … log lines. serve() and unserve() print a single summary count instead. Use this for any sizeable static directory — one line per file otherwise buries every other startup log. |
MIME types are inferred from the file extension via a built-in map in PacketronConstants._mimeTypes. Unknown extensions fall back to application/octet-stream.
Request API (PacketronRequest)
Extends Node's IncomingMessage. All public accessors are async.
| Method | Returns | Notes |
|---|---|---|
| getBody() | parsed body per flag, or null if request has no body | Resolves once parsing completes. |
| getFiles() | Object<fieldname, fileRef> | Shape depends on flag (see Multipart section). |
| getQueryParams() | Object<string, string> | Parsed from URL, cached on first call. |
| getCookies() | Object<string, string> | Parsed from Cookie header, cached. |
| getCookie(name) | string | null | Convenience accessor over getCookies(). |
| getIp() | string | Reads X-Forwarded-For then falls back to socket.remoteAddress. |
| hasBody() | boolean | Based on Content-Length / Transfer-Encoding. |
Response API (PacketronResponse)
Extends Node's ServerResponse.
response.sendJson(obj); // sets Content-Type and ends
response.sendHtml("<h1>Hi</h1>");
response.sendTextPlain("hello");
response.send(payload); // auto-deduces JSON for objects
response.sendOk(); // 200, empty
response.sendStatusCode(404); // empty body
response.sendFile(filePath); // streams with MIME from extension
response.downloadFile(filePath); // sets Content-Disposition: attachment
response.setCookie("session", "abc", {
maxAge: 3600,
path: "/",
httpOnly: true,
secure: true,
sameSite: "lax"
});
response.clearCookie("session", { path: "/" });Body Parsing Modes
All body parsing happens via streams during the request lifetime. The chosen parser populates internal state, then getBody() resolves.
JSON_BODY
server.handle({
method: PacketronRequestMethod.POST,
routePath: "api/users",
flags: PacketronHandlerFlags.JSON_BODY,
handler: async (req, res) => {
const body = await req.getBody();
if (body === null) return res.sendStatusCode(400);
res.sendJson(body);
}
});Invalid JSON sets the body to null and logs to stderr — there is no auto-400.
FORM_DATA
Captures fields from application/x-www-form-urlencoded and multipart/form-data. File parts are drained and discarded so the request still completes — use MULTIPART_FORM_DATA if you need the files.
XML_BODY, PLAIN_TEXT_BODY
Accumulate the body in memory and either XML-parse or return the raw string.
Warning:
JSON_BODY,XML_BODY, andPLAIN_TEXT_BODYhave no built-in size cap. Pair them withmaxRequestSizeInBytes(see Rate Limiting & Request Size Limits) on any externally exposed route.
Multipart / File Uploads
MULTIPART_FORM_DATA is the recommended way to handle file uploads in modern code. It captures both fields and files in one pass, streams files to disk with backpressure, and supports configurable limits + a streaming hook for handing files off to remote storage.
server.handle({
method: PacketronRequestMethod.POST,
routePath: "upload",
flags: PacketronHandlerFlags.MULTIPART_FORM_DATA,
multipartOptions: {
maxFileSize: 5 * 1024 * 1024 * 1024, // 5 GB per file
maxFiles: 20,
maxFields: 50,
maxFieldSize: 1024 * 1024, // per-field byte cap
maxFieldNameSize: 100,
maxParts: Infinity,
maxHeaderPairs: 2000,
fileStorageDirectory: "/data/uploads" // optional override
},
handler: async (req, res) => {
const fields = await req.getBody();
const files = await req.getFiles();
res.sendJson({ fields, files });
}
});File entry shape (under MULTIPART_FORM_DATA)
{
path: "/tmp/.packetroninternal/files/avatar_a1b2c3d4_1709999999999.png",
filename: "avatar.png",
encoding: "7bit",
mimeType: "image/png",
size: 38421,
truncated: false
}If multiple files share a fieldname, the value becomes an array. Same rule applies to repeated form fields under getBody().
Streaming hook (skip disk storage)
Provide onFile to take over the stream — useful for piping directly to S3, GCS, or running a hashing transform:
multipartOptions: {
onFile: async (fieldname, fileStream, info) => {
const uploadResult = await uploadToS3(fileStream, info.filename);
return { key: uploadResult.Key, etag: uploadResult.ETag };
// Whatever you return becomes the entry under getFiles()[fieldname].
}
}Limit events
When a configured limit is breached mid-upload, busboy-equivalent events fire on the request:
| Event | Args |
|---|---|
| "multipart-file-limit" | (fieldname, info) |
| "multipart-files-limit" | none |
| "multipart-fields-limit" | none |
| "multipart-parts-limit" | none |
Files that hit maxFileSize are unlinked automatically.
Legacy FILE_UPLOAD flag
Still supported for backwards compatibility. Behavior:
getFiles()returns{ fieldname: "/abs/path/to/file" }(plain string paths).getBody()returns""— fields are not captured.- Filenames keep more of the original (use
MULTIPART_FORM_DATAfor crypto-random sanitized names).
Use MULTIPART_FORM_DATA for new code.
Rate Limiting & Request Size Limits
Both knobs are exposed on handle(), serve(), and serveFile():
server.handle({
method: PacketronRequestMethod.POST,
routePath: "api/upload",
flags: PacketronHandlerFlags.MULTIPART_FORM_DATA,
maxRequestsPerSecond: 100,
maxRequestSizeInBytes: 50 * 1024 * 1024,
handler: async (req, res) => { /* ... */ }
});maxRequestsPerSecond
- Fixed 1-second window per endpoint.
- Per-worker counter — under clustering, the effective cap is
workers × maxRequestsPerSecond. Cross-worker coordination would require IPC on every request and defeat the framework's perf goals. - On breach: responds
429 Too Many RequestswithRetry-After: 1. - Overhead when unused: zero (single null check, branch-predicted away).
- Overhead when used: one
Date.now(), one integer compare, one increment per request (~80–120 ns).
maxRequestSizeInBytes
- Reads
Content-Lengthfirst — if present and over budget, responds413 Payload Too Largebefore reading a single byte of the body. - If
Content-Lengthis absent (chunked transfer-encoding), attaches a singledatalistener that counts bytes and destroys the request on overflow. - Overhead when unused: zero.
- Overhead when used with
Content-Length: ~50 ns. With chunked: one add + compare per chunk.
Both limits can be combined, and either can be set independently. Pass null (the default) to disable.
Plugin System
Plugins are reusable async functions with a numeric priority. A plugin returning true short-circuits — neither subsequent plugins nor the route handler will run.
const { PacketronPlugin, PacketronPluginPriority } = require("@gamiumgamers/packetron");
const authPlugin = PacketronPlugin.create({
priority: PacketronPluginPriority.HIGH,
handler: async (request, response) => {
const token = await request.getCookie("session");
if (!token) {
response.sendStatusCode(401);
return true; // short-circuit
}
}
});Attach globally:
const server = new Packetron({ port: 3000, plugins: [authPlugin] });
// or:
server.insertGlobalPlugin(authPlugin);Or per-route:
server.handle({
routePath: "api/secret",
plugins: [authPlugin],
handler: (req, res) => res.sendJson({ ok: true })
});PacketronPluginPriority
EXTEMELY_LOW (0) ... MEDIUM (4) ... EXTEMELY_HIGH (8). Higher priorities run first within their scope. Note the spelling — EXTEMELY is preserved for API stability.
A single plugin instance can be registered with multiple managers; plugin.setPriority(p) re-sorts every manager it's attached to. plugin.remove(manager) detaches from a specific manager, or from all if called with no arg.
Built-in Plugins
Two hardening plugins ship with Packetron. Both are PacketronPlugin subclasses built via a create() factory, so they slot into the same plugins: [...] / insertGlobalPlugin() paths as any custom plugin. Both default to PacketronPluginPriority.VERY_HIGH so they run before route handlers. Configuration is by bitwise flags and value enums — no string options.
CORS — PacketronCorsPlugin
const { Packetron, PacketronCorsPlugin, PacketronCorsFlags, PacketronRequestMethod } = require("@gamiumgamers/packetron");
const cors = PacketronCorsPlugin.create({
flags: PacketronCorsFlags.ALLOW_ALL_ORIGINS | PacketronCorsFlags.HANDLE_PREFLIGHT,
allowedMethods: [PacketronRequestMethod.GET, PacketronRequestMethod.POST, PacketronRequestMethod.OPTIONS],
allowedHeaders: ["Content-Type", "Authorization"],
exposedHeaders: ["X-Request-Id"],
maxAgeInSeconds: 86400
});
const server = new Packetron({ port: 3000, plugins: [cors] });| Option | Type | Default | Description |
|---|---|---|---|
| flags | number | ALLOW_ALL_ORIGINS \| HANDLE_PREFLIGHT | Bitwise PacketronCorsFlags. |
| allowedOrigins | string[] | [] | Explicit origin allowlist (ignored when ALLOW_ALL_ORIGINS is set). |
| allowedMethods | PacketronRequestMethod[] | all methods | Methods advertised on preflight. |
| allowedHeaders | string[] | ["Content-Type", "Authorization"] | Request headers advertised on preflight (ignored when ALLOW_ALL_HEADERS is set). |
| exposedHeaders | string[] | [] | Response headers exposed to the browser. |
| maxAgeInSeconds | number|null | null | Preflight cache lifetime (null omits the header). |
| priority | number | VERY_HIGH | PacketronPluginPriority. |
PacketronCorsFlags
| Flag | Effect |
|---|---|
| ALLOW_ALL_ORIGINS | Emit * (or reflect the request origin when ALLOW_CREDENTIALS is also set). |
| ALLOW_CREDENTIALS | Send Access-Control-Allow-Credentials: true; forces origin reflection instead of *. |
| ALLOW_ALL_HEADERS | Echo the browser's Access-Control-Request-Headers on preflight. |
| HANDLE_PREFLIGHT | Answer OPTIONS preflight requests directly with 204 No Content. |
When the response origin is reflected (rather than *), a Vary: Origin header is added automatically so caches key correctly.
Security Headers — PacketronSecurityHeadersPlugin
const {
PacketronSecurityHeadersPlugin,
PacketronSecurityHeadersFlags,
PacketronFrameOptions,
PacketronReferrerPolicy
} = require("@gamiumgamers/packetron");
const security = PacketronSecurityHeadersPlugin.create({
flags: PacketronSecurityHeadersFlags.CONTENT_TYPE_NO_SNIFF
| PacketronSecurityHeadersFlags.FRAME_OPTIONS
| PacketronSecurityHeadersFlags.STRICT_TRANSPORT_SECURITY
| PacketronSecurityHeadersFlags.HSTS_INCLUDE_SUBDOMAINS
| PacketronSecurityHeadersFlags.REFERRER_POLICY
| PacketronSecurityHeadersFlags.CONTENT_SECURITY_POLICY,
frameOptions: PacketronFrameOptions.DENY,
referrerPolicy: PacketronReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
hstsMaxAgeInSeconds: 63072000,
contentSecurityPolicy: "default-src 'self'"
});
server.insertGlobalPlugin(security);Each header is emitted only when its flag is set. Header value strings are precomputed once at construction, so the per-request cost is just flag tests and setHeader calls.
PacketronSecurityHeadersFlags
| Flag | Header |
|---|---|
| CONTENT_TYPE_NO_SNIFF | X-Content-Type-Options: nosniff |
| FRAME_OPTIONS | X-Frame-Options (value from PacketronFrameOptions) |
| STRICT_TRANSPORT_SECURITY | Strict-Transport-Security |
| HSTS_INCLUDE_SUBDOMAINS | Append ; includeSubDomains to HSTS |
| HSTS_PRELOAD | Append ; preload to HSTS |
| REFERRER_POLICY | Referrer-Policy (value from PacketronReferrerPolicy) |
| CONTENT_SECURITY_POLICY | Content-Security-Policy (free-form directive string) |
| PERMISSIONS_POLICY | Permissions-Policy (free-form directive string) |
| CROSS_ORIGIN_OPENER_POLICY | Cross-Origin-Opener-Policy (value from PacketronCrossOriginOpenerPolicy) |
| CROSS_ORIGIN_RESOURCE_POLICY | Cross-Origin-Resource-Policy (value from PacketronCrossOriginResourcePolicy) |
| CROSS_ORIGIN_EMBEDDER_POLICY | Cross-Origin-Embedder-Policy (value from PacketronCrossOriginEmbedderPolicy) |
| REMOVE_SERVER_HEADER | Strip the Server header |
| REMOVE_X_POWERED_BY | Strip the X-Powered-By header |
Value enums
Fixed-vocabulary header values are enums, not strings:
PacketronFrameOptions—DENY,SAME_ORIGINPacketronReferrerPolicy—NO_REFERRER,NO_REFERRER_WHEN_DOWNGRADE,ORIGIN,ORIGIN_WHEN_CROSS_ORIGIN,SAME_ORIGIN,STRICT_ORIGIN,STRICT_ORIGIN_WHEN_CROSS_ORIGIN,UNSAFE_URLPacketronCrossOriginOpenerPolicy—UNSAFE_NONE,SAME_ORIGIN_ALLOW_POPUPS,SAME_ORIGINPacketronCrossOriginResourcePolicy—SAME_SITE,SAME_ORIGIN,CROSS_ORIGINPacketronCrossOriginEmbedderPolicy—UNSAFE_NONE,REQUIRE_CORP,CREDENTIALLESS
Path Parameters — PacketronPathParameterPlugin
The core router matches paths by exact string equality and has no concept of parameters. This opt-in plugin is a companion router for parameterized routes — applications that don't need them never add it and pay nothing: the exact-match hot path is untouched and no per-request string work is done on their behalf.
Register patterns with handle(). A :name segment captures one path segment; a *name segment captures the rest of the path. Matched values arrive as a third handler argument and are also attached as request.pathParameters. A non-matching request returns control to the core router (and then the static file router), so parameterized and exact routes coexist.
const { Packetron, PacketronPathParameterPlugin, PacketronPathParameterFlags, PacketronRequestMethod } = require("@gamiumgamers/packetron");
const router = PacketronPathParameterPlugin.create({
flags: PacketronPathParameterFlags.DECODE_PARAMETERS
});
router.handle({
method: PacketronRequestMethod.GET,
routePath: "users/:id",
handler: (request, response, parameters) => {
response.sendJson({ id: parameters.id }); // also: request.pathParameters.id
}
});
router.handle({
method: PacketronRequestMethod.GET,
routePath: "users/:id/posts/:postId",
handler: (request, response, parameters) => response.sendJson(parameters)
});
router.handle({
method: PacketronRequestMethod.GET,
routePath: "files/*path", // wildcard captures the remainder
handler: (request, response, parameters) => response.sendTextPlain(parameters.path)
});
const server = new Packetron({ port: 3000, plugins: [router] });Matching is a per-method segment trie. Within a segment the most specific edge wins — a literal beats a :parameter, which beats a *wildcard — and the matcher backtracks, so /users/me and /users/:id can both be registered and the static one takes priority. Patterns are keyed by PacketronRequestMethod, so GET and POST registrations are independent.
| Option | Type | Default | Description |
|---|---|---|---|
| flags | number | DECODE_PARAMETERS | Bitwise PacketronPathParameterFlags. |
| priority | number | HIGH | PacketronPluginPriority (runs after VERY_HIGH plugins like CORS/security). |
handle() options: method (PacketronRequestMethod, default GET), routePath (the pattern), handler ((request, response, parameters) => any).
PacketronPathParameterFlags
| Flag | Effect |
|---|---|
| DECODE_PARAMETERS | decodeURIComponent each captured value (malformed values pass through unchanged). |
| CASE_INSENSITIVE | Match literal segments case-insensitively. |
| IGNORE_TRAILING_SLASH | Treat a trailing slash as optional on both patterns and requests. |
Internally, each pattern segment is classified by PacketronPathSegmentType (LITERAL, PARAMETER, WILDCARD).
Global Data (Cross-Worker State)
server.setGlobalData("counter", 42);
const value = server.getGlobalData("counter");Backed by a BSON file under the OS temp directory, shared across all workers and any other Packetron servers on the same machine.
A missing key reads back as null, and so does a store that does not exist yet — reading is always safe. A stored 0, false or "" reads back unchanged.
Caveat: the read/write is synchronous and unlocked. Concurrent writers can lose updates. Treat it as a low-frequency configuration store, not a counter or queue. Use a real KV store (Redis, etc.) for hot state.
Pass PacketronServerFlags.NO_GLOBAL_DATA to opt a server out of global data. Because the store is shared machine-wide, opting out only stops this server from creating or seeding it — it never deletes a store other running servers may be using.
Flag Reference
PacketronServerFlags
START_IMMEDIATELY = 1 << 0
STOP_ON_ERROR = 1 << 1
TRY_NEXT_PORT = 1 << 2
SILENCE_INTERNAL_LOGS = 1 << 3
NO_GLOBAL_DATA = 1 << 4
RAISE_PROCESS_PRIORITY = 1 << 5
SKIP_FIREWALL = 1 << 6PacketronHandlerFlags
JSON_BODY = 1 << 0
DONT_USE_BODY = 1 << 1
FILE_UPLOAD = 1 << 2
LOCAL_PLUGINS_ONLY = 1 << 3
FORM_DATA = 1 << 4
XML_BODY = 1 << 5
PLAIN_TEXT_BODY = 1 << 6
MULTIPART_FORM_DATA = 1 << 7PacketronFileRouterFlags
NON_RECURSIVE = 1 << 0
NO_DIRECTORY_STRUCTURE = 1 << 1
USER_SPECIFIED_FILE = 1 << 2
LOCAL_PLUGINS_ONLY = 1 << 3
SILENT = 1 << 4Combine with |:
flags: PacketronHandlerFlags.JSON_BODY | PacketronHandlerFlags.LOCAL_PLUGINS_ONLYHTTP Response Codes
HttpResponseCode exports named constants for the standard 1xx–5xx codes. Use them with response.sendStatusCode(...):
const { HttpResponseCode } = require("@gamiumgamers/packetron");
response.sendStatusCode(HttpResponseCode.UNAUTHORIZED); // 401Common ones: OK (200), CREATED (201), NO_CONTENT (204), BAD_REQUEST (400), UNAUTHORIZED (401), FORBIDDEN (403), NOT_FOUND (404), PAYLOAD_TOO_LARGE (413), TOO_MANY_REQUESTS (429), INTERNAL_SERVER_ERROR (500).
Caveats
Honest disclosure — things to know before deploying:
- The core router is exact-match only. Path parameters (
/users/:id) are available via the opt-inPacketronPathParameterPlugin(see Built-in Plugins); the core router itself still does exact string equality and there is no regex routing. - No
Content-Typedispatch. The body parser is chosen at registration via a flag; the client cannot influence it. - CORS and security headers ship as built-in plugins (
PacketronCorsPlugin,PacketronSecurityHeadersPlugin— see Built-in Plugins). CSRF is still left to the application. JSON_BODY/XML_BODY/PLAIN_TEXT_BODYbuffer the entire body in memory. Always pair them withmaxRequestSizeInByteson externally exposed routes.- Rate limits are per-worker. With 4 workers and
maxRequestsPerSecond: 100, the cluster will accept up to 400 req/s for that endpoint. - Global data API is sync + unlocked. Don't use it as a hot-path data structure.
STOP_ON_ERRORis dangerous. A single logged error will kill the worker; the primary will respawn but in-flight requests are lost. Leave it off for production.
License
ISC — see package.json.
