Skip to content

Examples

Every example on this page is a complete, compileable .weld file from the repository's examples/ directory — not a fragment. Each is shown in full, explained, and paired with the requests that exercise it. Compile and run any of them with:

sh
./weld examples/<name>.weld --emit bin -O ReleaseFast && ./<name>
# then, in another shell:
curl localhost:8080/...

Examples that talk to an external service (Postgres, an upstream, gRPC) read their endpoints from environment variables — set those first (shown per example).

Basics

hello.weld — routes, status, content type

The smallest useful service: static routes that set a content type and status and return text.

weld
route GET "/" {
    type "text/html"
    respond "<h1>Welcome to Weld</h1>\n"
}

route GET "/hello" {
    respond "Hello, world!\n"
}

route GET "/health" {
    status 200
    respond "OK\n"
}

route GET "/teapot" {
    status 418
    respond "I'm a little teapot\n"
}
sh
$ curl -i localhost:8080/            # Content-Type: text/html
$ curl localhost:8080/hello          # Hello, world!
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/teapot   # 418

calc.weld — expressions, bindings, interpolation

Everything inside {…} is a real, type-checked expression: arithmetic, comparisons, &&/||, let bindings, string concatenation, and a defaulted query parameter.

weld
route GET "/add/:a/:b" (a: int, b: int) {
    respond "sum={a + b} diff={a - b} prod={a * b} quot={a / b} mod={a % b}\n"
}

route GET "/cmp/:a/:b" (a: int, b: int) {
    let bigger = a > b
    respond "a>b: {bigger}, equal: {a == b}, in_range: {a > 0 && a < 100}\n"
}

route GET "/greet/:name" (name: string, query loud: bool = false) {
    let greeting = "Hello, " + name
    respond "{greeting}{loud}\n"
}
sh
$ curl localhost:8080/add/7/3        # sum=10 diff=4 prod=21 quot=2 mod=1
$ curl localhost:8080/cmp/9/4        # a>b: true, equal: false, in_range: true
$ curl 'localhost:8080/greet/Ada?loud=true'   # Hello, Adatrue

api.weld — typed params, wildcards, optional query

Typed path parameters (a bad int yields 400), an optional query param defaulted with ??, and a trailing wildcard that captures the remainder of the path.

weld
route GET "/" {
    type "text/html"
    respond "<h1>Weld API</h1>\n"
}

# :id is typed int; {id} interpolates it (bad int -> 400)
route GET "/users/:id" (id: int) {
    respond "user id = {id}\n"
}

# two int params plus an optional query param, defaulted with ??
route GET "/users/:id/posts/:post" (id: int, post: int, query q: string?) {
    respond "user {id}, post {post}, filter={q ?? "none"}\n"
}

# trailing wildcard captures the remainder of the path
route GET "/files/*path" {
    respond "serving file: {path}\n"
}

route GET "/health" {
    status 200
    respond "OK\n"
}
sh
$ curl localhost:8080/users/7/posts/99?q=recent   # user 7, post 99, filter=recent
$ curl localhost:8080/files/docs/readme.md         # serving file: docs/readme.md
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/users/abc   # 400

constants.weld — top-level let constants

Module-scoped let bindings are evaluated once at compile time and used by bare name from any route or function; a later one can build on an earlier one. Great for config defaults — a query parameter can even default to a constant.

weld
# Top-level `let` — module-scoped constants, evaluated at compile time and
# usable by bare name from any route or fn. A `let` may reference an earlier one.

let siteName = "Weld Demo"
let pageSize: int = 20
let maxPageSize = pageSize * 5
let taxRate = 0.08

type Page = { site: string, size: int, max: int }

fn label(n: int) -> string {
    return "{siteName}: showing {n} of up to {maxPageSize}"
}

route GET "/config" -> Page {
    respond Page { site: siteName, size: pageSize, max: maxPageSize }
}

route GET "/list" (query size: int = pageSize) -> string {
    # `size` is a query param defaulting to the pageSize constant.
    respond label(size)
}

route GET "/tax" -> string {
    respond "tax rate is {taxRate}"
}
sh
$ curl localhost:8080/config          # {"site":"Weld Demo","size":20,"max":100}
$ curl localhost:8080/list            # Weld Demo: showing 20 of up to 100
$ curl 'localhost:8080/list?size=5'   # Weld Demo: showing 5 of up to 100
$ curl localhost:8080/tax             # tax rate is 0.08

Types & data

users.weld — records and automatic JSON

Return a record or a list of records and Weld encodes it as JSON (application/json); return a string and it is sent as text. A route's -> T is the response contract.

weld
type User = {
    id: int,
    name: string,
    email: string?,      # optional field -> JSON null when absent
}

# Returns a single User, encoded as JSON automatically
route GET "/users/:id" (id: int) -> User {
    respond User { id: id, name: "Ada Lovelace", email: "ada@example.com" }
}

# Returns a JSON array of Users
route GET "/users" -> [User] {
    respond [
        User { id: 1, name: "Ada", email: "ada@example.com" },
        User { id: 2, name: "Alan Turing", email: null },
    ]
}

# Field access; returns a plain-text string
route GET "/users/:id/name" (id: int) -> string {
    let u = User { id: id, name: "Grace Hopper", email: null }
    respond u.name
}
sh
$ curl localhost:8080/users/42
{"id":42,"name":"Ada Lovelace","email":"ada@example.com"}
$ curl localhost:8080/users/1/name    # Grace Hopper

crud.weld — typed JSON request bodies

A body x: T parameter parses the JSON request body into a typed record (invalid JSON → 400).

weld
type NewUser = { name: string, email: string? }
type User = { id: int, name: string, email: string? }

# The JSON request body is parsed into a typed NewUser (bad JSON -> 400)
route POST "/users" (body input: NewUser) -> User {
    respond User { id: 1, name: input.name, email: input.email }
}

