env-runner
env-runner is a generic environment runner for JavaScript applications, abstracting away the complexities of various runtime environments. It enables developers to run server applications across Node.js worker threads, child processes, Bun, Deno, Cloudflare Workers (via Miniflare), Vercel, Netlify, or even in-process. The package provides essential features like hot-reloading for development, WebSocket proxying, and a bidirectional messaging system between the main process and the runner environment. Currently at version 0.1.7, it is actively developed with rapid minor releases focusing on enhancements and new runner integrations, offering a unified API for deploying serverless functions or local servers across diverse JavaScript ecosystems. Its key differentiator is providing a consistent interface regardless of the underlying runtime.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'onReady')
cause Attempting to use the old callback assignment style on `RunnerManager` or `EnvServer` (e.g., `manager.onReady = ...`).fixUse the new event listener methods: `manager.onReady((runner, address) => { ... });` or `envServer.onReady((runner, address) => { ... });` -
Error: Cannot find module 'miniflare'
cause Using `MiniflareEnvRunner` (or `NetlifyEnvRunner`) without having its corresponding peer dependency installed.fixInstall the missing peer dependency: `npm install miniflare` (or `npm install @netlify/runtime`) in your project. -
Error: Module './app.ts' does not export a default 'fetch' handler.
cause The entry module specified for the runner (e.g., `app.ts`) does not export a default object containing a `fetch` method.fixEnsure your application entry file has `export default { fetch(request: Request) { /* ... */ } };` at its root. -
TypeError: Class constructor NodeProcessEnvRunner cannot be invoked without 'new'
cause Attempting to invoke a runner class constructor directly (e.g., `NodeProcessEnvRunner({...})`) instead of instantiating it with `new`.fixAlways instantiate runner classes using the `new` keyword: `const runner = new NodeProcessEnvRunner({...});`.
Warnings
- breaking The `RunnerManager` and `EnvServer` APIs changed their event handling from direct callback properties (e.g., `onReady = () => {}`) to a multi-listener event pattern (e.g., `.onReady((runner, address) => {})`).
- breaking Core graceful shutdown mechanisms were removed from the package, requiring applications to implement their own shutdown logic.
- gotcha Specific runners, such as `MiniflareEnvRunner` or `NetlifyEnvRunner`, rely on peer dependencies (`miniflare`, `@netlify/runtime`) that must be installed separately by the consumer.
- gotcha As a pre-1.0 package, `env-runner` may introduce frequent breaking changes between minor versions, especially in its early stages of development.
Install
-
npm install env-runner -
yarn add env-runner -
pnpm add env-runner
Imports
- EnvServer
import { EnvServer } from 'env-runner' - RunnerManager
import { RunnerManager } from 'env-runner' - NodeProcessEnvRunner
import { NodeProcessEnvRunner } from 'env-runner'import { NodeProcessEnvRunner } from 'env-runner/runners/node-process'
Quickstart
import { serve } from "srvx";
import { EnvServer } from "env-runner";
import { fileURLToPath } from 'node:url';
import path from 'node:path';
// app.ts (your application entry point - create this file)
// export default {
// fetch(request: Request) {
// return new Response(`Hello from env-runner at ${new Date().toISOString()}!`);
// },
// };
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const appEntryPath = path.join(__dirname, 'app.ts'); // Path to your app.ts entry
const envServer = new EnvServer({
runner: "node-process", // Choose your desired runner: 'miniflare', 'bun-process', etc.
entry: appEntryPath,
watch: true,
watchPaths: [path.join(__dirname, 'src')], // Example additional watch path
});
envServer.onReady((_runner, address) => {
if (address) {
console.log(`Worker ready on ${address.host}:${address.port}`);
} else {
console.log("Worker ready, but no address reported.");
}
});
envServer.onReload(() => {
console.log("Application reloaded!");
});
envServer.onError((error) => {
console.error("EnvServer error:", error);
});
await envServer.start();
// Use with any HTTP server (srvx is used here as an example from the README)
const server = serve({
fetch: (request) => envServer.fetch(request),
});
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
server.listen({ port, host: 'localhost' });
console.log(`HTTP server listening on http://localhost:${port}`);
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down env-runner and HTTP server...');
await envServer.close();
server.close();
process.exit(0);
});