Runtime and Performance
Pure-Zig execution for HTTP handlers, tuned for fast cold starts and tight memory.
zigttp
zigts strips TypeScript down to the parts the compiler can prove, so zigttp can rewrite, re-prove, and ship only correct handlers. One binary. No runtime surprises.
$ zigttp expert "add /health and ship it"
[compiler-agent] Planning handler patch...
[compiler-agent] Simulating edit against zigts rules...
[compiler-agent] Re-running proofs + contract extraction...
PROVEN 7/7 proofs
CONTRACT stable: no new secrets, hosts, or modules
DEPLOY ready: health-check (1.2MB)
The Loop
zigttp expert works because zigts cuts away the
dynamic parts the compiler cannot sign. The compiler proposes
the edit, checks it against the subset, reruns all 7 proofs,
and stops at code it can certify.
The thing writing the patch is the thing rejecting unsupported moves. No shell agent guessing around a separate compiler.
$ zigttp expert "add a health check endpoint with auth bypass"
[compiler-agent] Planning handler patch...
[compiler-agent] Simulating edit against zigts rules...
error[E0017]: `try/catch` is not supported in zigts
--> handler.ts:9:5
= help: use assert() for Result narrowing
[compiler-agent] Rewriting with assert + match...
[compiler-agent] Re-running proofs + contract extraction...
PROVEN 7/7 proofs
CONTRACT stable: no new secrets, hosts, or modules
Handler ready: health-check (1.2MB)
[compiler-agent] Created src/health-check.ts
[compiler-agent] Running zigts gen-tests health-check.ts...
[compiler-agent] Wrote 6 test cases to tests.jsonl (exhaustive paths)
What Ships
zigts makes handlers analyzable; the compiler-agent, proof engine, runtime, modules, and deploy pipeline build on that constraint.
Pure-Zig execution for HTTP handlers, tuned for fast cold starts and tight memory.
zigts trims TypeScript to an analyzable core, then adds
match, guard, and
comptime where proof needs them.
Seven static checks prove handler shape, narrow Results, and turn behavior into deployable contracts.
Capabilities live in native zigttp:* modules,
so every import stays explicit and measurable.
Deploy consumes the proven contract, blocks unsafe diffs, and escalates only when capability surface changes.
Keep handler code linear, overlap I/O underneath, then opt into durable runs when the workflow must survive crashes.
The compiler-agent edits inside the same proof system, while
zigts gen-tests turns exhaustive paths into
JSONL fixtures.
Embed zigttp from Zig or add virtual modules without giving up explicit configuration.
The Provable Subset
zigts removes the dynamic surface the compiler cannot sign. What stays is small enough to analyze and expressive enough to build real handlers.
import { method, pathname, params } from "zigttp:router";
const getUser = (id: string) =>
fetchSync(`https://api.example.com/users/${id}`)
|> Response.json;
function handler(req) {
return match (`${method(req)} ${pathname(req)}`) {
"GET /" => Response.json({ status: "ok" }),
"GET /health" => Response.json({ healthy: true }),
"GET /users/:id" => getUser(params(req).id),
"POST /echo" => Response.json(req.body),
_ => Response.json(
{ error: "Not Found" },
{ status: 404 }
),
};
}
import { parseBearer, jwtVerify } from "zigttp:auth";
import { schemaCompile, validateJson } from "zigttp:validate";
import { env } from "zigttp:env";
comptime(() => schemaCompile("user", JSON.stringify({
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
}
})));
function handler(req: Request): Response {
const token = parseBearer(req.headers["authorization"]);
if (!token) return Response.json(
{ error: "unauthorized" }, { status: 401 }
);
const auth = jwtVerify(token, env("JWT_SECRET") ?? "");
assert(auth.ok, Response.json(
{ error: auth.error }, { status: 401 }
));
return match (req.method) {
"POST" => {
const result = validateJson("user", req.body ?? "");
assert(result.ok, Response.json(
{ errors: result.errors }, { status: 400 }
));
Response.json({ user: result.value }, { status: 201 });
},
_ => Response.json({ user: auth.value }),
};
}
type Props = {
name: string;
count: number;
};
function Card(props: Props): JSX.Element {
return (
<div class="card">
<h1>Hello, {props.name}</h1>
<p>Count: {props.count}</p>
</div>
);
}
function handler(req: Request): Response {
const data: Props = { name: "World", count: 42 };
return Response.html(
renderToString(<Card name={data.name} count={data.count} />)
);
}
import { decodeForm } from "zigttp:decode";
import { schemaCompile } from "zigttp:validate";
comptime(() => schemaCompile("todo.create", JSON.stringify({
type: "object",
required: ["text"],
properties: {
text: { type: "string", minLength: 1 },
}
})));
function TodoForm(): JSX.Element {
return (
<form hx-post="/todos" hx-target="#todo-list" hx-swap="beforeend">
<input name="text" required="required" />
<button type="submit">Add</button>
</form>
);
}
function TodoItem(text: string): JSX.Element {
return <li class="todo">{text}</li>;
}
function handler(req: Request): Response {
if (req.method === "GET") {
return Response.html(renderToString(
<main>
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
<TodoForm />
<ul id="todo-list"></ul>
</main>
));
}
const todo = decodeForm("todo.create", req.body ?? "");
assert(todo.ok, Response.text("bad request", { status: 400 }));
return Response.html(renderToString(TodoItem(todo.value.text)));
}
import { guard } from "zigttp:compose";
import { parseBearer, jwtVerify } from "zigttp:auth";
import { env } from "zigttp:env";
distinct type Secret = string;
type Claims = { sub: string };
type AuthRequest = Request & { user?: Claims };
const jwtSecret: Secret = env("JWT_SECRET") ?? "";
const cors = (req: AuthRequest): Response | undefined =>
match (req.method) {
"OPTIONS" => Response.text("", {
status: 204,
headers: { "Access-Control-Allow-Origin": "*" }
}),
_ => undefined,
};
const requireAuth = (req: AuthRequest): Response | undefined => {
const token = parseBearer(req.headers["authorization"]);
if (!token) return Response.json(
{ error: "unauthorized" }, { status: 401 }
);
const auth = jwtVerify(token, jwtSecret);
if (!auth.ok) return Response.json(
{ error: auth.error }, { status: 403 }
);
req.user = auth.value;
return undefined;
};
const dashboard = (req: AuthRequest): Response =>
Response.json({ welcome: req.user?.sub ?? "guest" });
// Pipe: guards short-circuit, handler runs last
const handler = guard(cors) |> guard(requireAuth) |> dashboard;
import { run, step, waitSignal } from "zigttp:durable";
import { decodeJson } from "zigttp:decode";
import { schemaCompile } from "zigttp:validate";
comptime(() => schemaCompile("order", JSON.stringify({
type: "object",
required: ["item", "qty"],
properties: {
item: { type: "string" },
qty: { type: "number", minimum: 1 }
}
})));
function handler(req: Request): Response {
const body = decodeJson("order", req.body ?? "");
assert(body.ok, Response.json(
{ errors: body.errors }, { status: 400 }
));
return run(`order-${body.value.item}`, () => {
const charge = step("charge", () =>
fetchSync("https://pay.api/charge", {
method: "POST",
body: body.value
})
);
// Wait for external webhook confirmation
const confirm = waitSignal("payment-confirmed");
return Response.json({
status: "fulfilled",
chargeStatus: charge.status,
signal: confirm
});
});
}
No npm
Every capability ships as Zig. Imports stay explicit, sandboxes stay derivable, and npm stays out of the trust boundary.
zigttp:authparseBearer, jwtVerify, jwtSign, verifyWebhookSignature,
timingSafeEqual
zigttp:cryptosha256, hmacSha256, base64Encode, base64Decode
zigttp:ioparallel, race
zigttp:httpparseCookies, setCookie, negotiate, parseContentType,
cors
zigttp:durablerun, step, stepWithTimeout, sleep, sleepUntil, waitSignal,
signal
zigttp:cachecacheGet, cacheSet, cacheDelete, cacheIncr, cacheStats
zigttp:sqlsql, sqlOne, sqlMany, sqlExec
zigttp:serviceserviceCall
zigttp:envenv
zigttp:validateschemaCompile, validateJson, validateObject, coerceJson,
schemaDrop
zigttp:decodedecodeJson, decodeForm, decodeQuery
zigttp:urlurlParse, urlSearchParams, urlEncode, urlDecode
zigttp:iduuid, ulid, nanoid
zigttp:routerrouterMatch
zigttp:composeguard
zigttp:scopescope, using, ensure
zigttp:loglogDebug, logInfo, logWarn, logError
zigttp:textescapeHtml, unescapeHtml, slugify, truncate, mask
zigttp:timeformatIso, formatHttp, parseIso, addSeconds
zigttp:ratelimitrateCheck, rateReset
Four Commands
Install. Init. Prove. Deploy.
# 1. Install from GitHub
$ curl -fsSL https://zigttp.timok.com/install | sh
# 2. Create a handler
$ zigttp init my-handler
Created my-handler/handler.ts
Created my-handler/tests.jsonl
# 3. Work with the compiler-agent
$ cd my-handler && zigttp expert "add auth + validation"
PROVEN 7/7 proofs
# 4. Deploy
$ zigttp deploy
Packaged OCI image
Service provisioned
Good Questions
The questions that matter before you accept a restricted subset.
Node.js, Deno, and Bun run the full language. zigttp uses zigts, a restricted subset, so the compiler can prove handlers, derive least-privilege sandboxes, and replay runs deterministically. That guarantee needs a closed language surface.
zigttp runs seven compile-time checks across every path: returns, reachability, Result narrowing, match coverage, property access, dead code, and contract extraction. Those proofs classify the handler and feed runtime policy, sandboxing, and deploy.
No. zigttp ships 20 native zigttp:* modules in
Zig, and the compiler tracks every import. If a module does not
cover it, call the external API with fetchSync.
Use the README installer or download
v0.16.0-RC8 from GitHub Releases. Building from
source currently requires Zig 0.16.0.
Yes. zigttp is MIT licensed on GitHub, including the runtime, compiler, and virtual-module standard library.