route GET "/users/:id" (id: int) -> User {
    respond User { id: id, name: "Ada Lovelace", email: "ada@example.com" }
}
sh
$ curl -X POST localhost:8080/users -d '{"name":"Ada","email":"ada@x.com"}'
{"id":1,"name":"Ada","email":"ada@x.com"}

store.weld — in-memory CRUD with state

state holds server-global, lock-guarded values that persist across requests. Values assigned into state are deep-copied into a durable allocator, so request-scoped strings survive.

weld
type NewUser = { name: string, email: string? }
type User = { id: int, name: string, email: string? }

state db {
    var users: [User] = []
    var nextId: int = 1
}

# Create: parse the body, assign an id, persist it
route POST "/users" (body input: NewUser) -> User {
    let user = User { id: db.nextId, name: input.name, email: input.email }
    db.users = push(db.users, user)
    db.nextId = db.nextId + 1
    status 201
    respond user
}

# List everything
route GET "/users" -> [User] {
    respond db.users
}

# Count
route GET "/count" {
    respond "count={len(db.users)}\n"
}
sh
$ curl -X POST localhost:8080/users -d '{"name":"Ada"}'   # {"id":1,...}
$ curl -X POST localhost:8080/users -d '{"name":"Alan"}'  # {"id":2,...}
$ curl localhost:8080/count                                # count=2

enums.weld — proto-style enums

Each variant has a number (required) and an optional string label; a hidden Unspecified = 0 is the fallback for unknown inputs. Role(x) reinterprets a number or string into the enum; int(r) / str(r) go back.

weld
enum Role {
    # Unspecified = 0 : "unspecified" is implicit
    Admin = 1 : "admin",
    User  = 2 : "user",
}

type Account = {
    id: int,
    role: Role,        # enum field -> JSON string
}

# Enum in a JSON record; Role.Admin is a variant reference
route GET "/accounts/:id" (id: int) -> Account {
    respond Account { id: id, role: Role.Admin }
}

# Reinterpret a path string into an enum, then back to int/string
route GET "/role/:name" (name: string) {
    let r = Role(name)                 # string -> enum (unknown -> Unspecified)
    respond "name={name} number={int(r)} string={str(r)}\n"
}

# Reinterpret a number into an enum
route GET "/role-num/:n" (n: int) {
    let r = Role(n)
    respond "number={n} -> {str(r)}\n"
}
sh
$ curl localhost:8080/accounts/1       # {"id":1,"role":"admin"}
$ curl localhost:8080/role/admin       # name=admin number=1 string=admin
$ curl localhost:8080/role/nope        # name=nope number=0 string=unspecified

sumtypes.weld — tagged unions & exhaustive match

A type is a record or a sum — a tagged union with positional payloads. Construct a variant qualified (Shape.Circle(2.0)); match it unqualified, binding the payload; a responded sum auto-encodes as {"tag":…,"values":[…]}.

weld
type Shape =
    | Circle(float)
    | Rect(float, float)
    | Empty

fn area(s: Shape) -> float {
    return match s {
        Circle(r)  => 3.14159 * r * r,
        Rect(w, h) => w * h,
        Empty      => 0.0,
    }
}

fn describe(s: Shape) -> string {
    return match s {
        Circle(r)  => "circle r={r}",
        Rect(w, h) => "rect {w}x{h}",
        Empty      => "empty",
    }
}

route GET "/area/circle/:r"   (r: float)            -> float  { respond area(Shape.Circle(r)) }
route GET "/area/rect/:w/:h"  (w: float, h: float)  -> float  { respond area(Shape.Rect(w, h)) }
route GET "/describe/rect/:w/:h" (w: float, h: float) -> string { respond describe(Shape.Rect(w, h)) }

# Build a shape by name and return it as JSON (exercises the tagged-union encoding).
route GET "/shape/:kind/:x" (kind: string, x: float) -> Shape {
    let s = match kind {
        "circle" => Shape.Circle(x),
        _        => Shape.Empty,
    }
    respond s
}
sh
$ curl localhost:8080/area/rect/3/4        # 12
$ curl localhost:8080/shape/circle/2.0     # {"tag":"Circle","values":[2]}
$ curl localhost:8080/shape/none/0         # {"tag":"Empty","values":[]}

Control flow & abstraction

features.weld — functions, for/var, match, indexing

User fns, builtins inside interpolation, var + assignment, for, push, list indexing, and match over both an enum and a string.

weld
enum Role { Admin = 1 : "admin", User = 2 }

fn double(x: int) -> int {
    return x * 2
}

fn greet(name: string) -> string {
    return "Hello, " + upper(name)
}

# user functions + builtins inside interpolation
route GET "/calc/:n" (n: int) {
    let d = double(n)
    respond "double={d} greet={greet("ada")} len={len("hello")}\n"
}

# for + var + assignment + push + indexing
route GET "/sum/:n" (n: int) {
    var total = 0
    var xs = [1, 2, 3]
    xs = push(xs, n)
    for x in xs {
        total = total + x
    }
    respond "total={total} count={len(xs)} first={xs[0]}\n"
}

# match on an enum
route GET "/role/:name" (name: string) {
    let r = Role(name)
    let label = match r {
        Role.Admin => "an admin",
        Role.User => "a user",
        _ => "unknown",
    }
    respond "role={str(r)} label={label}\n"
}

# match on a string
route GET "/hi/:lang" (lang: string) {
    let g = match lang {
        "es" => "Hola",
        "fr" => "Bonjour",
        _ => "Hello",
    }
    respond "{g}, {name(lang)}!\n"
}

fn name(lang: string) -> string {
    return match lang {
        "es" => "amigo",
        "fr" => "ami",
        _ => "friend",
    }
}
sh
$ curl localhost:8080/calc/5     # double=10 greet=Hello, ADA len=5
$ curl localhost:8080/sum/4      # total=10 count=4 first=1
$ curl localhost:8080/hi/es      # Hola, amigo!

