Experimental - under active development

A JavaScript runtime
built for speed

Built from scratch in Zig for serverless workloads. One binary, zero dependencies, instant cold starts.

3ms runtime init
1.2MB binary size
4MB memory baseline
80K req/s
terminal
$ zig build -Doptimize=ReleaseFast

$ ./zig-out/bin/zigttp-server -e "function handler(r) { return Response.json({hello:'world'}) }"
listening on http://127.0.0.1:8080

$ curl http://localhost:8080/
{"hello":"world"}

What makes it different

Where Node.js and Deno optimize for generality, zigttp optimizes for a single use case: running a request handler as fast as possible, then getting out of the way.

Opinionated subset

TypeScript with the footguns removed. No classes, no this, no var, no while loops. Unsupported features fail at parse time with a suggested alternative, not at runtime with a cryptic stack trace.

JSX as a first-class primitive

The parser handles JSX and TSX directly. No Babel, no build step, no transpilation. Write server-rendered HTML with components and get back a string.

Compile-time evaluation

comptime() folds expressions at load time, modeled after Zig's comptime. Hash a version string, uppercase a constant, precompute a config value - all before the handler runs.

Native modules over polyfills

Common FaaS needs - JWT auth, JSON Schema validation, caching, crypto - are implemented in Zig and exposed as zigttp:* virtual modules with zero interpretation overhead.

Clean, predictable code

Functions in, responses out. No middleware chains, no decorator magic, no hidden state.

function handler(request) {
    if (request.url === "/api/health") {
        return Response.json({
            status: "ok",
            runtime: "zts",
            timestamp: Date.now(),
        });
    }

    return Response.json(
        { error: "Not Found" },
        { status: 404 }
    );
}
function HomePage() {
    return (
        <html>
            <head>
                <title>Hello World</title>
            </head>
            <body>
                <h1>Hello World</h1>
                <p>Welcome to zigttp!</p>
            </body>
        </html>
    );
}

function handler(request) {
    if (request.url === "/") {
        return Response.html(renderToString(<HomePage />));
    }
    return Response.text("Not Found", { status: 404 });
}
import { routerMatch } from "zigttp:router";

const routes = {
    "GET /":            getHome,
    "GET /health":      getHealth,
    "GET /users/:id":   getUser,
    "POST /echo":       postEcho,
};

function handler(req) {
    const match = routerMatch(routes, req);
    if (match) {
        req.params = match.params;
        return match.handler(req);
    }
    return Response.json(
        { error: "Not Found" },
        { status: 404 }
    );
}
import { parseBearer, jwtVerify } from "zigttp:auth";
import { schemaCompile, validateJson } from "zigttp:validate";

schemaCompile("user", JSON.stringify({
    type: "object",
    required: ["name", "email"],
    properties: {
        name:  { type: "string", minLength: 1 },
        email: { type: "string", minLength: 5 },
    }
}));

function handler(req) {
    const token = parseBearer(req.headers["authorization"]);
    if (!token) return Response.json(
        { error: "unauthorized" }, { status: 401 }
    );

    const auth = jwtVerify(token, "my-secret");
    if (!auth.ok) return Response.json(
        { error: auth.error }, { status: 401 }
    );

    if (req.method === "POST") {
        const result = validateJson("user", req.body);
        if (!result.ok) return Response.json(
            { errors: result.errors }, { status: 400 }
        );
        return Response.json({ user: result.value }, { status: 201 });
    }

    return Response.json({ user: auth.value });
}
function TodoForm() {
    return (
        <form
            hx-post="/todos"
            hx-target="#todo-list"
            hx-swap="beforeend">
            <input type="text" name="text" required />
            <button type="submit">Add Todo</button>
        </form>
    );
}

function handler(request) {
    if (request.url === "/" && request.method === "GET") {
        return Response.html(
            renderToString(<TodoForm />)
        );
    }

    if (request.url === "/todos" && request.method === "POST") {
        const html = renderToString(
            <div class="todo-item">New todo</div>
        );
        return Response.html(html);
    }

    return Response.text("Not Found", { status: 404 });
}

Performance

