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.1ms
13MB
Deno
12.8ms
30MB
esrun
8.8ms
26MB
LLRT
3.5ms
11MB
Node.js
21.3ms
43MB
Parse + run ~100 KB script
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
14.2ms
17MB
Deno
21.3ms
30MB
esrun
20.3ms
27MB
LLRT
11.2ms
15MB
Node.js
31ms
46MB
Load a 300-module graph
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
11.9ms
24MB
Deno
30.9ms
33MB
esrun
33.2ms
28MB
LLRT
15.1ms
13MB
Node.js
91.1ms
64MB
Peak resident memory (idle)
Runtime
Perf (lower is better)
Bun
13MB
Deno
30MB
esrun
26MB
LLRT
11MB
Node.js
43MB
Peak resident memory (under load)
Runtime
Perf (lower is better)
Bun
136MB
Deno
129MB
esrun
110MB
LLRT
165MB
Node.js
139MB
Tight compute loop
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
110.4ms
22MB
Deno
231.3ms
40MB
esrun
189.5ms
38MB
LLRT
1686.7ms
11MB
Node.js
193.8ms
55MB
JSON parse/stringify
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
144.7ms
38MB
Deno
189.9ms
57MB
esrun
189.2ms
55MB
LLRT
617.5ms
11MB
Node.js
193.8ms
71MB
JSON (large documents)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
372.9ms
107MB
Deno
497.5ms
139MB
esrun
465.2ms
94MB
LLRT
1526ms
139MB
Node.js
495.6ms
152MB
Regex (match, validate, replace)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
12.3ms
31MB
Deno
61.8ms
41MB
esrun
57.6ms
40MB
LLRT
926.9ms
11MB
Node.js
63.8ms
57MB
String building and slicing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
57.4ms
42MB
Deno
59ms
49MB
esrun
55.3ms
48MB
LLRT
156.9ms
11MB
Node.js
52.4ms
64MB
structuredClone
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
222.4ms
30MB
Deno
249ms
44MB
esrun
265.5ms
53MB
LLRT
380.5ms
11MB
Node.js
194.6ms
56MB
throw/catch + stack capture
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
274.9ms
34MB
Deno
4194.1ms
65MB
esrun
366.8ms
48MB
LLRT
362.5ms
11MB
Node.js
1263.8ms
65MB
async/await throughput
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
38ms
31MB
Deno
29.6ms
40MB
esrun
30ms
40MB
LLRT
540.1ms
11MB
Node.js
46.3ms
57MB
setTimeout churn
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
29.1ms
46MB
Deno
234.1ms
168MB
esrun
101ms
91MB
LLRT
35.3ms
51MB
Node.js
35.6ms
87MB
URL parsing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
30.5ms
47MB
Deno
97.1ms
48MB
esrun
86.9ms
48MB
LLRT
117.1ms
13MB
Node.js
31.8ms
65MB
TextEncoder/TextDecoder (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.3ms
35MB
Deno
66.9ms
51MB
esrun
62.7ms
50MB
LLRT
67ms
11MB
Node.js
70.5ms
66MB
TextEncoder/TextDecoder (64 KiB)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
68.4ms
37MB
Deno
142ms
40MB
esrun
100ms
40MB
LLRT
240.5ms
15MB
Node.js
73.5ms
56MB
base64 (atob/btoa)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
9.7ms
36MB
Deno
7.3ms
43MB
esrun
22.1ms
39MB
LLRT
33.1ms
11MB
Node.js
7.1ms
56MB
TypedArray / DataView
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
16.5ms
33MB
Deno
13ms
42MB
esrun
12.5ms
40MB
LLRT
69.2ms
13MB
Node.js
13.4ms
57MB
Headers / Request parsing
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
167.7ms
35MB
Deno
1496.8ms
127MB
esrun
421.9ms
50MB
LLRT
—
—
Node.js
400.9ms
78MB
Multipart FormData round trip
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
20.3ms
46MB
Deno
372.3ms
112MB
esrun
88ms
45MB
LLRT
4170.4ms
16MB
Node.js
227.4ms
128MB
Date + Intl formatting
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
75.4ms
38MB
Deno
137ms
48MB
esrun
136.5ms
45MB
LLRT
—
—
Node.js
127.4ms
65MB
ReadableStream piping
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
5.6ms
25MB
Deno
10.4ms
45MB
esrun
9.8ms
45MB
LLRT
90.4ms
11MB
Node.js
7.7ms
65MB
SubtleCrypto SHA-256
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
400.1ms
35MB
Deno
400.2ms
42MB
esrun
331.2ms
37MB
LLRT
323.7ms
11MB
Node.js
492.1ms
60MB
HMAC + AES-GCM
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
82.1ms
32MB
Deno
100.4ms
43MB
esrun
34ms
38MB
LLRT
34.4ms
14MB
Node.js
142.1ms
61MB
ECDSA P-256 sign + verify
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
188.2ms
25MB
Deno
2164.7ms
43MB
esrun
1816.8ms
37MB
LLRT
1000.4ms
12MB
Node.js
305.9ms
60MB
PBKDF2 (10k iterations)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
62.7ms
19MB
Deno
69.9ms
35MB
esrun
97.3ms
30MB
LLRT
95.4ms
14MB
Node.js
71.3ms
51MB
fetch (local server)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
15.8ms
21MB
Deno
32.7ms
43MB
esrun
40.1ms
42MB
LLRT
15.6ms
13MB
Node.js
88.5ms
86MB
File read (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
37.5ms
34MB
Deno
42ms
43MB
esrun
44.9ms
40MB
LLRT
26.7ms
13MB
Node.js
46.4ms
56MB
File read (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
17.3ms
37MB
Deno
56.9ms
168MB
esrun
27.4ms
92MB
LLRT
7ms
21MB
Node.js
63ms
115MB
File write (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
12.4ms
23MB
Deno
80ms
42MB
esrun
80ms
35MB
LLRT
89.4ms
13MB
Node.js
90.4ms
60MB
File write (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
18.3ms
23MB
Deno
38.8ms
82MB
esrun
36.8ms
36MB
LLRT
38.7ms
21MB
Node.js
46.4ms
95MB
File append (small)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
29.9ms
25MB
Deno
38.5ms
43MB
esrun
37.4ms
35MB
LLRT
—
—
Node.js
52.7ms
63MB
File append (large)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
6.3ms
21MB
Deno
15.6ms
52MB
esrun
8.7ms
29MB
LLRT
—
—
Node.js
16.1ms
65MB
File stat (one path)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
48.7ms
25MB
Deno
89.3ms
42MB
esrun
55ms
36MB
LLRT
38.4ms
13MB
Node.js
68.1ms
52MB
File stat (1000 paths)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
199.4ms
33MB
Deno
357.2ms
46MB
esrun
242.5ms
42MB
LLRT
154ms
13MB
Node.js
276.1ms
62MB
File exists (one path)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
7.3ms
26MB
Deno
88.5ms
42MB
esrun
42.1ms
36MB
LLRT
46.6ms
13MB
Node.js
56.4ms
52MB
File exists (1000 paths)
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
30.1ms
36MB
Deno
361.9ms
47MB
esrun
190ms
37MB
LLRT
183.2ms
13MB
Node.js
234.2ms
63MB
Glob scan
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
24.7ms
32MB
Deno
—
—
esrun
47.8ms
37MB
LLRT
—
—
Node.js
185.7ms
69MB
Spawn a child process
Runtime
Perf (lower is better)
Memory (lower ↓)
Bun
98.5ms
24MB
Deno
107.9ms
52MB
esrun
81.5ms
37MB
LLRT
94.9ms
13MB
Node.js
183ms
54MB
See full benchmarks →

Why ES-Runtime?

Connect

HTTP and WebSocket servers in the binary.

Store

Embedded SQLite plus first-party Postgres, MySQL/MariaDB, MongoDB (beta), Redis, and Valkey.

Speak

Parsers for every format your API touches.

Ships with

Native image processing, a PDF toolkit and durable state.

Secure

Deny by default on a capability-based runtime.

Develop

Tests with DOM parity, real-browser runs, watch, HMR, plugins.

Run on

A Rust core around V8, scripted in JavaScript and TypeScript.

Built-in Utils

SMTP mail, archive handling, password-grade hashing, two-factor codes, cookies, CSRF tokens, and webhooks.

Message

AMQP 0-9-1, MQTT and NATS messaging.

Observe

OTLP spans to any collector, no SDK.

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
82.1k
45 MB
Deno
78.3k
53 MB
esrun
49.3k
55 MB
Node.js
40.4k
100 MB
Elysia hello-world · Speed & Memory
Runtime
Throughput (higher ↑)
Memory (lower ↓)
Bun
93.5k
46 MB
Deno
78.8k
58 MB
esrun
50.7k
61 MB
Node.js
41.9k
98 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

Eleven capabilities: read, write, imports, net, listen, env, run, signals, workers, diagnostics, diagnostics-detail. 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 by esdev)
  • Bundler
  • Test runner
  • Watch mode
  • Debugger
  • Permission discovery

Not goals at all

  • Node.js compatibility
  • CommonJS at runtime
  • Package installer
  • Linter / formatter
  • FFI
  • Native addons
  • Remote module imports
  • SharedWorker / data: worker URLs

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.