Skip to content

srdjan/zigttp

Repository files navigation

zigttp - Serverless JavaScript Runtime using Zig and zts

Note: This project is experimental and under active development.

A serverless JavaScript runtime for FaaS (Function-as-a-Service) use cases, powered by zts - a pure Zig JavaScript engine. Designed for AWS Lambda, Azure Functions, Cloudflare Workers, and edge computing deployments.

Features

  • Instant cold starts: No JIT warm-up, predictable startup times
  • Small deployment package: Pure Zig, zero external dependencies
  • Request isolation: RuntimePool with pre-warmed contexts
  • Functional API: Response helpers similar to Deno/Fetch API
  • Safe by default: Strict mode JavaScript, sandboxed execution
  • TypeScript/TSX: Native type stripping with compile-time evaluation

Use Cases

  • AWS Lambda / Azure Functions / Cloudflare Workers style deployments
  • Edge computing with JavaScript handlers
  • Lightweight HTTP function handlers
  • Multi-tenant request processing

Quick Start

1. Build

zig build -Doptimize=ReleaseFast

2. Run

# Inline handler
./zig-out/bin/zigttp-server -e "function handler(r) { return Response.json({hello:'world'}) }"

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

# Test it
curl http://localhost:8080/

Usage

zigttp-server [options] <handler.js>
zigttp-server -e "<inline-code>"

Options:
  -p, --port <PORT>     Port to listen on (default: 8080)
  -h, --host <HOST>     Host to bind to (default: 127.0.0.1)
  -e, --eval <CODE>     Evaluate inline JavaScript handler
  -m, --memory <SIZE>   JS runtime memory limit (default: 256k)
  -q, --quiet           Disable request logging
  --help                Show help message

Handler API

Your handler must define a handler function that receives a request object and returns a Response.

Request Object

{
    method: string,     // "GET", "POST", etc.
    url: string,        // URL path (e.g., "/api/users")
    headers: object,    // HTTP headers
    body: string|null   // Request body (for POST/PUT)
}

Response Helpers

// Basic response
new Response(body, { status: 200, headers: {} })

// JSON response (sets Content-Type automatically)
Response.json(data, init?)

// Text response  
Response.text(text, init?)

// HTML response
Response.html(html, init?)

Example Handler

function handler(request) {
    // Simple routing
    if (request.url === '/') {
        return Response.html('<h1>Hello World</h1>');
    }

    if (request.url === '/api/echo') {
        return Response.json({
            method: request.method,
            url: request.url,
            body: request.body
        });
    }

    if (request.method === 'POST' && request.url === '/api/data') {
        var data = JSON.parse(request.body);
        return Response.json({ received: data, ok: true });
    }

    // 404 fallback
    return new Response('Not Found', { status: 404 });
}

JSX Support

zigttp-server includes a native JSX transformer for server-side rendering. Use .jsx files to write handlers with JSX syntax.

Basic JSX

// examples/jsx-simple.jsx
function handler(request) {
    var page = <div class="hello">Hello JSX!</div>;
    return Response.html(renderToString(page));
}

Components

function Card(props) {
    return (
        <div class="card">
            <h2>{props.title}</h2>
            <div>{props.children}</div>
        </div>
    );
}

function handler(request) {
    var page = <Card title="Welcome">Hello from JSX!</Card>;
    return Response.html(renderToString(page));
}

JSX Runtime API

  • h(tag, props, ...children) - Create virtual DOM node (used internally by transformer)
  • renderToString(node) - Render virtual DOM to HTML string
  • Fragment - Fragment component for grouping without wrapper element

JSX Features

Feature Example Output
Elements <div>text</div> <div>text</div>
Attributes <div class="foo"> <div class="foo">
Expressions <div>{value}</div> <div>...</div>
Components <Card title="x"/> Calls Card function
Fragments <>a</> a (no wrapper)
Self-closing <br/> <br />
Boolean attrs <input disabled/> <input disabled />

Full SSR Example

// examples/jsx-ssr.jsx
function Layout(props) {
    return (
        <html>
            <head><title>{props.title}</title></head>
            <body>
                <h1>{props.title}</h1>
                {props.children}
            </body>
        </html>
    );
}

function handler(request) {
    var page = (
        <Layout title="My App">
            <p>Method: {request.method}</p>
        </Layout>
    );
    return Response.html(renderToString(page));
}

TypeScript Support

zts includes a native TypeScript/TSX stripper that removes type annotations at load time. Use .ts or .tsx files directly without a separate build step.

Basic Usage

// handler.ts
interface Request {
    method: string;
    path: string;
    headers: Record<string, string>;
    body: string | null;
}

function handler(request: Request): Response {
    const data: { message: string } = { message: "Hello TypeScript!" };
    return Response.json(data);
}

Compile-Time Evaluation

The comptime() function evaluates expressions at load time and replaces them with literal values:

// Arithmetic
const x = comptime(1 + 2 * 3);              // -> const x = 7;

// String operations
const upper = comptime("hello".toUpperCase()); // -> const upper = "HELLO";

// Math functions
const pi = comptime(Math.PI);               // -> const pi = 3.141592653589793;
const max = comptime(Math.max(1, 5, 3));    // -> const max = 5;

// Hash function (FNV-1a)
const etag = comptime(hash("content-v1"));  // -> const etag = "a1b2c3d4";