Powered by zts - a pure Zig JavaScript engine with a JIT compiler, NaN-boxed values, and hidden classes. It skips everything a FaaS handler doesn't need.

JS Engine Performance (vs QuickJS)

String Operations
63x faster
16.3M ops/s
Object Create
4.8x
8.1M ops/s
Property Access
3.9x
13.2M ops/s
HTTP Handler
3.1x
1.0M ops/s
Function Calls
2.4x
12.4M ops/s
String Concat
1.3x
8.3M ops/s
Array Operations
1.3x
8.7M ops/s

Cold Start Comparison

zigttp ~71ms embedded bytecode
zigttp ~83ms runtime parsing
Deno ~150-200ms V8 snapshot

Runtime initialization is only 3ms. macOS dynamic linker accounts for 80-90ms. Linux static builds target 18-33ms total cold start.

Two-layer architecture

zigttp is a server. zts is the engine. Both are written in Zig. Together they form a purpose-built runtime for handling HTTP requests with JavaScript.

zigttp server

  • HTTP/1.1 listener and parser
  • HandlerPool with lock-free slot acquisition
  • Static file serving with LRU cache
  • CLI with inline eval, CORS, memory limits
  • Build-time handler precompilation
  • Native outbound HTTP bridge

zts engine

  • Two-pass compiler (parse to IR, then bytecode)
  • Stack-based VM with baseline JIT (x86-64, ARM64)
  • NaN-boxed values with hidden classes
  • Polymorphic inline cache (8-entry PIC)
  • Generational GC with hybrid arena allocation
  • Native TypeScript/TSX stripping
1 HTTP request arrives
2 Pool acquires isolated runtime
3 Handler invoked with Request
4 Response sent, runtime released

Virtual modules

Import native Zig implementations with zero JS interpretation overhead. Common FaaS functionality, none of the npm baggage.

zigttp:auth

JWT signing and verification (HS256), bearer token parsing, webhook signature verification, timing-safe comparison.

parseBearer, jwtVerify, jwtSign, verifyWebhookSignature
zigttp:validate

JSON Schema validation with compile-once-validate-many pattern. Supports type, required, properties, min/max, enum.

schemaCompile, validateJson, validateObject, coerceJson
zigttp:cache

In-memory key-value store with namespace isolation, TTL expiry, LRU eviction. Persists across requests in the same pool slot.

cacheGet, cacheSet, cacheDelete, cacheIncr, cacheStats
zigttp:crypto

SHA-256 hashing, HMAC-SHA256, base64 encoding and decoding. All implemented in Zig with no OpenSSL dependency.

sha256, hmacSha256, base64Encode, base64Decode
zigttp:router

Pattern-matching HTTP router with path parameter extraction. Declare routes as an object, match with a single call.

routerMatch
zigttp:env

Environment variable access for configuration. Simple key-value interface for deployment secrets and config.

env

Get started

Requires Zig 0.16.0 or later.

1 Build

$ git clone https://github.com/srdjan/zigttp
$ cd zigttp
$ zig build -Doptimize=ReleaseFast

2 Run

# Inline handler
$ ./zig-out/bin/zigttp-server -e "function handler(r) { return Response.json({ok:true}) }"

# Or with a file
$ ./zig-out/bin/zigttp-server examples/handler.jsx

3 Deploy

# Production build with embedded handler
$ zig build -Doptimize=ReleaseFast -Dhandler=handler.js

# Container: scratch base, 1.2MB total
$ docker build -t myapp .

The language subset

zts implements ES5 with select ES6+ extensions. Every unsupported feature is detected at parse time with a helpful error message suggesting an alternative.

Supported

  • Strict mode, let/const
  • Arrow functions
  • Template literals
  • Destructuring, spread
  • for...of (arrays)
  • Optional chaining, nullish coalescing
  • Typed arrays
  • JSX/TSX (native parsing)
  • TypeScript type stripping
  • comptime() evaluation
  • range(start, end, step)

Intentionally excluded

  • class - use functions and objects
  • this / new - use closures
  • var - use let/const
  • while - use for...of with range()
  • try/catch - use Result types
  • async/await - sequential by design
  • Promises - one request at a time
  • Regular expressions
  • Event loop
  • require / dynamic import