Alpha

A secure, standards-based server runtime.

V8-based ECMAScript runtime for the server. WinterTC-compliant, capability secured.

import { serve } from "runtime:http";

const server = serve({ port: 8080 }, (req) => {
  return new Response("👋 Hello from ESRun!");
});

const { hostname, port } = await server.addr;
console.log(`listening on http://${hostname}:${port}`);

Benchmarks

Cold start (near-empty script)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
4.3ms
12MB
Deno
12.8ms
28MB
esrun
8.6ms
24MB
LLRT
3.5ms
11MB
Node.js
21.6ms
45MB
Parse + run ~100 KB script
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
15.1ms
16MB
Deno
21.3ms
30MB
esrun
19.9ms
25MB
LLRT
11.3ms
15MB
Node.js
33ms
46MB
Load a 300-module graph
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
12.3ms
24MB
Deno
31.6ms
33MB
esrun
33ms
27MB
LLRT
15.1ms
13MB
Node.js
95.7ms
64MB
Peak resident memory (idle)
Runtime
Perf (lower is better)
Bun
12MB
Deno
28MB
esrun
24MB
LLRT
11MB
Node.js
45MB
Peak resident memory (under load)
Runtime
Perf (lower is better)
Bun
145MB
Deno
125MB
esrun
108MB
LLRT
161MB
Node.js
135MB
Tight compute loop
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
108.5ms
24MB
Deno
233.4ms
39MB
esrun
190.1ms
36MB
LLRT
1700ms
11MB
Node.js
191.5ms
54MB
JSON parse/stringify
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
150.4ms
38MB
Deno
193.2ms
56MB
esrun
185.7ms
54MB
LLRT
618ms
11MB
Node.js
255.9ms
80MB
JSON (large documents)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
425.3ms
104MB
Deno
499.1ms
138MB
esrun
468.5ms
93MB
LLRT
1576.5ms
124MB
Node.js
667.2ms
159MB
Regex (match, validate, replace)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.5ms
32MB
Deno
62.1ms
40MB
esrun
58.1ms
39MB
LLRT
935.4ms
11MB
Node.js
64.9ms
55MB
String building and slicing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
59.5ms
44MB
Deno
58.1ms
47MB
esrun
56.4ms
46MB
LLRT
159ms
13MB
Node.js
59.8ms
64MB
structuredClone
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
233ms
32MB
Deno
259.3ms
43MB
esrun
271.8ms
52MB
LLRT
390.9ms
11MB
Node.js
210.9ms
55MB
throw/catch + stack capture
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
314.5ms
48MB
Deno
4248.9ms
64MB
esrun
392.4ms
46MB
LLRT
369.8ms
11MB
Node.js
1422.7ms
63MB
async/await throughput
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
37.9ms
33MB
Deno
30.1ms
39MB
esrun
30.3ms
38MB
LLRT
544.2ms
13MB
Node.js
54.9ms
55MB
setTimeout churn
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
29ms
48MB
Deno
236.4ms
167MB
esrun
113.2ms
88MB
LLRT
45.3ms
50MB
Node.js
50.4ms
86MB
URL parsing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
30.3ms
49MB
Deno
98.3ms
46MB
esrun
84.5ms
46MB
LLRT
118.4ms
11MB
Node.js
34.9ms
64MB
TextEncoder/TextDecoder (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.4ms
38MB
Deno
65.7ms
48MB
esrun
64.5ms
46MB
LLRT
67.5ms
11MB
Node.js
67.4ms
63MB
TextEncoder/TextDecoder (64 KiB)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
72.3ms
41MB
Deno
143.7ms
40MB
esrun
99.5ms
38MB
LLRT
242.3ms
12MB
Node.js
308.3ms
52MB
base64 (atob/btoa)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
10.9ms
38MB
Deno
7.2ms
42MB
esrun
22.3ms
35MB
LLRT
33.4ms
11MB
Node.js
7ms
55MB
TypedArray / DataView
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.2ms
34MB
Deno
14.9ms
41MB
esrun
13.3ms
39MB
LLRT
70.8ms
13MB
Node.js
16.2ms
56MB
Headers / Request parsing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
222.7ms
40MB
Deno
1519.5ms
127MB
esrun
414.1ms
48MB
LLRT
—
—
Node.js
419.4ms
76MB
Multipart FormData round trip
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
19.4ms
47MB
Deno
375.7ms
111MB
esrun
88.5ms
44MB
LLRT
4325.1ms
13MB
Node.js
271ms
124MB
Date + Intl formatting
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
76.6ms
41MB
Deno
137.5ms
47MB
esrun
131.1ms
43MB
LLRT
—
—
Node.js
122.4ms
63MB
ReadableStream piping
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
5.7ms
26MB
Deno
10.6ms
44MB
esrun
10.2ms
43MB
LLRT
91.3ms
13MB
Node.js
10.8ms
62MB
SubtleCrypto SHA-256
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
433ms
36MB
Deno
415.8ms
42MB
esrun
334.8ms
33MB
LLRT
327.1ms
11MB
Node.js
512.9ms
58MB
HMAC + AES-GCM
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
84.9ms
32MB
Deno
97.8ms
40MB
esrun
34.1ms
34MB
LLRT
35.2ms
11MB
Node.js
187.3ms
61MB
ECDSA P-256 sign + verify
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
199ms
25MB
Deno
2234.2ms
40MB
esrun
1112.4ms
33MB
LLRT
1015.5ms
11MB
Node.js
378.2ms
57MB
PBKDF2 (10k iterations)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
64.1ms
17MB
Deno
71.7ms
32MB
esrun
98.7ms
26MB
LLRT
96.7ms
11MB
Node.js
70.7ms
48MB
fetch (local server)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
17.4ms
22MB
Deno
35.4ms
42MB
esrun
40.8ms
40MB
LLRT
18.3ms
13MB
Node.js
94.9ms
90MB
File read (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
38ms
35MB
Deno
40.3ms
42MB
esrun
41.7ms
36MB
LLRT
26.8ms
11MB
Node.js
113.5ms
55MB
File read (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.4ms
37MB
Deno
64.6ms
167MB
esrun
26.2ms
91MB
LLRT
6.8ms
21MB
Node.js
60.1ms
114MB
File write (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
12.4ms
24MB
Deno
85.3ms
40MB
esrun
81ms
32MB
LLRT
92.2ms
11MB
Node.js
164.9ms
55MB
File write (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
20.1ms
24MB
Deno
42.4ms
79MB
esrun
35.6ms
33MB
LLRT
49.5ms
19MB
Node.js
58.9ms
91MB
File append (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
32.3ms
26MB
Deno
42ms
39MB
esrun
35.9ms
32MB
LLRT
—
—
Node.js
96.1ms
55MB
File append (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
7ms
20MB
Deno
15.5ms
49MB
esrun
8.9ms
27MB
LLRT
—
—
Node.js
23.6ms
60MB
File stat (one path)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
47.7ms
26MB
Deno
90.8ms
40MB
esrun
55.6ms
34MB
LLRT
41.2ms
13MB
Node.js
75.5ms
51MB
File stat (1000 paths)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
193.4ms
35MB
Deno
358.1ms
45MB
esrun
238.6ms
38MB
LLRT
165.5ms
11MB
Node.js
279.4ms
61MB
File exists (one path)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
7.5ms
27MB
Deno
90ms
40MB
esrun
40.1ms
32MB
LLRT
48.2ms
11MB
Node.js
60.8ms
48MB
File exists (1000 paths)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
31.3ms
39MB
Deno
362ms
43MB
esrun
180.5ms
33MB
LLRT
195.3ms
11MB
Node.js
245.1ms
59MB
Glob scan
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
28.5ms
33MB
Deno
—
—
esrun
48.2ms
36MB
LLRT
—
—
Node.js
187.8ms
64MB
Spawn a child process
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
131.4ms
26MB
Deno
142.6ms
52MB
esrun
87.7ms
34MB
LLRT
121.2ms
11MB
Node.js
239.6ms
55MB
Cold start (near-empty script)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
4.3ms
12MB
Deno
12.8ms
28MB
esrun
8.6ms
24MB
LLRT
3.5ms
11MB
Node.js
21.6ms
45MB
Parse + run ~100 KB script
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
15.1ms
16MB
Deno
21.3ms
30MB
esrun
19.9ms
25MB
LLRT
11.3ms
15MB
Node.js
33ms
46MB
Load a 300-module graph
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
12.3ms
24MB
Deno
31.6ms
33MB
esrun
33ms
27MB
LLRT
15.1ms
13MB
Node.js
95.7ms
64MB
Peak resident memory (idle)
Runtime
Perf (lower is better)
Bun
12MB
Deno
28MB
esrun
24MB
LLRT
11MB
Node.js
45MB
Peak resident memory (under load)
Runtime
Perf (lower is better)
Bun
145MB
Deno
125MB
esrun
108MB
LLRT
161MB
Node.js
135MB
Tight compute loop
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
108.5ms
24MB
Deno
233.4ms
39MB
esrun
190.1ms
36MB
LLRT
1700ms
11MB
Node.js
191.5ms
54MB
JSON parse/stringify
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
150.4ms
38MB
Deno
193.2ms
56MB
esrun
185.7ms
54MB
LLRT
618ms
11MB
Node.js
255.9ms
80MB
JSON (large documents)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
425.3ms
104MB
Deno
499.1ms
138MB
esrun
468.5ms
93MB
LLRT
1576.5ms
124MB
Node.js
667.2ms
159MB
Regex (match, validate, replace)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.5ms
32MB
Deno
62.1ms
40MB
esrun
58.1ms
39MB
LLRT
935.4ms
11MB
Node.js
64.9ms
55MB
String building and slicing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
59.5ms
44MB
Deno
58.1ms
47MB
esrun
56.4ms
46MB
LLRT
159ms
13MB
Node.js
59.8ms
64MB
structuredClone
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
233ms
32MB
Deno
259.3ms
43MB
esrun
271.8ms
52MB
LLRT
390.9ms
11MB
Node.js
210.9ms
55MB
throw/catch + stack capture
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
314.5ms
48MB
Deno
4248.9ms
64MB
esrun
392.4ms
46MB
LLRT
369.8ms
11MB
Node.js
1422.7ms
63MB
async/await throughput
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
37.9ms
33MB
Deno
30.1ms
39MB
esrun
30.3ms
38MB
LLRT
544.2ms
13MB
Node.js
54.9ms
55MB
setTimeout churn
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
29ms
48MB
Deno
236.4ms
167MB
esrun
113.2ms
88MB
LLRT
45.3ms
50MB
Node.js
50.4ms
86MB
URL parsing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
30.3ms
49MB
Deno
98.3ms
46MB
esrun
84.5ms
46MB
LLRT
118.4ms
11MB
Node.js
34.9ms
64MB
TextEncoder/TextDecoder (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.4ms
38MB
Deno
65.7ms
48MB
esrun
64.5ms
46MB
LLRT
67.5ms
11MB
Node.js
67.4ms
63MB
TextEncoder/TextDecoder (64 KiB)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
72.3ms
41MB
Deno
143.7ms
40MB
esrun
99.5ms
38MB
LLRT
242.3ms
12MB
Node.js
308.3ms
52MB
base64 (atob/btoa)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
10.9ms
38MB
Deno
7.2ms
42MB
esrun
22.3ms
35MB
LLRT
33.4ms
11MB
Node.js
7ms
55MB
TypedArray / DataView
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.2ms
34MB
Deno
14.9ms
41MB
esrun
13.3ms
39MB
LLRT
70.8ms
13MB
Node.js
16.2ms
56MB
Headers / Request parsing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
222.7ms
40MB
Deno
1519.5ms
127MB
esrun
414.1ms
48MB
LLRT
—
—
Node.js
419.4ms
76MB
Multipart FormData round trip
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
19.4ms
47MB
Deno
375.7ms
111MB
esrun
88.5ms
44MB
LLRT
4325.1ms
13MB
Node.js
271ms
124MB
Date + Intl formatting
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
76.6ms
41MB
Deno
137.5ms
47MB
esrun
131.1ms
43MB
LLRT
—
—
Node.js
122.4ms
63MB
ReadableStream piping
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
5.7ms
26MB
Deno
10.6ms
44MB
esrun
10.2ms
43MB
LLRT
91.3ms
13MB
Node.js
10.8ms
62MB
SubtleCrypto SHA-256
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
433ms
36MB
Deno
415.8ms
42MB
esrun
334.8ms
33MB
LLRT
327.1ms
11MB
Node.js
512.9ms
58MB
HMAC + AES-GCM
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
84.9ms
32MB
Deno
97.8ms
40MB
esrun
34.1ms
34MB
LLRT
35.2ms
11MB
Node.js
187.3ms
61MB
ECDSA P-256 sign + verify
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
199ms
25MB
Deno
2234.2ms
40MB
esrun
1112.4ms
33MB
LLRT
1015.5ms
11MB
Node.js
378.2ms
57MB
PBKDF2 (10k iterations)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
64.1ms
17MB
Deno
71.7ms
32MB
esrun
98.7ms
26MB
LLRT
96.7ms
11MB
Node.js
70.7ms
48MB
fetch (local server)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
17.4ms
22MB
Deno
35.4ms
42MB
esrun
40.8ms
40MB
LLRT
18.3ms
13MB
Node.js
94.9ms
90MB
File read (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
38ms
35MB
Deno
40.3ms
42MB
esrun
41.7ms
36MB
LLRT
26.8ms
11MB
Node.js
113.5ms
55MB
File read (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.4ms
37MB
Deno
64.6ms
167MB
esrun
26.2ms
91MB
LLRT
6.8ms
21MB
Node.js
60.1ms
114MB
File write (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
12.4ms
24MB
Deno
85.3ms
40MB
esrun
81ms
32MB
LLRT
92.2ms
11MB
Node.js
164.9ms
55MB
File write (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
20.1ms
24MB
Deno
42.4ms
79MB
esrun
35.6ms
33MB
LLRT
49.5ms
19MB
Node.js
58.9ms
91MB
File append (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
32.3ms
26MB
Deno
42ms
39MB
esrun
35.9ms
32MB
LLRT
—
—
Node.js
96.1ms
55MB
File append (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
7ms
20MB
Deno
15.5ms
49MB
esrun
8.9ms
27MB
LLRT
—
—
Node.js
23.6ms
60MB
File stat (one path)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
47.7ms
26MB
Deno
90.8ms
40MB
esrun
55.6ms
34MB
LLRT
41.2ms
13MB
Node.js
75.5ms
51MB
File stat (1000 paths)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
193.4ms
35MB
Deno
358.1ms
45MB
esrun
238.6ms
38MB
LLRT
165.5ms
11MB
Node.js
279.4ms
61MB
File exists (one path)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
7.5ms
27MB
Deno
90ms
40MB
esrun
40.1ms
32MB
LLRT
48.2ms
11MB
Node.js
60.8ms
48MB
File exists (1000 paths)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
31.3ms
39MB
Deno
362ms
43MB
esrun
180.5ms
33MB
LLRT
195.3ms
11MB
Node.js
245.1ms
59MB
Glob scan
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
28.5ms
33MB
Deno
—
—
esrun
48.2ms
36MB
LLRT
—
—
Node.js
187.8ms
64MB
Spawn a child process
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
131.4ms
26MB
Deno
142.6ms
52MB
esrun
87.7ms
34MB
LLRT
121.2ms
11MB
Node.js
239.6ms
55MB
See full benchmarks →

Why ES-Runtime?

Standard web APIs

fetch, URL, URLPattern, streams, WebCrypto, encoding, timers, and events — the WinterTC Minimum Common Web Platform API. What you learn here is what runs in the browser.

Batteries in the binary

HTTP and WebSocket servers, filesystem, sockets, subprocesses, WASM/WASI, and XML, YAML, TOML, JSONL, MessagePack, and Protobuf parsers — all behind runtime: imports, nothing to install.

ESM everywhere

Static imports, dynamic import(), top-level await, import.meta, and JSON modules. One module system, no CommonJS interop rules to learn — esdev's bundler converts a CJS dependency at build time.

Written in Rust

The runtime around the engine is safe Rust: no data races, no use-after-free, no buffer overruns to reach for under hostile input.

Powered by V8

The engine behind Chrome and Node.js, embedded from Rust. A baked startup snapshot opens a full realm in about 9 ms.

Capability-gated

Deny-by-default: a run reaches what the command line that started it named, and nothing else. Every host operation is gated on an explicit capability, checked in Rust rather than in JavaScript.

Choosing a runtime? How esrun compares to Node.js, Bun and Deno →

State without infrastructure

Durable workers: addressable state that outlives the process.

CALLERSONE DURABLE WORKERITS FILE · SQLITEAnaBenChloétake(1)take(1)take(1)Stock("hoodie")MAILBOXRUNNINGSTATEAnaBenChloéidleleft: 2on diskgone with the processanswer only after commit⚡ SIGKILLhoodie.dbleft: 2untouchedThree customers want the last two hoodies. Their calls queue in one worker's mailbox.

One call at a time, per worker

Calls to one worker queue and run in order, so two customers cannot both take the last item. No lock to take, no row to contend for.

Answered once it is on disk

A result is released only after the writes behind it have committed. A crash is a call that never returned, never one that returned a lie.

Timers that outlive the process

An alarm is stored beside the state and runs after a restart: retries, abandoned carts, a nightly roll-up, without a scheduler service.

Nothing else to run

State lives in embedded SQLite, one file per worker, under the same read and write grants as everything else. No Redis, no queue, no database server.

Killed with SIGKILL under load, the example shop loses no order it acknowledged.

Benchmarks

Hono hello-world · Speed & Memory
Runtime
Throughput (higher ↑)
Memory (lower ↓)
Bun
74.0k
47 MB
Deno
69.7k
51 MB
esrun
50.6k
51 MB
Node.js
30.9k
107 MB
Elysia hello-world · Speed & Memory
Runtime
Throughput (higher ↑)
Memory (lower ↓)
Bun
86.1k
46 MB
Deno
76.1k
63 MB
esrun
51.4k
54 MB
Node.js
30.4k
99 MB

Plaintext hello-world over loopback, driven by oha at 100 connections — best of three runs. Elysia is measured on the esdev bundle every runtime serves (esrun is ESM-only), and Node serves both frameworks through the same @hono/node-server glue, so the delta is route handling, not the adapter. How it is measured →

Deny by default

A run reaches what the command line named, and nothing else.

RV8 ISOLATEOP BOUNDARY · RUSTTHE HOSTfetch(url)file(path).text()serve(opts, fn)write(path, bytes)env.DATABASE_URLnew Command("sh")netreadlistenwriteenvrunapi.example.com./config.json:8080./out/report.csvDATABASE_URL/bin/shesrun --allow-net --allow-read --allow-listen app.js

Nine capabilities: read, write, imports, net, listen, env, run, signals, workers. The gate is the op, not the import.

Two binaries.

esrun runs your service and nothing else — no watcher, no debugger port, no transform. That narrowness is what a capability flag is worth.

esdev pays for it on your machine: same runtime, same flags, plus the toolchain to get there.

In esdev, not in production

  • TypeScript & JSX
  • CommonJS (converted at build)
  • Bundler
  • Test runner
  • Watch mode
  • Debugger

Not goals at all

  • Node.js compatibility
  • Package installer
  • Linter / formatter
  • FFI
  • Native addons
  • Remote module imports

Ship a runtime you can trust.

Start building

Install both binaries with one command, and run your first module. Standards-based, fast, ESM-only.

Ship it locked down

Nothing is granted unless the command line says so. Build the deploy line that states exactly what your service may touch.