Troubleshooting & Common Issues (40 Problems & Solutions) β
A comprehensive, production-tested diagnostic guide to 40 real-world errors, edge cases, and solutions when developing and deploying with Velociradix.
π 1. Native C++ Addon Not Found (velociradix.node) β
Symptom β
Error: velociradix native addon not found. Run `npm rebuild velociradix`...Root Cause β
Velociradix relies on a C++17 native Node-API addon (bin/velociradix.node). Occurs if npm install ran with --ignore-scripts or inside a container without build tools.
Solution β
npm rebuild velociradix
# Or compile from source:
make clean && make NODE_INC=$(node -e 'console.log(require("path").join(process.execPath, "../../include/node"))') addonπ 2. Port Binding Failed (velociradix: bind() failed - Port 3000 is already in use) β
Symptom β
Error: velociradix: bind() failed - Port 3000 is already in useRoot Cause β
Another process or Velociradix instance is currently listening on port 3000. Velociradix checks port availability synchronously during app.listen().
Solution β
Identify and kill the process using port 3000:
lsof -i :3000
kill -9 <PID>Or specify a dynamic/free port in app.listen(0).
π 3. morgan / Express Loggers Printing Empty Fields (status or response-time -) β
Symptom β
morgan('dev') logs GET /api - - ms - - with empty status code and timing, or logs the previous requestβs URL on the next request.
Root Cause β
morganneeds Expressres(headersSent,on('finish')). Nativeapp.use(fn)is(ctx, next)β that is not enough.- Older
velociradix/expressuseExpressRoutercalledapp.useExpress(router)(ctx, next)even thoughuseExpressreturnsapp, so prefixeduse('/api', morgan)never ran the bridge. - Mount stripping defined own
req.path/req.urlon the pooled Request and left them there β the next request kept a stale path.
Solution β
Use the Express bridge (same path Velociradix core uses):
import { createApp } from "velociradix";
import morgan from "morgan";
const app = createApp();
app.use(morgan("dev")); // arity-3 β useExpress automatically (8.2.1+)
// or: app.useExpress(morgan("dev"));import express from "velociradix/express";
import morgan from "morgan";
const app = express();
app.use(morgan("dev"));
app.use("/api", morgan("tiny")); // prefix mount uses the same bridgePrefer 8.2.1+. Do not expect bare (ctx, next) middleware to drive morgan.
π 4. Multiple useExpress Middlewares Overwriting res Context β
Symptom β
Only the last useExpress middleware receives response completion events (res.on('finish')).
Root Cause β
Registering multiple useExpress calls previously overwrote ctx._expressRes.
Solution β
Upgrade to v7.0.0. Velociradix maintains ctx._expressResList array to propagate events to all Express middleware instances.
π 5. CORS Preflight Blocked (OPTIONS 404) β
Symptom β
Browser console error:
Access to fetch at 'http://localhost:3000/api' from origin 'http://localhost:5173' has been blocked by CORS policy.Root Cause β
cors() middleware was mounted below route definitions or OPTIONS HTTP method was unhandled.
Solution β
Mount cors() at the very top of your application before defining routes:
import { createApp, cors } from "velociradix";
const app = createApp();
app.use(cors({ origin: "*" }));π οΈ 6. VitePress Command Not Found (sh: vitepress: command not found) β
Symptom β
Running npm run docs:dev or npm run docs:build fails with exit code 127.
Root Cause β
node_modules or vitepress package is not installed.
Solution β
npm install
npm run docs:buildποΈ 7. velociradix/express Import Failed (ERR_MODULE_NOT_FOUND) β
Symptom β
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'velociradix/express'Root Cause β
Using an older version of Velociradix (< v7.0.0) that does not export ./express in package.json.
Solution β
Update package.json dependency to "velociradix": "^7.0.0".
π‘οΈ 8. Validation Error: ctx.validate() Throwing 400 β
Symptom β
Request fails with 400 Bad Request: { "error": "Validation failed", "details": { "errors": [...] } }.
Root Cause β
Incoming payload did not satisfy rules specified in ctx.validate(schema).
Solution β
Inspect validation errors in details.errors array and ensure client payload matches required schema types.
π₯ 9. ctx.body() Returning Undefined or Empty Object β
Symptom β
await ctx.body() returns undefined on POST requests.
Root Cause β
Request headers missing Content-Type: application/json or payload size exceeds limit.
Solution β
Set client header Content-Type: application/json and verify payload size limit:
app.setPayloadLimit(10 * 1024 * 1024); // 10 MB limitπ 10. ctx.sendFile() Path Traversal Error β
Symptom β
ctx.sendFile(filepath) returns 403 Forbidden or 404 Not Found.
Root Cause β
File path contains directory traversal sequences (../) resolving outside allowed directory.
Solution β
Pass { root } so the resolved path must stay inside that directory:
return ctx.sendFile(reqPath, { root: "./public" });π 11. JWT Verification Error (Token Invalid / Expired) β
Symptom β
ctx.jwtVerify(secret) throws 401 Unauthorized.
Root Cause β
Authorization header missing Bearer prefix or token expiration time (exp) passed.
Solution β
Send header as Authorization: Bearer <token> and verify secret matches signer:
const token = ctx.jwtSign({ userId: 1 }, secret, { expiresIn: 3600 });πͺ 12. Encrypted Cookies Not Persisting β
Symptom β
ctx.getEncryptedCookie(name, secret) returns undefined on subsequent requests.
Root Cause β
Cookie secret key mismatch or cookie SameSite / Domain attribute mismatch.
Solution β
Ensure identical secret key is passed to both setEncryptedCookie and getEncryptedCookie:
ctx.setEncryptedCookie("session", data, SECRET_KEY, { httpOnly: true });π 13. Rate Limiter Blocking Legitimate Proxied Requests β
Symptom β
All users behind a reverse proxy (NGINX / Cloudflare) get rate limited together (429 Too Many Requests).
Root Cause β
ctx.ip defaulted to proxy IP (127.0.0.1) because setTrustProxy was disabled.
Solution β
Enable setTrustProxy(true) so rate limit tracks client IP from X-Forwarded-For:
app.setTrustProxy(true);
app.use(rateLimit({ windowMs: 60000, max: 100 }));πΎ 14. Memory Spike on Heavy File Streams β
Symptom β
Node process RSS memory grows significantly during large file downloads.
Root Cause β
Buffering entire file into memory before sending instead of chunked response.
Solution β
Use ctx.sendFile(path) which leverages native range streaming and low memory footprint.
π₯ 15. Uncaught Async Route Exceptions Crashing Process β
Symptom β
Unhandled promise rejection terminates Node process.
Root Cause β
Async error inside custom middleware without try/catch or next handling.
Solution β
Register global error handler via app.onError:
app.onError((err, ctx) => {
console.error("Unhandled Route Error:", err);
return ctx.status(500).json({ error: err.message });
});π 16. Static Directory Serving 404 β
Symptom β
app.serveStatic('/static', './public') returns 404 for valid files.
Root Cause β
Relative path resolved from different working directory.
Solution β
Use absolute path resolved via import.meta.url:
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
app.serveStatic("/static", join(__dirname, "public"));π 17. TypeScript Error on Custom ctx.state Properties β
Symptom β
TypeScript error Property 'user' does not exist on type 'ContextState'.
Root Cause β
Custom properties attached to ctx.state need interface augmentation.
Solution β
Augment ContextState in your project declaration file (types.d.ts):
declare module "velociradix" {
interface ContextState {
user?: { id: number; role: string };
}
}π€ 18. ctx.renderHtml() HTML Entities Unescaped β
Symptom β
Template variables contain raw HTML tags rendering unintended UI.
Root Cause β
Passing unescaped user input into ctx.renderHtml().
Solution β
Use ctx.escapeHtml() on untrusted variables before rendering:
const safeName = ctx.escapeHtml(userInput);
return ctx.renderHtml("<h1>Hello {{ name }}</h1>", { name: safeName });π¦ 19. Multer File Upload Returning req.file Undefined β
Symptom β
ctx.req.file is undefined inside upload handler.
Root Cause β
useExpress(upload.single('file')) was not executed before route handler.
Solution β
Mount multer middleware via useExpress:
import multer from "multer";
const upload = multer({ dest: "uploads/" });
app.useExpress(upload.single("avatar"));
app.post("/upload", (ctx) => ctx.json({ file: ctx.req.file }));β‘ 20. Server-Sent Events (ctx.sse()) Connection Timeout β
Symptom β
SSE stream closes automatically after 30 seconds.
Root Cause β
Reverse proxy or load balancer timing out idle HTTP connections.
Solution β
Send periodic heartbeat ping messages in your SSE loop:
const interval = setInterval(() => {
ctx.sseSend(": heartbeat\n\n");
}, 15000);π 21. Postman & Swagger UI Missing Registered Routes β
Symptom β
/docs or /postman-docs UI does not display routes registered after call.
Root Cause β
Calling app.swagger() or app.postmanDoc() before registering all routes.
Solution β
Call app.swagger() or app.postmanDoc() after registering all application routes.
π 22. ctx.ip Returning Localhost Behind NGINX β
Symptom β
ctx.ip returns 127.0.0.1 when deployed behind NGINX or AWS ALB.
Root Cause β
setTrustProxy not enabled.
Solution β
app.setTrustProxy(true);βοΈ 23. Shutdown Hooks Not Executing in Docker Container β
Symptom β
Container exits instantly on docker stop without running onShutdown callbacks.
Root Cause β
Node process running as PID 1 inside container without signal forwarding.
Solution β
Use tini or init in Docker container:
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "index.mjs"]β‘ 24. C++ Fast-Path fastGet Bypassing Middlewares β
Symptom β
app.fastGet('/static', data) does not execute JS middlewares.
Root Cause β
Fast-Path responses serve data directly from C++ native memory for maximum performance (~350k req/s).
Solution β
If middleware processing (auth, logging) is required, use standard app.get() route registration instead.
β±οΈ 25. Cache Middleware Serving Expired Responses β
Symptom β
cache({ ttlMs: 5000 }) returns stale data past 5 seconds.
Root Cause β
System clock drift or modified ttlMs setting on dynamic routes.
Solution β
Verify system clock and set explicit TTL options on cache middleware instance.
π‘οΈ 26. CSRF Token Validation Failed (403 Forbidden) β
Symptom β
POST request rejected with Invalid CSRF Token.
Root Cause β
CSRF token in request header does not match value in cookie.
Solution β
Pass token in header X-CSRF-Token matching the cookie from ctx.csrfToken(). Same-origin Origin must match Host. Query-string _csrf is ignored.
π 27. WebSocket Upgrade Header Rejection β
Symptom β
400 Bad Request when connecting to WebSocket endpoint.
Root Cause β
Missing Upgrade: websocket header in client handshake.
Solution β
Ensure client connects using standard WebSocket protocol (ws:// or wss://).
πͺ 28. Cross-Site Cookies Blocked in Chrome β
Symptom β
Cookies set by API backend are not sent by browser frontend on different domain.
Root Cause β
Missing SameSite=None; Secure attributes on cookie.
Solution β
ctx.setCookie("token", value, {
sameSite: "none",
secure: true,
httpOnly: true,
});π 29. Cluster Worker Process Exiting (Worker Died) β
Symptom β
Cluster worker terminates unexpectedly under heavy load.
Root Cause β
Uncaught exception in single worker process.
Solution β
Respawn dead workers automatically in master process:
import cluster from "node:cluster";
if (cluster.isPrimary) {
cluster.on("exit", () => cluster.fork());
}π¦ 30. N-API Addon Version Mismatch (NODE_MODULE_VERSION) β
Symptom β
Error: The module 'velociradix.node' was compiled against a different Node.js version.Root Cause β
Binary compiled on different Node.js major version (e.g. Node 18 vs Node 22).
Solution β
Recompile addon for current active Node.js runtime:
npm rebuild velociradixπ 31. autoRoute Changes Not Detected by tsx watch β
Symptom β
Creating or modifying route files inside routes/ does not trigger hot-reloading when running npx tsx watch server.ts.
Root Cause β
tsx watch builds a static dependency graph from server.ts. Because autoRoute scans and imports modules dynamically at runtime, tsx watch does not automatically track the routes/ folder unless instructed.
Solution β
Pass --include to watch the routes/ directory explicitly:
npx tsx watch --include "routes/**" server.ts
# Or with native Node.js 20+:
node --watch --watch-path=routes server.tsβ‘ 32. autoRouteAsync / New Features Not Found in Consumer Project β
Symptom β
Property 'autoRouteAsync' does not exist on type 'App' when running in a separate demo project.
Root Cause β
The consumer project installed velociradix from the public npm registry (npm install velociradix@latest), which has not yet received unreleased local changes.
Solution β
Link or install the local workspace folder in your project:
npm install ../velociradixπ 33. VitePress 404 on .html Extension in Dev Server β
Symptom β
Navigating to http://localhost:5173/Velociradix/guide/routing.html returns a 404 page.
Root Cause β
In development mode (npm run docs:dev), VitePress serves routes as Clean URLs (without the .html extension).
Solution β
Open the clean URL without .html:
http://localhost:5173/Velociradix/guide/routingπ§΅ 34. Throughput Drop on Single-Core or Low-End Hardware β
Symptom β
Benchmark req/s drops when setting high worker counts on a 1-core VPS or laptop.
Root Cause β
Spawning multiple C++ worker threads on 1 CPU core causes high OS thread context-switching and mutex lock contention on the single Node.js V8 event loop.
Solution β
Auto-tune or set worker count to 1 for low-spec machines:
app.setWorkers(1);π¦ 35. ctx.body() Returns null on Large Payloads (413 Payload Too Large) β
Symptom β
Request body is empty or fails when uploading JSON/binary larger than 1MB.
Root Cause β
Velociradix enforces a default payload protection limit to prevent Out-Of-Memory denial of service.
Solution β
Increase the payload size limit during server setup:
app.setPayloadLimit(10 * 1024 * 1024); // 10MBβ»οΈ 36. ctx.params or ctx.ip Overwritten Inside setTimeout β
Symptom β
Accessing ctx.params.id inside setTimeout(() => { ... }, 1000) returns values from a different request or undefined.
Root Cause β
Velociradix recycles Context objects in a high-speed memory pool (Object Pooling) as soon as the HTTP response finishes.
Solution β
Copy all required request values into local variables before starting asynchronous background tasks:
app.get("/task/:id", (ctx) => {
const taskId = ctx.params.id; // Copy value!
setTimeout(() => {
console.log("Processing task:", taskId);
}, 1000);
return ctx.json({ queued: true });
});π‘οΈ 37. Client IP Always Shows 127.0.0.1 Behind NGINX or Cloudflare β
Symptom β
ctx.ip and rateLimit() treat all incoming users as the same proxy IP 127.0.0.1.
Root Cause β
Velociradix ignores X-Forwarded-For headers by default to protect against IP spoofing.
Solution β
Enable proxy trust mode in your application:
app.setTrustProxy(true);π‘ 38. Server-Sent Events (SSE) Buffering in NGINX Reverse Proxy β
Symptom β
SSE event stream (ctx.sseInterval) delays events and sends them all at once when connection closes.
Root Cause β
NGINX buffers response stream chunks by default.
Solution β
Set X-Accel-Buffering: no and Cache-Control: no-cache:
app.get("/events", (ctx) => {
ctx.setHeader("X-Accel-Buffering", "no");
return ctx.sseInterval(() => ({ data: "update" }), 1000);
});π 39. Missing TypeScript Types (@types/node Missing) β
Symptom β
TypeScript compiler errors on Socket, Buffer, or EventEmitter types.
Root Cause β
@types/node is missing from project devDependencies.
Solution β
Install Node.js types:
npm install -D @types/node typescriptβ οΈ 40. Mixing res.send() and Returning Values in Express Shim β
Symptom β
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
Root Cause β
Calling res.send() inside an Express middleware and also returning a value from the Velociradix route handler.
Solution β
Choose one response pattern per request:
// Pattern A: Native return
app.get("/api", (ctx) => ctx.json({ ok: true }));
// Pattern B: Express shim
app.get("/api", (ctx) => {
ctx.res.status(200).send("ok");
});