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.
Root Cause โ
morgan checks res.headersSent and res._header. If response was sent via native ctx.send(), Express res flags were not synchronized.
Solution โ
NOTE
Fixed in v7.0.0. Upgrade to Velociradix v7.0.0 where respondRes automatically synchronizes res.headersSent = true, res.finished = true, and executes res.writeHead() hooks for morgan & response-time.
๐ 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 โ
Use path.resolve or path.join to normalize file paths before passing to ctx.sendFile:
import { resolve } from 'node:path';
const safePath = resolve('./public', reqPath.replace(/^\//, ''));
return ctx.sendFile(safePath);๐ 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 cookie value generated by ctx.csrfToken().
๐ 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');
});