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-2000ms

Even 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 ~40ms

Result: Updates reflect in under 50ms.

How It Works

  1. SSR Middleware Mode: Vite can run in middleware mode, designed for SSR rendering
  2. Module Graph Caching: Vite maintains a module dependency graph
  3. Selective Invalidation: Only changed modules and their parents are invalidated
  4. 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

FrameworkAdapterNotes
NestJS'nest'Best use case—slowest to restart traditionally
Express'express'Well-supported
Fastify'fastify'Well-supported
Koa'koa'Well-supported
Apollo ServerCustomCommunity adapter available
Cloud FunctionsCustomCommunity adapter available

Setup Guide

1. Install Dependencies

Terminal window
npm install vite vite-plugin-node -D

Version 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 use
export 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 Vite
export const viteNodeApp = app;
// Only listen in production
if (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

Terminal window
npm run dev

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

Terminal window
npx vite-node src/main.ts

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

Terminal window
npx vite-node --watch src/main.ts

Performance Comparison

ApproachRestart TimeNotes
vite-plugin-node~50msTrue HMR, no restart
node —watch~500-1000msFull restart (Node 18+)
nodemon~500-1500msFull restart
NestJS CLI (webpack)~1-3sHMR but still restarts
ts-node-dev~500-1500msFull 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

  1. Early-Stage: Limited real-world testing on large projects
  2. Configuration Complexity: Requires proper setup and exclusions
  3. Production Separation: Must maintain separate production build pipeline
  4. Module Graph: Initial build still takes time (cached after)
  5. 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:

vite.config.ts
export default defineConfig({
build: {
// Build for Node.js, not browser
ssr: true,
target: 'node18',
},
});

Or use your framework’s native build:

Terminal window
# NestJS
nest build
# Manual TypeScript
tsc

Comparison: vite-plugin-node vs vite-express

Featurevite-plugin-nodevite-express
PurposeBackend HMRServe frontend from Express
Server HMR✅ Yes❌ No
Frontend HMR❌ Not focused✅ Yes
FrameworksNestJS, Express, Fastify, KoaExpress only
Use CaseAPI developmentFull-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

  1. Check export name matches config (viteNodeApp default)
  2. Ensure you’re not calling app.listen() in dev mode
  3. Verify adapter matches your framework

Memory leaks

If memory grows over time:

  • Some modules may not clean up properly on HMR
  • Consider enabling reloadAppOnFileChange: true for periodic full reloads

Sources

  1. vite-plugin-node GitHub
  2. vite-plugin-node npm
  3. Getting Started with NestJS, Vite, and esbuild - LogRocket
  4. Vite SSR Documentation
  5. Vite HMR API
  6. How to use Vite HMR for Node.js - Stack Overflow
  7. vite-express GitHub