vite-node-backend
Overview
While Vite is primarily known as a frontend build tool, its HMR (Hot Module Replacement) capabilities can be leveraged for backend server development with frameworks like NestJS, Express, Fastify, and Koa. This eliminates the slow server restart cycle that plagues traditional backend development.
The Problem: Slow Server Restarts
Traditional NestJS/Express development suffers from:
Code change → Kill process → Respawn process → Re-bootstrap app → Ready ↓ ↓ ↓ ~100ms ~200ms ~500-2000msEven with Webpack HMR in NestJS CLI:
- The module updates via HMR
- But the server still restarts to apply changes
- Total time: several seconds on large projects
The Solution: vite-plugin-node
vite-plugin-node leverages Vite’s SSR middleware mode to provide true HMR for backend servers:
Code change → Invalidate module → Re-execute handler → Ready ↓ ↓ ~10ms ~40msResult: Updates reflect in under 50ms.
How It Works
- SSR Middleware Mode: Vite can run in middleware mode, designed for SSR rendering
- Module Graph Caching: Vite maintains a module dependency graph
- Selective Invalidation: Only changed modules and their parents are invalidated
- On-Demand Execution: When a request comes in, Vite loads the latest version of your handler
┌─────────────────────────────────────────────────────┐│ Vite Dev Server ││ ┌─────────────┐ ┌──────────────────────────┐ ││ │ Module Graph │ → │ Your Server App Entry │ ││ │ (cache) │ │ (NestJS/Express/Fastify) │ ││ └─────────────┘ └──────────────────────────┘ ││ ↓ ↓ ││ Only invalidate Execute latest code ││ changed modules for each request │└─────────────────────────────────────────────────────┘Supported Frameworks
| Framework | Adapter | Notes |
|---|---|---|
| NestJS | 'nest' | Best use case—slowest to restart traditionally |
| Express | 'express' | Well-supported |
| Fastify | 'fastify' | Well-supported |
| Koa | 'koa' | Well-supported |
| Apollo Server | Custom | Community adapter available |
| Cloud Functions | Custom | Community adapter available |
Setup Guide
1. Install Dependencies
npm install vite vite-plugin-node -DVersion Matching: Plugin version corresponds to Vite version (e.g., vite-plugin-node@7.x for vite@7.x).
2. Create vite.config.ts
import { defineConfig } from 'vite';import { VitePluginNode } from 'vite-plugin-node';
export default defineConfig({ server: { port: 3000, }, plugins: [ ...VitePluginNode({ // Framework adapter adapter: 'nest', // 'express' | 'fastify' | 'koa' | custom function
// Entry file path appPath: './src/main.ts',
// Export name from entry file exportName: 'viteNodeApp',
// TypeScript compiler (esbuild is 20x faster) tsCompiler: 'esbuild', // or 'swc'
// Optional: Auto-initialize on boot initAppOnBoot: false,
// Optional: Full reload on file change (debounced 500ms) reloadAppOnFileChange: false, }), ], optimizeDeps: { // Exclude NestJS optional dependencies exclude: [ '@nestjs/microservices', '@nestjs/websockets', 'cache-manager', 'class-transformer', 'class-validator', ], },});3. Modify Your Entry Point
NestJS (src/main.ts):
import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';
// Export the app for Vite to useexport const viteNodeApp = NestFactory.create(AppModule);
// Only listen in production (not when Vite handles it)async function bootstrap() { if (process.env.NODE_ENV === 'production') { const app = await viteNodeApp; await app.listen(3000); }}
bootstrap();Express (src/main.ts):
import express from 'express';
const app = express();
app.get('/', (req, res) => { res.send('Hello World!');});
// Export for Viteexport const viteNodeApp = app;
// Only listen in productionif (process.env.NODE_ENV === 'production') { app.listen(3000);}4. Add Scripts to package.json
{ "scripts": { "dev": "vite", "build": "nest build", "start:prod": "node dist/main.js" }}5. Run Development Server
npm run devChanges now reflect in under 50ms without server restart.
Alternative: vite-node CLI
For simpler cases without full HMR, the vite-node CLI (from Vitest) can run Node.js code with Vite’s transformation:
npx vite-node src/main.tsThis provides:
- Vite’s transformation pipeline (TypeScript, ESM handling)
- No configuration needed
- But no HMR (requires restart on changes)
Combine with --watch for basic hot restart:
npx vite-node --watch src/main.tsPerformance Comparison
| Approach | Restart Time | Notes |
|---|---|---|
| vite-plugin-node | ~50ms | True HMR, no restart |
| node —watch | ~500-1000ms | Full restart (Node 18+) |
| nodemon | ~500-1500ms | Full restart |
| NestJS CLI (webpack) | ~1-3s | HMR but still restarts |
| ts-node-dev | ~500-1500ms | Full restart |
Trade-offs and Considerations
Advantages
- Speed: Sub-50ms updates vs seconds
- State Preservation: In-memory state can survive reloads (with care)
- Unified Tooling: Same Vite for frontend and backend
- TypeScript: esbuild/swc transpilation (20x faster than tsc)
Limitations
- Early-Stage: Limited real-world testing on large projects
- Configuration Complexity: Requires proper setup and exclusions
- Production Separation: Must maintain separate production build pipeline
- Module Graph: Initial build still takes time (cached after)
- Memory: Vite’s module graph adds memory overhead
When to Use
Good Fit:
- NestJS projects (biggest benefit due to slow restarts)
- Full-stack monorepos (shared Vite config)
- Active development with frequent changes
- Teams already familiar with Vite
Consider Alternatives:
- Simple scripts (just use
node --watch) - Production servers (use compiled JS)
- Projects with complex native dependencies
- When stability is more important than speed
Production Considerations
vite-plugin-node is development-only. For production:
export default defineConfig({ build: { // Build for Node.js, not browser ssr: true, target: 'node18', },});Or use your framework’s native build:
# NestJSnest build
# Manual TypeScripttscComparison: vite-plugin-node vs vite-express
| Feature | vite-plugin-node | vite-express |
|---|---|---|
| Purpose | Backend HMR | Serve frontend from Express |
| Server HMR | ✅ Yes | ❌ No |
| Frontend HMR | ❌ Not focused | ✅ Yes |
| Frameworks | NestJS, Express, Fastify, Koa | Express only |
| Use Case | API development | Full-stack dev server |
Troubleshooting
”Module not found” errors
Ensure optimizeDeps.exclude includes all NestJS optional dependencies:
optimizeDeps: { exclude: [ '@nestjs/microservices', '@nestjs/websockets', // ... other optional deps ],}HMR not working
- Check export name matches config (
viteNodeAppdefault) - Ensure you’re not calling
app.listen()in dev mode - Verify adapter matches your framework
Memory leaks
If memory grows over time:
- Some modules may not clean up properly on HMR
- Consider enabling
reloadAppOnFileChange: truefor periodic full reloads