Skip to content

Dev Server

The velox dev command starts your application with hot module replacement, so code changes take effect in sub-second reloads instead of full process restarts. It reports precise timing metrics, classifies errors with actionable suggestions, and supports both API-only and full-stack (API + web) development.

Terminal window
velox dev

Server starts at http://localhost:3030.

OptionShortDefaultDescription
--port <port>-p3030Port to listen on
--host <host>-HlocalhostHost to bind to
--entry <file>-e(auto-detected)Entry point file
--verbose-vfalseShow detailed timing and reload information
--debug-dfalseEnable debug logging and request tracing
--no-hmrDisable HMR and use legacy tsx watch mode
--no-clearDisable console clearing on restart
--allfalseStart both API and Web dev servers (workspace monorepos)
Terminal window
velox dev # Default: HMR on localhost:3030
velox dev -p 4000 # Custom port
velox dev -H 0.0.0.0 # Bind to all interfaces
velox dev -e src/server.ts # Custom entry point
velox dev --verbose # Detailed timing metrics
velox dev --debug # Debug logging + request tracing
velox dev --no-hmr # Legacy tsx watch mode
velox dev --all # API + Web servers in parallel

The dev server auto-detects your entry point by looking for common patterns (src/index.ts, src/server.ts, etc.). In a workspace monorepo, it searches inside apps/api/.

If multiple entry points are found, you’ll be prompted to choose. To skip detection, specify one directly:

Terminal window
velox dev --entry src/server.ts

The dev server automatically detects your project type:

  • Vite projects — Starts with HMR (or tsx watch with --no-hmr)
  • RSC/Vinxi projects — Delegates to Vinxi’s dev server with file-based routing

In a workspace monorepo with apps/api/ and apps/web/, use --all to start both servers:

Terminal window
velox dev --all

This starts the API server (with HMR) and the web dev server (pnpm dev in apps/web/) in parallel. Both are shut down together on Ctrl+C.

HMR provides fast reloads when files change:

  • Sub-second restart times
  • Preserves server state
  • Automatic velox:ready signal for accurate timing

In package.json:

{
"hotHook": {
"boundaries": [
"src/procedures/**/*.ts",
"src/schemas/**/*.ts",
"src/handlers/**/*.ts"
]
}
}

hot-hook doesn’t reload everything — it reloads only files that match a boundary glob, then re-imports them on next request. Pick boundaries that contain code which is safe to re-instantiate per request:

  • src/procedures/** — procedures are pure functions over (input, ctx). Reloading swaps the handler reference; in-flight requests finish on the old handler, new requests use the new one. No state to migrate.
  • src/schemas/** — Zod schemas are immutable values. Reloading is identical to re-importing the module.
  • src/handlers/** — same shape as procedures (handler functions invoked per request).

Avoid making boundaries of files that hold long-lived state:

  • src/index.ts — the Fastify server instance. Reloading this would tear down listening sockets.
  • src/config/database.ts — your Prisma client. Reloading would close the connection pool mid-request.
  • src/config/auth.ts — JWT signing keys, session stores.
  • Plugin registration files — Fastify plugins are encapsulated; re-registering them throws.

Files outside the boundaries list still trigger a full process restart on change (hot-hook falls back to tsx watch semantics for non-boundary files), so you don’t lose change detection — you just lose the fast in-process reload.

For framework conventions on where to put procedures and schemas, see Project Structure.

For accurate timing metrics, add to your entry point:

await app.start();
if (process.send) {
process.send({ type: 'velox:ready' });
}

The --debug flag enables debug logging and request tracing by setting LOG_LEVEL=debug and VELOX_REQUEST_LOGGING=true:

Terminal window
velox dev --debug

Useful for diagnosing request handling, middleware execution, and route matching.

Add shutdown handlers to prevent connection leaks:

const shutdown = async () => {
await prisma.$disconnect();
process.exit(0);
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);