Skip to content

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 โ€‹

text
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 โ€‹

bash
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 โ€‹

text
Error: velociradix: bind() failed - Port 3000 is already in use

Root 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:

bash
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:

text
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:

javascript
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 โ€‹

bash
npm install
npm run docs:build

๐ŸŽ๏ธ 7. velociradix/express Import Failed (ERR_MODULE_NOT_FOUND) โ€‹

Symptom โ€‹

text
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:

javascript
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:

javascript
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:

javascript
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:

javascript
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:

javascript
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:

javascript
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:

javascript
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):

typescript
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:

javascript
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:

javascript
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:

javascript
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 โ€‹

javascript
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:

dockerfile
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 โ€‹

javascript
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:

javascript
import cluster from 'node:cluster';

if (cluster.isPrimary) {
  cluster.on('exit', () => cluster.fork());
}

๐Ÿ“ฆ 30. N-API Addon Version Mismatch (NODE_MODULE_VERSION) โ€‹

Symptom โ€‹

text
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:

bash
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:

bash
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:

bash
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:

text
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:

javascript
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:

javascript
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:

javascript
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:

javascript
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:

javascript
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:

bash
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:

javascript
// 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');
});

Released under the MIT License.