QEMU management library for Node.js
Typed QMP client, process lifecycle manager, and qemu-img wrappers -
all in pure TypeScript with no native bindings.
- QMP client - typed JSON-RPC over Unix socket or TCP, full capability negotiation, sequential command dispatch, typed async events
- 60+ typed QMP commands - VM control, block/storage, device hotplug, migration, CPU/memory, display, introspection, QOM
- Process manager - spawn, monitor, and gracefully stop QEMU processes; PID files; stdout/stderr streams
- CLI argument builder - convert a typed
QemuConfiginto a validatedstring[]argv - Image tools - typed wrappers around
qemu-img: create, resize, convert, info, check, snapshots - AsyncDisposable -
await usingsupport onQMPClientandQemuProcess - Automatic reconnect - exponential backoff on unexpected disconnect (opt-in)
- Dual ESM + CJS output, TypeScript source maps, full
.d.tsdeclarations
npm install node-qemuRequires Node.js ≥ 18 and qemu-system-* / qemu-img binaries on the host.
import { QMPClient } from "node-qemu";
await using client = new QMPClient({ socketPath: "/run/qemu/vm1.sock" });
await client.connect();
// Typed command
const status = await client.queryStatus();
console.log(status.status); // "running" | "paused" | ...
// Typed events
client.on("SHUTDOWN", (e) => console.log("Guest shut down:", e.reason));
client.on("BLOCK_JOB_COMPLETED", (e) => console.log("Backup done:", e.device));
client.on("MIGRATION", (e) => console.log("Migration:", e.status));
// Pause and resume
await client.stop();
await client.cont();
// Graceful shutdown
await client.systemPowerdown();import { QemuProcess, QMPClient } from "node-qemu";
const proc = new QemuProcess({
binary: "/usr/bin/qemu-system-x86_64",
config: {
machine: { type: "q35", accel: "kvm" },
cpu: "host",
memory: { size: 2048 },
drives: [{ file: "/var/lib/qemu/vm1.qcow2", format: "qcow2", virtio: true }],
net: [{ type: "tap", ifname: "tap0", script: "no", vhost: true }],
qmp: { socketPath: "/run/qemu/vm1.sock" },
vnc: { display: "0" },
enableKvm: true,
noReboot: true,
pidfile: "/run/qemu/vm1.pid",
},
});
await proc.start();
console.log("QEMU PID:", proc.pid);
proc.on("stderr", (line) => console.error("[qemu]", line));
proc.on("exit", (code, signal) => console.log("Exited", { code, signal }));
// Connect QMP after process starts
await using client = new QMPClient({ socketPath: proc.socketPath! });
await client.connect();
// Graceful stop - SIGTERM with 5s SIGKILL fallback
await proc.stop();import { QemuImg } from "node-qemu";
// Create a thin-provisioned overlay over a base image
await QemuImg.create({
filename: "/var/lib/qemu/vm1.qcow2",
format: "qcow2",
size: "20G",
backingFile: "/var/lib/qemu/base.qcow2",
});
// Resize in place
await QemuImg.resize({ filename: "/var/lib/qemu/vm1.qcow2", size: "+10G" });
// Convert raw → qcow2 with compression
await QemuImg.convert({
src: "/var/lib/qemu/vm1.raw",
dst: "/var/lib/qemu/vm1.qcow2",
format: "qcow2",
compress: true,
});
// Inspect
const info = await QemuImg.info({ filename: "/var/lib/qemu/vm1.qcow2" });
console.log(info.format, info.virtualSize, info.backingFile);
// Internal qcow2 snapshots
await QemuImg.snapshotCreate({ filename: "/var/lib/qemu/vm1.qcow2", tag: "clean" });
const snaps = await QemuImg.snapshotList({ filename: "/var/lib/qemu/vm1.qcow2" });
await QemuImg.snapshotApply({ filename: "/var/lib/qemu/vm1.qcow2", tag: "clean" });
await QemuImg.snapshotDelete({ filename: "/var/lib/qemu/vm1.qcow2", tag: "clean" });const client = new QMPClient({ host: "192.168.1.10", port: 4444 });
await client.connect();const client = new QMPClient({
socketPath: "/run/qemu/vm1.sock",
reconnect: true,
reconnectDelay: 1000, // initial delay ms (doubles on each attempt)
reconnectMaxDelay: 30_000,
});
client.on("disconnected", () => console.log("Lost connection, retrying..."));
client.on("connected", () => console.log("Reconnected"));// Track changed blocks since last backup
await client.blockDirtyBitmapAdd({ node: "hd0", name: "inc-backup" });
// ... time passes, guest writes data ...
// Incremental backup using the bitmap
await client.blockdevBackup({
device: "hd0",
target: "backup-target",
sync: "incremental",
bitmap: "inc-backup",
});
// Monitor completion
client.once("BLOCK_JOB_COMPLETED", async (e) => {
console.log("Backup complete:", e.device);
await client.blockDirtyBitmapClear({ node: "hd0", name: "inc-backup" });
});await client.migrateSetCapabilities([
{ capability: "xbzrle", state: true },
{ capability: "zero-blocks", state: true },
]);
await client.migrate({ uri: "tcp:192.168.1.2:4444" });
client.on("MIGRATION", (e) => {
if (e.status === "completed") console.log("Migration done");
if (e.status === "failed") console.error("Migration failed");
});import { QmpError, QmpCommandError } from "node-qemu";
try {
await client.blockdevBackup({ device: "hd0", target: "missing", sync: "full" });
} catch (err) {
if (err instanceof QmpCommandError) {
console.error(err.qmpClass, err.description); // "DeviceNotFound" "..."
} else if (err instanceof QmpError) {
console.error(err.code); // "CONNECTION_CLOSED" | "CLIENT_CLOSED" | ...
}
}QMPClient extends EventEmitter with fully typed payloads:
| Event | Payload | Description |
|---|---|---|
SHUTDOWN |
ShutdownEvent |
Guest or host-initiated shutdown |
RESET |
ResetEvent |
System reset |
STOP |
- | VM execution paused |
RESUME |
- | VM execution resumed |
GUEST_PANICKED |
GuestPanicEvent |
Guest kernel panic |
WATCHDOG |
WatchdogEvent |
Watchdog timer fired |
BLOCK_IO_ERROR |
BlockIoErrorEvent |
Block device I/O error |
BLOCK_JOB_COMPLETED |
BlockJobEvent |
Backup/mirror/stream finished |
BLOCK_JOB_CANCELLED |
BlockJobEvent |
Block job cancelled |
BLOCK_JOB_ERROR |
BlockJobErrorEvent |
Block job I/O error |
BLOCK_JOB_READY |
BlockJobEvent |
Mirror ready to pivot |
JOB_STATUS_CHANGE |
JobStatusChangeEvent |
Generic job status changed |
DEVICE_ADDED |
DeviceEvent |
Hotplug device added |
DEVICE_DELETED |
DeviceEvent |
Hotplug device removed |
MIGRATION |
MigrationEvent |
Migration status update |
MIGRATION_PASS |
MigrationPassEvent |
Migration pass counter |
NIC_RX_FILTER_CHANGED |
NicRxFilterEvent |
NIC RX filter updated |
VNC_CONNECTED |
VncEvent |
VNC client connected |
VNC_DISCONNECTED |
VncEvent |
VNC client disconnected |
MEMORY_FAILURE |
MemoryFailureEvent |
Hardware memory failure |
SUSPEND |
- | Guest suspended |
WAKEUP |
- | Guest woke up |
connected |
- | QMP handshake complete |
disconnected |
- | Socket closed |
error |
Error |
Transport error |
Full API documentation is available at sourceregistry.github.io/node-qemu.
For the underlying QMP wire protocol, see the QEMU QMP Reference and qemu-img manual.
| Library | Role |
|---|---|
| node-lxc | LXC container lifecycle |
| node-ovsdb | Open vSwitch / OVSDB networking |
| node-qemu | QEMU VM lifecycle (this library) |