fairyfly
The serverless-first JavaScript runtime for the backend.

Built for cold starts, tight memory, and short-lived handlers. One binary with HTTP, TLS, HTTP/2, WebSocket, fetch, SQLite, Postgres, workers, and FFI.

Installcurl -fsSL https://www.fairyfly.dev/install.sh | sh
165kreq/sec
~10 mscold start
2.9 msp50
1binary
Fairyfly logoFairyfly logo

Built for serverless

Fast cold starts, roughly 5 MB RSS on Apple silicon, and one binary. No npm install for the core.

One binary, batteries included

HTTP, TLS, HTTP/2, WebSocket, fetch, SQLite, Postgres, workers, crypto, and FFI ship in the same binary.

Small enough to audit

About 24k lines of Zig across 54 files. The whole runtime fits in your head.

ESM without the baggage

Pure-JS ESM packages via ff imprint, one flat node_modules, and a lockfile with integrity hashes.

Predictable limits

Every hard cap is documented — connections, bodies, timers, workers, and fetch slots.

Deploy anywhere

Prebuilt binaries, containers, and bytecode for serverless, edge, and long-lived deployments.

Serverless first

Built for the shape of serverless.

Fairyfly is designed first for short-lived, memory-constrained workloads: fast cold starts, one binary, no npm install for the core, and a process that exits when the event loop drains.

  • Start fast: one binary with no runtime dependency tree.
  • Stay small: roughly 5 MB RSS for a hello-world server on Apple silicon.
  • Exit cleanly: no referenced work means the process stops.
handler.jsJavaScript
const port = Number(process.env.PORT ?? 3000);

http.serve({ port }, (url) => {
  if (url === "/health") return Response.json({ ok: true });
  return new Response("hello from fairyfly");
});
$ ff start

Fairyfly is a native POSIX binary: it fits container-based serverless platforms and edge VMs, not V8-isolate runtimes such as Cloudflare Workers.

Quick start

Serve, call, and store — with zero setup.

No package.json, no node_modules, no build step for the core. The server, client, and database are already in the binary.

  • Start an HTTP server with http.serve.
  • Call any API with the built-in fetch.
  • Persist data with embedded SQLite through Database.open.
Read the quickstart →
server.jsJavaScript
http.serve({ port: 3000 }, (url, method, body) => {
  if (url === "/json") return Response.json({ ok: true });
  if (url === "/echo" && method === "POST") return new Response(body);
  return new Response("not found", { status: 404 });
});
$ ff server.js

Performance

MicroVM benchmark: 10ms cold start, 165k RPS.

Four runtimes benchmarked inside isolated microVMs (2 vCPUs, 1 GB RAM): Phase 1 isolated cold-start telemetry vs. Phase 2 5,000,000 requests across 500 concurrency via oha.

165kwarm req / second
~10 msapp cold start
2.93 msp50 latency
5.01 msp99 latency
RuntimeApp Cold StartWarm RPSp50p99Slowest
Fairyfly10.4 ms164,967 /s2.93 ms5.01 ms25.5 ms
Bun80.6 ms163,610 /s2.94 ms5.29 ms21.8 ms
Deno79.4 ms163,566 /s2.93 ms5.27 ms117.2 ms
Node118.6 ms15,523 /s32.33 ms34.17 ms7,983 ms

2 vCPU / 1 GB RAM microVMs · 5M requests @ 500 concurrency via oha (100% success rate across all 5M requests) · Full microVM benchmark & telemetry →

Architecture

Small enough to read. Fast enough to ship.

About 24k lines of Zig across 54 files. One event loop, no hidden request threads, and a hot path designed for the common case.

  • 512-slot SoA connection table with packed 2-byte flags.
  • 64 KB static response staging per slot, spilling to the heap at 10 MB.
  • libxev event loop: io_uring on Linux, kqueue on macOS, epoll fallback.
How it works →
JavaScript source
↓
QuickJS parser + bytecode
↓
Zig API layer
↓
libxev · io_uring / epoll / kqueue

CLI

One binary for the whole workflow.

Scaffold, run, test, and ship with the same ff binary. No separate task runner or build tool.

ff init

Write ff.json and an entry file in one command.

ff init -y my-app

ff start

Run the project's main, with optional TLS cert and key.

ff start

ff test

Run test/*.test.js through the runtime.

ff test

ff compile

Ship bytecode with source and debug info stripped.

ff compile app.js -o app.ffbc

Full CLI reference →

Deploy

Ship it anywhere.

Ship a prebuilt binary, a container, or bytecode with the source stripped.

Prebuilt binary

macOS and Linux, installed to ~/.local/bin/ff with a SHA-256 check.

curl -fsSL https://www.fairyfly.dev/install.sh | sh

Docker

A container image with the runtime and tools ready to run.

docker run --rm ghcr.io/abdelmajidouhamane/ff:latest ff --version

Bytecode

Compile to .ffbc with source and debug info stripped.

ff compile app.js -o app.ffbc

Deployment guide → · Installation →

ES modules

Install packages without the baggage.

ff imprint installs pure-JS ESM packages, writes a lockfile with integrity hashes, and keeps one flat node_modules.

  • No CommonJS, require(), or node: imports.
  • One flat node_modules — conflicting versions fail loudly.
  • Direct GitHub, Codeberg, and tarball sources with integrity pins.
Package guide →
terminalShell
ff imprint marked@18.0.11
# adds marked to ff.json and ff.lock
# materializes node_modules/
then: import { marked } from "marked";

Get started

Start with one binary. Stay small.

Install Fairyfly, run a server, and measure the memory yourself.

Installcurl -fsSL https://www.fairyfly.dev/install.sh | sh