iterator.weld — methods, mutation, custom iterators

A method is fn Type.name(self, …); mut self mutates the receiver in place. Any type with a next(mut self) -> T? method is iterable by for.

weld
type Counter = { n: int }

fn Counter.inc(mut self) {          # mutates the receiver
    self.n = self.n + 1
}
fn Counter.value(self) -> int {     # reads it (by value)
    return self.n
}

# An iterator that yields the numbers `from..to` (exclusive), one call at a time.
type Range = { cur: int, to: int }

fn Range.next(mut self) -> int? {
    if self.cur >= self.to {
        return null                 # null signals "finished"
    }
    let v = self.cur
    self.cur = self.cur + 1
    return v
}

# Uses both: a Counter counts how many odd numbers Range yields in [0, n).
route GET "/odds/:n" (n: int) {
    var odds = Counter { n: 0 }
    let r = Range { cur: 0, to: n }
    for x in r {
        if x % 2 != 0 {
            odds.inc()
        }
    }
    respond "there are {odds.value()} odd numbers in 0..{n}"
}
sh
$ curl localhost:8080/odds/10    # there are 5 odd numbers in 0..10

collections.weld — list builtins & indexed iteration

slice, concat, push, pop, reverse, and contains are built-in list operations; for i, x in xs yields an index alongside each element, and an empty [] on the right of ?? infers its element type from the left. The higher-order ops (map, filter, reduce) and sort/insert/removeAt are methods too, so they chain.

weld
# List builtins, indexed iteration, and empty-literal inference.

fn capLast(xs: [string], n: int) -> [string] {
    # slice(list, start, end) replaces a hand-rolled drop loop.
    if len(xs) <= n { return xs }
    return slice(xs, len(xs) - n, len(xs))
}

route GET "/tags" -> string {
    var tags = ["intro", "weld", "demo", "lists"]
    tags = concat(tags, ["extra"])       # join two lists
    tags = push(tags, "last")            # append one element
    tags = capLast(tags, 4)              # keep the final 4
    var recent = reverse(tags)           # newest first
    recent = pop(recent)                 # drop the newest

    var out = ""
    for i, t in recent {                 # indexed iteration
        out = out + "{i}:{t} "
    }
    respond "has-weld={contains(tags, "weld")} list={out}"
}

route GET "/merge" (query extra: string?) -> string {
    # An empty `[]` takes its element type from the `??` left side.
    var base: [string]? = null
    var items = base ?? []
    items = push(items, extra ?? "none")
    respond "count={len(items)} first={items[0]}"
}

route GET "/stats" -> string {
    let xs = [5, 3, 8, 1, 9, 2]
    # Collection ops are also methods, so they chain: filter -> map -> sort.
    let bigs = xs.filter(x => x > 3).map(x => x * 10)   # [50, 80, 90]
    let total = xs.reduce(0, (acc, x) => acc + x)       # 28
    let ranked = xs.sort()                              # [1, 2, 3, 5, 8, 9]
    let edited = xs.insert(0, 0).removeAt(3)            # insert 0 at front, drop index 3
    respond "n_big={len(bigs)} total={total} min={ranked[0]} max={ranked[len(ranked) - 1]} edited0={edited[0]}"
}
sh
$ curl localhost:8080/tags               # has-weld=false list=0:last 1:extra 2:lists
$ curl 'localhost:8080/merge?extra=hi'   # count=1 first=hi
$ curl localhost:8080/merge              # count=1 first=none
$ curl localhost:8080/stats              # n_big=3 total=28 min=1 max=9 edited0=0

generics.weld — generic types, methods, interfaces

type Name<T> is generic (arguments inferred at use); <T: Iface> constrains a parameter to an interface, checked structurally. Everything monomorphizes to Zig comptime generics.

weld
type Box<T> = { value: T }

fn Box<T>.get(self) -> T { return self.value }
fn Box<T>.replace(mut self, v: T) { self.value = v }

type Pair<A, B> = { first: A, second: B }

# An interface is a named set of methods; `Self` is the implementing type.
interface Show {
    fn show(self) -> string
}

type Point = { x: int, y: int }
fn Point.show(self) -> string { return "({self.x}, {self.y})" }   # Point satisfies Show

# `T: Show` requires the argument to implement Show — a compile error otherwise.
# Inside the generic body we can call the constrained interface method *through*
# the type parameter (`self.item.show()`), which dispatches to the concrete type.
type Labeled<T: Show> = { item: T }
fn Labeled<T: Show>.render(self) -> string {
    return "[" + self.item.show() + "]"
}

route GET "/demo" {
    var b = Box { value: 41 }          # Box<int>, inferred
    b.replace(b.get() + 1)
    let p = Pair { first: "answer", second: 42 }   # Pair<string, int>
    let lbl = Labeled { item: Point { x: 3, y: 4 } }
    respond "box={b.get()} pair={p.first}/{p.second} labeled={lbl.render()}"
}
sh
$ curl localhost:8080/demo    # box=42 pair=answer/42 labeled=[(3, 4)]

hashmap.weld — a generic HashMap written in Weld

The payoff of the whole language: a fully generic HashMap<K: Key, V> — generics, an interface bound, interface dispatch through the type parameter (key.hash()), a generic static constructor, expected-type inference, mutable arrays, and the hash builtin — with no built-in map involved.

weld
interface Key {
    fn hash(self) -> int
    fn equals(self, other: Self) -> bool
}

type Entry<K: Key, V> = { key: K?, val: V?, used: bool }
type HashMap<K: Key, V> = { slots: [Entry<K, V>] }

# A generic static constructor (no `self`). Its K, V are inferred from the call
# site's expected type — `HashMap.new(16)` below.
fn HashMap<K: Key, V>.new(cap: int) -> HashMap<K, V> {
    return HashMap { slots: filled(cap, Entry { key: null, val: null, used: false }) }
}

fn HashMap<K: Key, V>.set(mut self, key: K, value: V) {
    let cap = len(self.slots)
    var i = ((key.hash() % cap) + cap) % cap
    for _probe in self.slots {
        let e = self.slots[i]
        if !e.used { self.slots[i] = Entry { key: key, val: value, used: true } return }
        let ek = e.key else { return }
        if ek.equals(key) { self.slots[i] = Entry { key: key, val: value, used: true } return }
        i = (i + 1) % cap
    }
}

fn HashMap<K: Key, V>.get(self, key: K) -> V? {
    let cap = len(self.slots)
    var i = ((key.hash() % cap) + cap) % cap
    for _probe in self.slots {
        let e = self.slots[i]
        if !e.used { return null }
        let ek = e.key else { return null }
        if ek.equals(key) { return e.val }
        i = (i + 1) % cap
    }
    return null
}

# A concrete key type: any record that implements `hash` and `equals` works.
type Point = { x: int, y: int }
fn Point.hash(self) -> int { return hash(self.x) * 31 + hash(self.y) }
fn Point.equals(self, other: Point) -> bool { return self.x == other.x && self.y == other.y }

route GET "/lookup/:x/:y" (x: int, y: int) {
    var m: HashMap<Point, string> = HashMap.new(16)   # K, V inferred here
    m.set(Point { x: 1, y: 2 }, "origin-ish")
    m.set(Point { x: 3, y: 4 }, "somewhere")
    respond "({x},{y}) -> {m.get(Point { x: x, y: y }) ?? "(not found)"}"
}
sh
$ curl localhost:8080/lookup/3/4    # (3,4) -> somewhere
$ curl localhost:8080/lookup/9/9    # (9,9) -> (not found)

errors.weld — typed errors

A fn declares an error type with -> T ! E (E is a sum type); it fails with a variant and the fallible T! carries that typed value, so a caller can match on it. An unhandled typed error auto-maps to a 500 JSON body.

weld
type LookupError =
    | NotFound
    | Invalid(string)

# Returns a name, or fails with a *typed* error the caller can inspect.
fn lookup(id: int) -> string ! LookupError {
    if id < 0   { fail LookupError.Invalid("id must be non-negative") }
    if id > 100 { fail LookupError.NotFound }
    return "user-{id}"
}

route GET "/user/:id" (id: int) -> string {
    # Handle the typed error: pick a status and message per variant.
    let name = lookup(id) else e {
        let code = match e { NotFound => 404, Invalid(_) => 400 }
        let msg  = match e { NotFound => "no such user", Invalid(reason) => "bad request: {reason}" }
        status code
        respond msg
    }
    respond "found {name}"
}

# Without `else`, an unhandled typed error becomes a 500 with the error as JSON.
route GET "/raw/:id" (id: int) -> string {
    respond try lookup(id)
}
sh
$ curl localhost:8080/user/5      # found user-5
$ curl -s -w ' [%{http_code}]\n' localhost:8080/user/200   # no such user [404]
$ curl localhost:8080/raw/-1      # {"tag":"Invalid","values":["id must be non-negative"]}

tasks.weld — background work with every

An every N { } block runs its body on a fixed interval off the request path — ideal for periodic cleanup like expiring sessions or sweeping stale entries. It shares state (under the same lock as handlers) but has no request or response. Units are ms, s, m, h.

weld
# tasks.weld — background work with `every N { }`.
#
# An `every` block runs its body on a fixed interval off the request path — for
# periodic cleanup like expiring sessions or sweeping stale entries. It can read
# and write `state` (guarded by the same lock as handlers) and broadcast to hubs,
# but has no request/response (no respond/status/header/fail). Units: ms, s, m, h.
# (Not supported with the experimental --green runtime yet.)

type Session = { user: string, expires: int }

state store {
    var sessions: [string: Session] = [:]
}

let sessionTtlMs = 1800000     # 30 minutes

route GET "/login/:user" (user: string) -> string {
    let sid = uuid()
    store.sessions[sid] = Session { user: user, expires: now_ms() + sessionTtlMs }
    respond sid
}

route GET "/session/:sid" (sid: string) -> string {
    let s = store.sessions[sid] else { fail 401 "no such session" }
    respond "user={s.user} expires_in_ms={s.expires - now_ms()}"
}

route GET "/stats" -> string {
    respond "active_sessions={len(store.sessions)}"
}

# Every minute, drop sessions whose deadline has passed. Runs on its own thread;
# holds the state lock only for the sweep.
every 1m {
    let now = now_ms()
    var live: [string: Session] = [:]
    for id, s in store.sessions {
        if s.expires > now { live[id] = s }
    }
    store.sessions = live
}
sh
$ SID=$(curl -s localhost:8080/login/alice)   # a fresh session id (uuid)
$ curl "localhost:8080/session/$SID"          # user=alice expires_in_ms=1799xxx
$ curl localhost:8080/stats                   # active_sessions=1
# a minute after a session's 30-min TTL lapses, the sweep drops it

Streaming & files

stream.weld — chunked streaming with emit

emit streams a response chunk by chunk (HTTP chunked transfer); the full body is never buffered — each chunk is flushed as it is produced.

weld
route GET "/stream" {
    type "text/plain"
    emit "starting...\n"
    for x in [10, 20, 30] {
        emit "value = {x}\n"
    }
    emit "done\n"
}
sh
$ curl -N localhost:8080/stream    # lines arrive one at a time

streaming.weld — constant-memory request bodies

for chunk in request_body() reads the request body in fixed-size chunks straight off the socket, so the whole thing is never buffered — uploads far larger than MAX_BODY_SIZE cost only constant memory. A streaming route can't also bind a JSON body parameter (that needs the whole body); read what you need from the chunks instead.

weld
# streaming.weld — consume a large request body in constant memory.
#
# `for chunk in request_body()` reads the body in fixed-size chunks straight from
# the socket; each `chunk` is a slice of raw bytes and the whole body is never
# buffered. This handles uploads far larger than the buffered-body limit
# (MAX_BODY_SIZE) — the handler's memory use is constant regardless of size.
#
# A streaming route can't also declare a JSON `body` parameter (that needs the
# whole body); read what you need from the chunks instead.

route POST "/measure" -> string {
    var bytes = 0
    var chunks = 0
    for chunk in request_body() {
        bytes = bytes + len(chunk)
        chunks = chunks + 1
    }
    respond "received {bytes} bytes in {chunks} chunk(s)"
}
sh
$ head -c 10000000 /dev/zero | curl --data-binary @- localhost:8080/measure
# received 10000000 bytes in N chunk(s)

static.weld — serving static files

serve BASE SUB sends BASE/SUB with a content type inferred from the extension (404 if missing, 403 on a .. traversal). Pair it with a wildcard route.

weld
route GET "/" -> string {
    respond "see /assets/... for static files"
}

route GET "/assets/*path" (path: string) {
    serve "public" path                 # serves ./public/<path>
}
sh
$ mkdir -p public && echo 'body{color:teal}' > public/site.css
$ curl -i localhost:8080/assets/site.css        # Content-Type: text/css
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/assets/../secret   # 403

Requests & responses

headers.weld — response headers & cookies

header name value sets any response header; cookie name value emits a Set-Cookie with secure defaults (Path=/; HttpOnly; SameSite=Lax), and request_cookie reads one back. Together they express CORS, cache policy, and cookie sessions without hand-writing header strings.

weld
# headers.weld — response headers & cookies: CORS, caching, and cookie sessions.
#
# `header name value` sets an arbitrary response header; `cookie name value` sets a
# Set-Cookie with secure defaults (Path=/; HttpOnly; SameSite=Lax). request_cookie
# reads one back. Together these express CORS, cache policy, and cookie-based auth
# without touching a raw header string.

type Claims = { sub: string, exp: int }

let secret = "change-me-in-prod"

# CORS + caching on a plain JSON-ish response, set with `header`.
route GET "/api/data" -> string {
    header "Access-Control-Allow-Origin" "*"
    header "Cache-Control" "max-age=60"
    respond "some data"
}

# Log in: issue the session JWT as an HttpOnly cookie the browser sends back
# automatically. `header "Set-Cookie" …` would give full control (Max-Age, Secure).
route GET "/login/:user" (user: string) -> string {
    let claims = Claims { sub: user, exp: now_ms() / 1000 + 3600 }
    cookie "session" jwt_sign(claims, secret)
    respond "logged in as {user}"
}

# A later request carries the cookie; read + verify it. A missing/invalid token
# falls back to a 401 via `try` (jwt_verify) or the `else` on the optional.
route GET "/me" -> string {
    let token = request_cookie("session") else { fail 401 "not logged in" }
    let claims = try jwt_verify(Claims, token, secret)
    respond "you are {claims.sub}"
}

# Clear the session cookie by overwriting it with an immediate expiry.
route GET "/logout" -> string {
    header "Set-Cookie" "session=; Path=/; Max-Age=0"
    respond "logged out"
}
sh
$ curl -i localhost:8080/api/data      # Access-Control-Allow-Origin: *; Cache-Control: max-age=60
$ curl -i localhost:8080/login/alice   # Set-Cookie: session=<jwt>; Path=/; HttpOnly; SameSite=Lax
$ curl --cookie 'session=<jwt>' localhost:8080/me   # you are alice
$ curl -i localhost:8080/logout        # Set-Cookie: session=; Path=/; Max-Age=0

forms.weld — form bodies, uploads & required config

form_field reads a field from either a urlencoded or multipart/form-data body; upload / upload_filename pull a file part from a multipart body. Each returns an optional, so pair it with ?? or else. require "VAR" refuses to boot unless the env var is set.

weld
# forms.weld — HTML form bodies, file uploads, and required config.
#
# `require "VAR"` refuses to boot unless the env var is set (no more silent "").
# form_field reads a field from a urlencoded OR multipart/form-data body;
# upload / upload_filename read a file part from a multipart body. All return
# an optional (null when absent), so pair them with `??` or `else`.

require "APP_SECRET"

# Handles a classic HTML <form method=post> (application/x-www-form-urlencoded)
# as well as a multipart submission — form_field covers both encodings.
route POST "/contact" -> string {
    let name = form_field("name") ?? "anonymous"
    let message = form_field("message") else { fail 400 "message is required" }
    respond "thanks {name}, we received {len(message)} chars"
}

# A multipart file upload with an accompanying text field.
route POST "/upload" -> string {
    let file = upload("file") else { fail 400 "no file part named 'file'" }
    let filename = upload_filename("file") ?? "upload.bin"
    let note = form_field("note") ?? ""
    respond "stored {filename} ({len(file)} bytes), note: {note}"
}
sh
$ APP_SECRET=dev ./forms &                                   # require refuses to boot without it
$ curl --data 'name=Ada&message=hello' localhost:8080/contact
# thanks Ada, we received 5 chars
$ curl -F file=@photo.png -F note=hi localhost:8080/upload
# stored photo.png (NNN bytes), note: hi

Integrations

db.weld — native Postgres

A database block holds typed SQL querys, called db.name(args) (fallible). The driver speaks the Postgres v3 wire protocol directly — no libpq. $1, $2… bind from the typed params; columns map positionally onto the return record.

weld
type User = { id: int, name: string, email: string? }

database db {
    url env("DATABASE_URL")             # e.g. postgres://user@host:5432/dbname
    query findUser(id: int) "SELECT id, name, email FROM users WHERE id = $1" -> User
    query listUsers()       "SELECT id, name, email FROM users ORDER BY id"   -> [User]
}

route GET "/users/:id" (id: int) -> User {
    let u = db.findUser(id) else e {
        status 404
        respond "user {id} not found ({e.message})\n"
    }
    respond u
}

route GET "/users" -> [User] {
    respond try db.listUsers()
}
sh
$ export DATABASE_URL='postgres://user:pass@127.0.0.1:5432/mydb'
$ ./weld examples/db.weld --emit bin -O ReleaseFast && ./db &
$ curl localhost:8080/users/1

See Databases for auth, pooling, and cardinality (T / T? / [T]).

transaction.weld — atomic writes

transaction db { … } pins one pooled connection, wraps the body in BEGIN/COMMIT, and rolls back automatically if any query fails or a fail fires. Respond after the block.

weld
type Account = { id: int, balance: int }

database bank {
    url env("DATABASE_URL")
    query balanceOf(id: int) "SELECT id, balance FROM accounts WHERE id = $1" -> Account
    query debit(id: int, amt: int)  "UPDATE accounts SET balance = balance - $2 WHERE id = $1 RETURNING id, balance" -> Account
    query credit(id: int, amt: int) "UPDATE accounts SET balance = balance + $2 WHERE id = $1 RETURNING id, balance" -> Account
}

# Move `amt` from one account to another. Both updates commit together or not at all.
route POST "/transfer/:from/:to/:amt" (from: int, to: int, amt: int) -> Account {
    var result = Account { id: 0, balance: 0 }
    transaction bank {
        let src = try bank.balanceOf(from)
        if src.balance < amt { fail 400 "insufficient funds" }   # rolls back
        let _ = try bank.debit(from, amt)
        result = try bank.credit(to, amt)
    }
    respond result
}

route GET "/balance/:id" (id: int) -> Account {
    respond try bank.balanceOf(id)
}
sh
$ curl -X POST localhost:8080/transfer/1/2/50    # both updates commit, or neither

sqlite.weld — embedded SQLite

driver sqlite selects the bundled SQLite engine instead of Postgres — the url is a file path, opened in WAL mode on first use. Queries, positional ? parameters, RETURNING, and transaction blocks are identical to the Postgres driver; only the connection layer differs. The schema is a tracked migration, applied automatically at startup. See Databases for the full comparison.

weld
# sqlite.weld — an embedded SQL database with the bundled SQLite engine.
#
# `driver sqlite` selects the SQLite backend (Postgres is the default). The `url` is
# the database file path; it is opened in WAL mode on first use. Queries, positional
# `?` parameters, `RETURNING`, and `transaction` blocks all work exactly like the
# Postgres driver — only the connection layer differs. Run with `DB_PATH=app.db`.
#
# The schema lives in a `migration` (not a hand-run query): it is applied once,
# before serving, inside a transaction, and recorded in `_weld_migrations`.

type Todo = { id: int, title: string, done: int }

database db {
    driver sqlite
    url env("DB_PATH")

    migration "0001_todos" {
        up "CREATE TABLE todos(id INTEGER PRIMARY KEY, title TEXT, done INTEGER DEFAULT 0)"
        down "DROP TABLE todos"
    }

    query add(title: string) "INSERT INTO todos(title) VALUES (?) RETURNING id, title, done" -> Todo
    query complete(id: int) "UPDATE todos SET done = 1 WHERE id = ? RETURNING id, title, done" -> Todo
    query all() "SELECT id, title, done FROM todos ORDER BY id" -> [Todo]
}

route POST "/todos/:title" (title: string) -> Todo {
    respond try db.add(title)
}

route POST "/todos/:id/done" (id: int) -> Todo {
    respond try db.complete(id)
}

route GET "/todos" -> [Todo] {
    respond try db.all()
}
sh
$ export DB_PATH=app.db
$ ./weld examples/sqlite.weld --emit bin -O ReleaseFast && ./sqlite &
                                                # migration 0001_todos applied on startup
$ curl -X POST localhost:8080/todos/buy-milk   # {"id":1,"title":"buy-milk","done":0}
$ curl -X POST localhost:8080/todos/1/done     # {"id":1,"title":"buy-milk","done":1}
$ curl localhost:8080/todos                     # [{"id":1,"title":"buy-milk","done":1}]

gateway.weld — HTTP upstreams

An upstream calls an external REST API with typed requests/responses (std.http.Client). Calls are fallible; try propagates (auto 502), else handles.

weld
type User = { id: int, name: string, email: string? }

upstream backend {
    base env("BACKEND_URL")                       # e.g. http://127.0.0.1:9099
    header "Accept" "application/json"
    call getUser(id: int)  GET "/users/{id}" -> User
    call listUsers()       GET "/users"      -> [User]
}

# propagate failures automatically (try -> 502 on upstream error)
route GET "/proxy/:id" (id: int) -> User {
    respond try backend.getUser(id)
}

route GET "/all" -> [User] {
    let users = try backend.listUsers()
    respond users
}

# handle failures explicitly with `else`
route GET "/safe/:id" (id: int) -> User {
    let u = backend.getUser(id) else e {
        status 502
        respond "upstream unavailable (status={e.status}): {e.message}\n"
    }
    respond u
}
sh
$ export BACKEND_URL='http://127.0.0.1:9099'
$ curl localhost:8080/proxy/1

httpsapi.weld — HTTPS upstreams (real TLS)

An https:// base does a real TLS handshake and verifies the server certificate against the system CA bundle — no configuration.

weld
type DogImage = { message: string, status: string }

upstream dogs {
    base "https://dog.ceo"
    call random() GET "/api/breeds/image/random" -> DogImage
}

# GET /random-dog -> proxies the upStream<JSON> straight through.
route GET "/random-dog" -> DogImage {
    respond try dogs.random()
}
sh
$ ./weld examples/httpsapi.weld --emit bin -O ReleaseFast && ./httpsapi &
$ curl localhost:8080/random-dog     # proxies live JSON from dog.ceo over TLS

grpc.weld — gRPC over native HTTP/2

Protobuf messages are records with @field-numbers; a grpc block declares rpcs (unary and all three streaming modes). Server/bidi streams pair naturally with emit.

weld
type HelloRequest = { name: string @1, times: int @2 }
type HelloReply   = { message: string @1, length: int @2 }

grpc Greeter {
    addr env("GRPC_ADDR")                      # host:port, e.g. 127.0.0.1:50051
    rpc SayHello(HelloRequest) -> HelloReply
    rpc SayHelloStream(HelloRequest) -> Stream<HelloReply>          # server-streaming
    rpc SumLengths(Stream<HelloRequest>) -> HelloReply             # client-streaming
    rpc Chat(Stream<HelloRequest>) -> Stream<HelloReply>            # bidirectional
}

# HTTP route that fans out to the gRPC backend and returns the reply as JSON
route GET "/hello/:name" (name: string) -> HelloReply {
    respond try Greeter.SayHello(HelloRequest { name: name, times: 1 })
}

# gRPC server-stream -> HTTP chunked stream: one reply message -> one chunk
route GET "/hellostream/:name" (name: string) {
    type "text/plain"
    for reply in Greeter.SayHelloStream(HelloRequest { name: name, times: 3 }) {
        emit "{reply.message}\n"
    }
}

# client-streaming: send several requests, get a single aggregate reply back
route GET "/sum" -> HelloReply {
    respond try Greeter.SumLengths([
        HelloRequest { name: "ada", times: 1 },
        HelloRequest { name: "grace", times: 1 },
        HelloRequest { name: "turing", times: 1 },
    ])
}

# bidirectional: stream requests up, stream replies down -> HTTP chunks
route GET "/chat" {
    type "text/plain"
    for reply in Greeter.Chat([
        HelloRequest { name: "one", times: 1 },
        HelloRequest { name: "two", times: 1 },
        HelloRequest { name: "three", times: 1 },
    ]) {
        emit "{reply.message}\n"
    }
}
sh
$ export GRPC_ADDR='127.0.0.1:50051'
$ curl localhost:8080/hello/ada
$ curl -N localhost:8080/hellostream/ada    # one chunk per streamed reply

hub.weld — a WebSocket hub with auth & events

A hub is a WebSocket endpoint at /<HubName>. connect runs once (query params carry the JWT and the room to join); methods are id-correlated request/response calls; any method can push events to a filtered client set.

weld
type Claims = { sub: string, role: string }

# Any record is an event payload; the event name on the wire is the type name.
type ChatMsg = { from: string, text: string }
type Notice  = { text: string }

hub Chat {
    # Runs once, on connect. `fail` rejects the connection; `identify` records this
    # connection's identity (for direct messages); `join` adds it to a room.
    connect (query token: string, query room: string) injects (user: Claims) {
        user = try jwt_verify(Claims, token, env("JWT_SECRET"))
        identify(user.sub)
        join(room)
        Chat.clients.filter(c => c.subscribed(room))
            .send(Notice { text: "{user.sub} joined" })
    }

    # Broadcast to everyone in a room (fire-and-forget; arrives as an event).
    method say(room: string, text: string) {
        Chat.clients.filter(c => c.subscribed(room))
            .send(ChatMsg { from: user.sub, text: text })
    }

    # A direct message: target a single connection by its JWT identity.
    method dm(to: string, text: string) {
        Chat.clients.filter(c => c.identity == to)
            .send(ChatMsg { from: user.sub, text: text })
    }

    # Admin-only broadcast, filtered by the injected role.
    method announce(text: string) -> string {
        if user.role != "admin" { fail 403 "admins only" }
        Chat.clients.filter(c => c.user.role != "admin")
            .send(Notice { text: text })
        return "sent"
    }

    # A plain request/response method still works alongside events.
    method echo(text: string) -> string {
        return "you ({user.sub}) said: {text}"
    }
}

Connect to ws://host/Chat?token=<jwt>&room=<name>. The generated client gives you a typed ChatHub class. See WebSocket Hubs.

sessions.weld — issuing auth: bcrypt & JWT

The issuing side of authentication (auth.weld below shows the verifying side). password_hash / password_verify are bcrypt; jwt_sign / jwt_verify are HS256 and honor the standard exp claim, so an expired token is rejected 401 automatically. random_bytes + hex_encode mint opaque tokens.

weld
# sessions.weld — issuing side of auth with the crypto builtins.
# (auth.weld shows the verifying side: a decorator that checks a Bearer token.)
#
# password_hash / password_verify use bcrypt; jwt_sign / jwt_verify are HS256 and
# honor the standard `exp` claim — a request with an expired token is rejected
# 401 automatically. random_bytes + hex_encode mint opaque tokens.

type Credentials = { username: string, password: string }
type Claims = { sub: string, exp: int }
type Session = { token: string }

# The bcrypt hash per user; persisted in state, guarded by the state lock.
state accounts {
    var hashes: [string: string] = [:]
}

# In a real service this comes from the environment, not source.
let secret = "change-me-in-prod"
let sessionTtl = 3600

route POST "/register" (body creds: Credentials) -> Session {
    accounts.hashes[creds.username] = password_hash(creds.password)
    let claims = Claims { sub: creds.username, exp: now_ms() / 1000 + sessionTtl }
    respond Session { token: jwt_sign(claims, secret) }
}

route POST "/login" (body creds: Credentials) -> Session {
    let stored = accounts.hashes[creds.username] ?? ""
    if !password_verify(creds.password, stored) {
        fail 401 "invalid credentials"
    }
    let claims = Claims { sub: creds.username, exp: now_ms() / 1000 + sessionTtl }
    respond Session { token: jwt_sign(claims, secret) }
}

# Present the token as `?token=…`; an expired or tampered token fails `try` -> 401.
route GET "/me" (query token: string) -> string {
    let claims = try jwt_verify(Claims, token, secret)
    respond "you are {claims.sub}"
}

# An opaque random token (24 bytes -> 48 hex chars), e.g. for password resets.
route GET "/opaque-token" -> string {
    respond hex_encode(random_bytes(24))
}
sh
$ curl -X POST localhost:8080/register -d '{"username":"ada","password":"s3cret"}'
# {"token":"<jwt>"}
$ curl -X POST localhost:8080/login -d '{"username":"ada","password":"s3cret"}'
# {"token":"<jwt>"}
$ curl "localhost:8080/me?token=<jwt>"   # you are ada
$ curl localhost:8080/opaque-token       # 48 hex chars

auth.weld — decorators, JWT, injected variables

A decorator runs before the handler; it can fail to reject and assign the bindings in its injects clause, which become typed variables in the handler. Decorators stack.

weld
type Claims = { sub: string, role: string }

# `injects (user: Claims)` exposes a `user` binding to any route this decorates.
decorator require_role(role: string) injects (user: Claims) {
    # request_header returns string?; `else { ... }` runs when it is absent.
    let header = request_header("Authorization") else { fail 401 "missing authorization" }
    let token = trimPrefix(header, "Bearer ")
    # jwt_verify(Type, token, secret) HS256-verifies and decodes the payload.
    user = try jwt_verify(Claims, token, env("JWT_SECRET"))
    if user.role != role { fail 403 "forbidden" }
}

@require_role("admin")
route GET "/admin" -> Claims {
    respond user                       # `user` is in scope, typed as Claims
}

@require_role("member")
route GET "/me" {
    respond "you are {user.sub} ({user.role})"
}

route GET "/" {
    respond "public — no decorator"
}
sh
$ export JWT_SECRET=devsecret
$ curl localhost:8080/admin                                   # 401 (no token)
$ curl -H "Authorization: Bearer $TOKEN" localhost:8080/me    # you are ada (member)

ratelimit.weld — rate limiting written in Weld

No built-in primitive: a [string: Bucket] map in state, keyed by client IP, holds a fixed-window counter — composed from decorators, state, a map, now_ms, and fail.

weld
type Bucket = { count: int, window_start: int }

state limiter {
    var buckets: [string: Bucket] = [:]
}

decorator rate_limit(max: int, window_ms: int) {
    let ip = client_ip()
    let now = now_ms()
    # `m[key]` yields an optional (null if absent); `??` supplies a fresh bucket.
    let b = limiter.buckets[ip] ?? Bucket { count: 0, window_start: now }
    var count = b.count
    var start = b.window_start
    if now - start > window_ms {           # window elapsed -> reset
        start = now
        count = 0
    }
    count = count + 1
    limiter.buckets[ip] = Bucket { count: count, window_start: start }
    if count > max {
        fail 429 "rate limit exceeded for {ip}"
    }
}

@rate_limit(60, 60000)                     # 60 requests / minute, per client IP
route GET "/api/data" {
    respond "ok — tracking {len(limiter.buckets)} clients"
}
sh
$ for i in $(seq 1 61); do curl -s -o /dev/null -w '%{http_code} ' localhost:8080/api/data; done
# ...200 200 ... 429   (the 61st request in the window is rejected)

Testing & modules

tested.weld — in-language unit tests

test "name" { … assert cond … } blocks run by weld --emit test (they compile away in the server binary), so pure logic is testable without a running server.

weld
type Shape = Circle(float) | Rect(float, float) | Empty

fn area(s: Shape) -> float {
    return match s {
        Circle(r)  => 3.0 * r * r,     # (3.0, not pi, to keep the test exact)
        Rect(w, h) => w * h,
        Empty      => 0.0,
    }
}

fn clamp(x: int, lo: int, hi: int) -> int {
    if x < lo { return lo }
    if x > hi { return hi }
    return x
}

route GET "/area/:r" (r: float) -> float { respond area(Shape.Circle(r)) }

test "area of shapes" {
    assert area(Shape.Rect(3.0, 4.0)) == 12.0
    assert area(Shape.Empty) == 0.0
    assert area(Shape.Circle(2.0)) == 12.0
}

test "clamp bounds a value" {
    assert clamp(5, 0, 10) == 5
    assert clamp(-3, 0, 10) == 0
    assert clamp(99, 0, 10) == 10
}

test "string helpers" {
    assert upper("abc") == "ABC"
    assert contains("hello world", "world")
}
sh
$ ./weld examples/tested.weld --emit test    # compiles a Zig test file and runs it; exit 1 on failure

modular/ — a multi-file project with import

import merges each file's declarations into one program. Files are included once even via multiple paths (models.weld is imported by both files here), and cycles are safe. Compile the entry point (app.weld).

models.weld — shared types and helpers, no routes of its own:

weld
type User = { id: int, name: string, role: string }

enum Plan {
    Free = 1 : "free",
    Pro  = 2 : "pro",
}

fn describe(u: User) -> string {
    return "{u.name} (#{u.id}, {u.role})"
}

users.weld — user-facing routes; the User type and describe come from models.weld:

weld
import "models.weld"

route GET "/users/:id" (id: int) -> User {
    respond User { id: id, name: "alice", role: "admin" }
}

route GET "/users/:id/label" (id: int) -> string {
    let u = User { id: id, name: "bob", role: "member" }
    respond describe(u)
}

app.weld — the entry point that wires it together:

weld
import "users.weld"
import "models.weld"

route GET "/health" { respond "ok" }

route GET "/plans/:p" (p: int) -> string {
    respond str(Plan(p))
}
sh
$ ./weld examples/modular/app.weld --emit bin -O ReleaseFast && ./app &
$ curl localhost:8080/users/7/label    # bob (#7, member)
$ curl localhost:8080/plans/2          # pro

Generating a client

Any of these services also emits a typed frontend client from the same route declarations — no drift between server and client:

sh
./weld examples/users.weld --emit client -o api      # api.ts and api.js

See Generated Clients for the TypeScript/JS output, including typed hub clients for hub.weld.