// JSON parsing
const cfg = comptime(JSON.parse('{"a":1}')); // -> const cfg = ({a:1});

// TSX works too
const el = <div>{comptime(1+2)}</div>;      // -> <div>{3}</div>

Supported comptime Operations

Category Operations
Literals number, string, boolean, null, undefined, NaN, Infinity
Arithmetic + - * / % **
Bitwise | & ^ << >> >>>
Comparison == != === !== < <= > >=
Logical && || ??
Ternary cond ? a : b
Math PI, E, floor, ceil, round, sqrt, sin, cos, min, max, etc.
String length, toUpperCase, toLowerCase, trim, slice, split, replace, etc.
Built-in parseInt, parseFloat, JSON.parse, hash

Disallowed: variables, Date.now(), Math.random(), closures, assignments.

See docs/typescript-comptime-spec.md for the full specification.

JavaScript Subset

zts implements ES5 with some ES6+ extensions. Key limitations:

  • Strict mode only: No with, globals must be declared with var
  • No array holes: [1,,3] is a syntax error
  • No direct eval: Only global eval (1, eval)('code')
  • No value boxing: No new Number(1), new String('x')
  • Limited Date: Only Date.now() is available

Supported ES6+ features:

  • for...of (arrays only)
  • Typed arrays
  • \u{hex} in strings
  • Math.imul, Math.clz32, Math.fround, Math.trunc
  • Exponentiation operator (**)
  • String.prototype.codePointAt, replaceAll, trimStart, trimEnd
  • globalThis

Architecture

┌─────────────────────────────────────────────────────────────┐
│                     zigttp-server (Zig)                       │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ HTTP Server │──│ RuntimePool │──│  Native Bindings    │  │
│  │  (std.net)  │  │  (contexts) │  │ (console, Response) │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                    zts (Pure Zig)                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   Parser    │──│  Bytecode   │──│  Generational GC    │  │
│  │             │  │     VM      │  │ (Nursery + Tenured) │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Runtime Model

zts uses a generational garbage collector with:

  1. NaN-boxing for efficient value representation (64-bit tagged values)
  2. Hidden classes for inline caching (V8-style optimization)
  3. RuntimePool for request isolation in FaaS environments

The Result pattern throughout makes error handling explicit and prevents silent failures.

Project Structure

zigttp-server/
├── build.zig              # Zig build configuration
├── zts/                   # Pure Zig JavaScript engine
│   ├── parser/            # Two-pass parser with IR
│   │   ├── parse.zig      # Main parser (Pratt parser)
│   │   ├── tokenizer.zig  # Tokenizer
│   │   ├── codegen.zig    # Bytecode generation
│   │   └── ir.zig         # Intermediate representation
│   ├── interpreter.zig    # Stack-based bytecode VM
│   ├── value.zig          # NaN-boxing value representation
│   ├── object.zig         # Hidden classes, object system
│   ├── gc.zig             # Generational GC (nursery + tenured)
│   ├── heap.zig           # Size-class segregated allocator
│   ├── http.zig           # HTTP/JSX runtime for SSR
│   ├── pool.zig           # Lock-free runtime pooling
│   ├── builtins.zig       # Built-in JavaScript functions
│   ├── stripper.zig       # TypeScript/TSX type stripper
│   └── comptime.zig       # Compile-time expression evaluator
├── src/
│   ├── main.zig           # CLI entry point
│   ├── zruntime.zig       # RuntimePool, JS context management
│   ├── server.zig         # HTTP server implementation
│   ├── bindings.zig       # Native APIs (console, fetch, Deno)
│   └── jsx.zig            # JSX transformer
└── examples/
    ├── handler.jsx        # Example JSX handler
    ├── htmx-todo/         # HTMX Todo app example
    └── jsx-ssr.jsx        # Full SSR example

Building from Source

Prerequisites

  • Zig 0.16.0 or later (nightly)

Build Commands

# Debug build
zig build

# Release build (optimized)
zig build -Doptimize=ReleaseFast

# Run tests
zig build test              # Main runtime tests
zig build test-zts          # JS engine tests
zig build test-zruntime     # Native Zig runtime tests

# Run directly
zig build run -- -e "function handler(r) { return Response.json({ok:true}) }"

Extending with Native Functions

Add custom native functions callable from JavaScript by implementing the NativeFn signature in zts/object.zig:

// In bindings.zig or a custom module:

fn myNativeFunction(ctx: *zts.Context, this: zts.JSValue, args: []const zts.JSValue) !zts.JSValue {
    // Your implementation
    return zts.JSValue.fromInt(42);
}

// Register it via context.setGlobal()

See src/bindings.zig for examples of console, fetch, and Deno API implementations.

Performance for FaaS

  • Cold start: < 1ms to initialize runtime and load handler
  • Warm invocations: RuntimePool reuses pre-warmed contexts
  • Memory: 256KB default JS heap (configurable per function)
  • Deployment size: ~500KB binary, zero runtime dependencies

Deployment Patterns

# Single instance (Lambda-style)
./zigttp-server handler.js

# Multiple instances behind load balancer
# Each instance handles one request at a time for isolation

For high-throughput scenarios, deploy multiple instances. The small binary size and instant cold starts make horizontal scaling efficient.

License

MIT licensed.

Credits

  • zts - Pure Zig JavaScript engine (part of this project)
  • Zig programming language

About

Small Native Zig JavaScript runtime based on mquickjs

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages