<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Building Fitz]]></title><description><![CDATA[Fitz — a typed compiled language with HTTP, Postgres and OpenAPI as first-class syntax.]]></description><link>https://buildingfitz.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a426107e005e00ea94a72e7/3419ea25-fdff-48f7-a593-6a8b1de9e440.png</url><title>Building Fitz</title><link>https://buildingfitz.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 00:22:07 GMT</lastBuildDate><atom:link href="https://buildingfitz.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Presentando Fitz: un lenguaje donde HTTP, Postgres, JWT y WebSockets son parte de la sintaxis]]></title><description><![CDATA[TL;DR — Fitz es un lenguaje de programación nuevo, escrito en Rust, con compilador de tipado gradual. La premisa: en lugar de apilar FastAPI + SQLAlchemy + python-jose + Celery + Pydantic + uvicorn + ]]></description><link>https://buildingfitz.hashnode.dev/presentando-fitz-un-lenguaje-donde-http-postgres-jwt-y-websockets-son-parte-de-la-sintaxis</link><guid isPermaLink="true">https://buildingfitz.hashnode.dev/presentando-fitz-un-lenguaje-donde-http-postgres-jwt-y-websockets-son-parte-de-la-sintaxis</guid><category><![CDATA[Rust]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Martin Palopoli]]></dc:creator><pubDate>Fri, 10 Jul 2026 10:48:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a426107e005e00ea94a72e7/ec2efeb9-1c02-4e4f-a8bd-3de6e3d24189.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>TL;DR — Fitz es un lenguaje de programación nuevo, escrito en Rust, con compilador de tipado gradual. La premisa: en lugar de apilar FastAPI + SQLAlchemy + python-jose + Celery + Pydantic + uvicorn + Alembic + typer sobre Python, lo que cada una resuelve vive <strong>adentro del lenguaje</strong>: ruteo HTTP, generación de OpenAPI/AsyncAPI, async/await, autenticación con JWT, hashing de passwords, un ORM con driver Postgres escrito en Rust puro, migraciones de schema, WebSockets, cron, jobs en background, un CLI builder, healthchecks, observability con OpenTelemetry, secrets como tipos opacos, y un orquestador <code>fitz deploy</code>. Un solo binario. Cero deps externas para el stack core. <strong>Repo</strong>: <a href="https://github.com/Thegreekman76/fitz">github.com/Thegreekman76/fitz</a> · <strong>Docs</strong>: <a href="https://thegreekman76.github.io/fitz/">thegreekman76.github.io/fitz</a></p>
</blockquote>
<p>Hace años que vengo escribiendo APIs en Python — FastAPI más el elenco habitual: SQLAlchemy, python-jose para JWT, passlib para Argon2, Celery + Redis para jobs, Pydantic para validación, uvicorn para servir, alembic para migraciones. Cada API que entrego necesita más o menos las mismas nueve librerías, cada una con sus convenciones, sus breaking changes, su forma de integrarse con el resto.</p>
<p>En algún momento me hice la pregunta obvia: <strong>¿por qué no es esto el lenguaje y listo?</strong></p>
<p>Esa pregunta es Fitz.</p>
<h2>Cómo se ve Fitz</h2>
<p>Empecemos por la foto, después caminamos las piezas.</p>
<pre><code class="language-fitz">@server(43928)
fn main() =&gt; 0

type User { id: Int, email: Str, name: Str, role: Str }
type Credentials { email: Str, password: Str }
type LoginResponse { token: Str }

let SECRET = "demo-secret-cambiame-en-prod"
let ADA_HASH = hash.password("secret-ada-123")

@auth_provider
fn check_token(headers: Map&lt;Str, Str&gt;) -&gt; Result&lt;User&gt; {
    let auth: Str = match headers.get("authorization") {
        Ok(v) =&gt; v,
        Err(_) =&gt; return Err("falta header Authorization"),
    }
    let parts = auth.split(" ")
    if (parts.len() != 2 or parts[0] != "Bearer") {
        return Err("se esperaba 'Bearer &lt;token&gt;'")
    }
    let claims = jwt.decode(parts[1], SECRET)?
    return find_user(claims["email"])
}

@post("/login")
fn login(creds: Credentials) -&gt; LoginResponse {
    let user: User = match find_user(creds.email) {
        Ok(u) =&gt; u,
        Err(_) =&gt; return 401 { "error": "credenciales inválidas" },
    }
    if (not hash.verify(creds.password, ADA_HASH)) {
        return 401 { "error": "credenciales inválidas" }
    }
    let claims = { "email": user.email, "role": user.role }
    return LoginResponse { token: jwt.encode(claims, SECRET) }
}

@authenticated
@get("/me")
fn me(user: User) -&gt; User =&gt; user

@admin
@get("/admin/users")
fn admin_list(user: User) -&gt; List&lt;User&gt; { ... }
</code></pre>
<p>Lo que hace este código, <strong>sin un solo</strong> <code>import</code> <strong>ni una dependencia externa</strong>:</p>
<ul>
<li><p>Levanta un servidor HTTP en el puerto 43928.</p>
</li>
<li><p>Genera OpenAPI 3.1 automático en <code>/openapi.json</code>.</p>
</li>
<li><p>Sirve la UI de Scalar en <code>/docs</code> con botón "Authorize" funcional.</p>
</li>
<li><p>Firma y verifica JWT (HS256/384/512).</p>
</li>
<li><p>Hashea passwords con <strong>Argon2id</strong> (recomendación de OWASP, no bcrypt).</p>
</li>
<li><p>Valida estáticamente que cada <code>@authenticated</code>/<code>@admin</code> tenga un <code>@auth_provider</code> declarado, que el provider devuelva el tipo <code>User</code> correcto, y que los handlers <code>@admin</code> tengan un campo <code>role: Str</code> en el <code>User</code>.</p>
</li>
<li><p>Compila a un binario nativo con <code>fitz build</code>, con paridad bit-a-bit contra <code>fitz run</code>.</p>
</li>
</ul>
<p>La auth, el hashing, el JWT, el OpenAPI con el security scheme <code>bearerAuth</code>, las respuestas 401/403 — todo eso vive adentro del binario <code>fitz</code>. No hay <code>requirements.txt</code>, no hay <code>package.json</code>, no hay <code>Cargo.toml</code> del lado del usuario.</p>
<h2>Por qué "ciudadano de primera" importa</h2>
<p>"Ciudadano de primera clase" es una de esas frases que se gastan rápido. Lo digo concreto.</p>
<p>En FastAPI, <code>@app.get("/users")</code> es un método sobre una instancia. El framework es una librería de la cual hacés opt-in. El router es una estructura de datos Python. La autenticación es un <code>Depends(...)</code>. Nada de eso es visible para el type checker como algo especial — son simplemente llamadas a funciones y decoradores que producen metadata.</p>
<p>En Fitz, <code>@get("/users")</code> es un <strong>decorador que el compilador entiende</strong>. El checker valida el template del path, los tipos de los parámetros contra los path params, el tipo del body, el tipo de retorno. El generador de OpenAPI inspecciona el AST directamente — no introspeciona objetos de runtime, no necesita decoradores que se "registren" a sí mismos. El <code>User</code> que devolvés en tu handler es el mismo <code>User</code> que aparece en el schema generado y en la UI de Scalar.</p>
<p>Suena chico hasta que lo vivís una semana. Después dejás de pelear con "por qué Pydantic discrepa con SQLAlchemy sobre si este campo es opcional" y empezás a escribir endpoints.</p>
<h2>Las piezas</h2>
<h3>HTTP + OpenAPI + UI Scalar, todo automático</h3>
<pre><code class="language-fitz">type Post { id: Int, title: Str, body: Str, tags: List&lt;Str&gt; }

@get("/posts")
fn list_posts() -&gt; List&lt;Post&gt; { ... }

@post("/posts")
fn create_post(post: Post) -&gt; Post { ... }
</code></pre>
<p>Eso es todo lo que necesitás. <code>/openapi.json</code> y <code>/docs</code> (Scalar) aparecen solos. Path params (<code>/posts/{id}</code>) son tipados y coercionados. La deserialización del JSON del body chequea required, aplica defaults, valida nullables, rechaza campos extra. Podés desactivar con <code>@server(docs=false)</code>.</p>
<h3>WebSockets tipados, con AsyncAPI auto-generado</h3>
<pre><code class="language-fitz">type ChatMessage { from: Str, text: Str }

@server(43929, ws_heartbeat_secs=30)
fn main() =&gt; 0

@authenticated
@ws("/chat")
async fn chat(conn: WsConn&lt;ChatMessage&gt;, user: User) {
    loop {
        let msg = match conn.recv() {
            Ok(m) =&gt; m,
            Err(_) =&gt; break,
        }
        conn.broadcast(ChatMessage { from: user.name, text: msg.text })
    }
}
</code></pre>
<p>Cada frame se marshallea automáticamente desde y hacia el tipo declarado. La auth corre <strong>antes</strong> del upgrade WebSocket — token inválido devuelve 401 sin abrir el socket. El heartbeat con ping/pong mantiene la conexión viva más allá de los 60s default de Nginx. <code>/asyncapi.json</code> se genera solo (la spec hermana de OpenAPI para APIs event-driven). No conozco otro lenguaje que auto-genere AsyncAPI desde el código fuente tipado.</p>
<h3>Jobs en background y cron, sin Redis</h3>
<pre><code class="language-fitz">@cron("*/5 * * * *")
async fn cleanup_old_sessions() {
    db.exec("DELETE FROM sessions WHERE expires_at &lt; now()")
}

@background
async fn send_welcome_email(email: Str) {
    // cosa cara
}

@post("/signup")
fn signup(creds: Credentials) -&gt; User {
    let user = create_user(creds)
    spawn(send_welcome_email(user.email))  // fire-and-forget, Future&lt;Null&gt; tipado
    return user
}
</code></pre>
<p>Sin Celery. Sin Redis. Sin <code>celery worker -A app</code> corriendo al lado del <code>uvicorn</code>. El scheduler está en tu binario. Suficiente para el 90% de servicios — cuando lo superás, lo superás por una razón concreta, y eso es problema de Fase 11+.</p>
<h3>Un ORM nativo con driver Postgres en Rust puro</h3>
<p>Esta es la pieza de la que más orgulloso estoy, y la que más tiempo me llevó. Fitz tiene su propio driver de Postgres escrito en Rust — sin <code>libpq</code>, sin <code>tokio-postgres</code>, sin <code>sqlx</code>. El protocolo wire (v3.0), auth SCRAM-SHA-256, prepared statements, formato binario para 11 tipos OID — todo implementado desde el RFC.</p>
<pre><code class="language-fitz">@table("users")
type User {
    @primary id: Int,
    email: Str,
    name: Str,
    @has_many("Post", "user_id") posts: List&lt;Post&gt;,
}

@table("posts")
type Post {
    @primary id: Int,
    user_id: Int,
    title: Str,
    body: Str,
    @belongs_to user: User?,
}

@get("/users")
async fn list_users(db: DbConn) -&gt; List&lt;User&gt; {
    return User.all(db).preload("posts").await
}

@get("/users/{id}")
async fn get_user(db: DbConn, id: Int) -&gt; Result&lt;User&gt; {
    return User.where(fn(u) =&gt; u.id == id).first(db).await
}

@post("/users")
async fn create_user(db: DbConn, user: User) -&gt; User {
    return User.insert(db, user).await
}
</code></pre>
<p>La closure adentro de <code>.where(...)</code> se <strong>traduce a SQL parametrizado en tiempo de compilación</strong> — <code>fn(u) =&gt; u.id == id</code> se convierte en <code>WHERE id = $1</code>. Operadores como <code>.is_in([...])</code>, <code>.like(...)</code>, <code>.ilike(...)</code>, <code>.contains(...)</code>, más operadores JSONB como <code>.has_key(...)</code>, <code>.contains_json(...)</code> mapean a operadores nativos de Postgres. El eager loading con <code>.preload("posts")</code> dispara una sola query batched. Agregados (<code>.sum</code>/<code>.avg</code>/<code>.min</code>/<code>.max</code>/<code>.count</code>) y <code>GROUP BY</code> están soportados a través de un tipo separado <code>Aggregated&lt;Row&gt;</code>.</p>
<p>Esto compila a código nativo vía <code>fitz build</code>. El binario generado hace exactamente las mismas calls a Postgres. Cero overhead de runtime para el SQL — ya es constante en build-time, comparable en performance con Diesel o sqlx.</p>
<h4>¿Cuán rápido es de verdad? — cabeza-a-cabeza contra SQLAlchemy</h4>
<p>La promesa "cero overhead" es fácil de decir y fácil de inflar, así que el repo trae un <strong>bench reproducible</strong> entre dos boilerplates equivalentes (<a href="https://github.com/Thegreekman76/fitz/tree/main/boilerplates/api-postgres-fitz"><code>api-postgres-fitz</code></a> vs <a href="https://github.com/Thegreekman76/fitz/tree/main/boilerplates/api-postgres-python"><code>api-postgres-python</code></a>) — mismo Postgres, mismos endpoints, mismo shape de respuesta, mismo <code>docker compose</code>. Números headline en <strong>v0.10.13</strong> (Intel Core Ultra 7 155H, Docker 29.2.1, 30s sostenidos, concurrencia 10):</p>
<table>
<thead>
<tr>
<th>Métrica</th>
<th>Fitz ORM</th>
<th>Python + SQLAlchemy</th>
<th>Speedup</th>
</tr>
</thead>
<tbody><tr>
<td>Memory peak</td>
<td><strong>9.2 MB</strong></td>
<td>51 MB</td>
<td><strong>5.5× más eficiente</strong></td>
</tr>
<tr>
<td><code>GET /users</code> p50</td>
<td><strong>4.88 ms</strong></td>
<td>37.85 ms</td>
<td><strong>7.76×</strong></td>
</tr>
<tr>
<td><code>GET /users</code> RPS</td>
<td><strong>1944</strong></td>
<td>246</td>
<td><strong>7.91×</strong></td>
</tr>
<tr>
<td><code>GET /users/{id}</code> p50</td>
<td><strong>3.60 ms</strong></td>
<td>31.87 ms</td>
<td><strong>8.85×</strong></td>
</tr>
<tr>
<td><code>GET /users/{id}</code> RPS</td>
<td><strong>2604</strong></td>
<td>296</td>
<td><strong>8.80×</strong></td>
</tr>
<tr>
<td>Cold start</td>
<td><strong>0.14 s</strong></td>
<td>0.22 s</td>
<td>1.57×</td>
</tr>
<tr>
<td>Image size</td>
<td><strong>131 MB</strong></td>
<td>258 MB</td>
<td>2× más liviano</td>
</tr>
</tbody></table>
<p>Eso es <del>8× el throughput con ~5× menos memoria, en la misma máquina, sobre la misma red Docker, contra el mismo Postgres. Reproducí los números con <a href="https://github.com/Thegreekman76/fitz/tree/main/benchmarks/orm-vs-sqlalchemy"><code>bash benchmarks/orm-vs-sqlalchemy/run.sh</code></a> (</del>5–8 min con cache Docker caliente; requiere <code>oha</code> + <code>jq</code>). Metodología completa, output crudo y las partes donde la comparación es <em>in</em>justa para Fitz están en el <a href="https://github.com/Thegreekman76/fitz/blob/main/benchmarks/orm-vs-sqlalchemy/README.md">README del bench</a>.</p>
<h3>Interop con Python cuando lo necesitás</h3>
<pre><code class="language-fitz">from python import math, json

let radius = 5.0
let area: Float = math.pi * radius * radius

let parsed: Result&lt;Map&lt;Str, Any&gt;&gt; = match json.loads("{\"name\": \"ada\"}") {
    Ok(d) =&gt; Ok(d),
    Err(e) =&gt; Err("JSON malformado: {e}"),
}
</code></pre>
<p>SQLAlchemy, NumPy, pandas, lo que esté en PyPI — accesible desde Fitz con <code>from python import ...</code>. El runtime embebe CPython vía PyO3. Las excepciones Python se vuelven <code>Result::Err</code> automáticamente. Async Python (<code>asyncpg</code>, SQLAlchemy 2.x async) se bridgea al <code>.await</code> de Fitz transparente. Incluso podés hacer <code>fitz build --bundle-python</code> para entregar un binario con CPython embebido — no se necesita Python en la máquina destino.</p>
<p>Esto es intencional. Fitz no quiere reemplazar el ecosistema de Python — quiere darte un lenguaje mejor para la capa web mientras dejás abierta la puerta a todo lo que Python ya construyó.</p>
<h3>Async, finalmente sin color</h3>
<pre><code class="language-fitz">async fn fetch_user(id: Int) -&gt; Result&lt;User&gt; { ... }

async fn main() {
    let user = fetch_user(42).await?
    print("llegó {user.name}")
}
</code></pre>
<p><code>async</code>/<code>await</code> en el core, sobre runtime tokio. El operador <code>?</code> funciona a través de <code>Result&lt;T&gt;</code>. El type checker exige que <code>?</code> solo aparezca adentro de funciones que retornan <code>Result&lt;...&gt;</code>. Compila a <code>async fn</code> + <code>.await</code> en Rust — mismo modelo de ejecución, mismo executor multi-thread.</p>
<h3>CLI builder — el mismo lenguaje, herramientas de línea de comandos</h3>
<p>Fitz no es solo para servicios HTTP. El mismo compilador trae un CLI builder built-in, sin librerías:</p>
<pre><code class="language-fitz">@command("greet", desc="Saludar a una persona")
fn greet(name: Str, loud: Bool = false, count: Int = 1) -&gt; Int {
    let n = count
    while n &gt; 0 {
        if loud { print("HOLA, {name}!") } else { print("hola, {name}") }
        n = n - 1
    }
    return 0
}

@command("add", desc="Sumar dos números")
fn add(a: Int, b: Int) -&gt; Int {
    print("{a + b}")
    return 0
}
</code></pre>
<pre><code class="language-bash">$ ./mybin greet Ada --loud --count 3
HOLA, Ada!
HOLA, Ada!
HOLA, Ada!

$ ./mybin --help
USAGE: mybin &lt;command&gt; [ARGS] [OPTIONS]
COMMANDS:
    greet    Saludar a una persona
    add      Sumar dos números
</code></pre>
<p>Convención sobre decoración: params sin default son positional args, params con default son flags. <code>Bool</code> con <code>default = false</code> se vuelve <code>--flag</code>, otros tipos <code>--flag &lt;value&gt;</code>. Short flags auto-derivados (<code>--loud</code> → <code>-l</code>) con detección de conflictos. Help auto-generado, exit codes POSIX estándar. <strong>Paridad bit-a-bit</strong> entre <code>fitz run</code> (desarrollo) y <code>fitz build</code> (binario self-contained que dropeás en <code>/usr/local/bin</code>).</p>
<p>Es el mismo lenguaje. Mismo type checker. Mismo async/await. Mismo <code>Result&lt;T&gt;</code> para errores. Si tu herramienta necesita pegar a la DB, el ORM está ahí. Si necesita HTTP, <code>@get</code>/<code>@post</code> están ahí. La línea entre "servicio web" y "CLI tool" deja de ser una decisión de stack.</p>
<h3>Stack production-ready — del repo a producción</h3>
<p>Esto es lo que separa a Fitz de los lenguajes "prototipo interesante". Los servicios reales necesitan health checks, secrets, observability, y una forma de shippear. Todo eso es parte del lenguaje:</p>
<pre><code class="language-fitz">@server(43928)
fn main() =&gt; 0

// Auto-montados en GET /healthz y /readyz — Kubernetes-friendly.
@healthz
fn liveness() -&gt; Bool =&gt; true

@readyz
async fn readiness(db: DbConn) -&gt; Bool {
    return match db.exec("SELECT 1").await {
        Ok(_) =&gt; true,
        Err(_) =&gt; false,
    }
}

// Secret&lt;T&gt; nunca leakea a logs, imprime "***" en Display.
let db_url: Secret&lt;Str&gt; = secret("DATABASE_URL")
let log_level: Str = config("LOG_LEVEL", "info")

// Tracing + métricas con un decorador cada uno.
@trace(name="process_order")
@metric(name="orders")
async fn process(order: Order) -&gt; Result&lt;Receipt&gt; {
    // process_order_duration_seconds (histogram) y orders_calls_total
    // (counter) se populan automático al hacer drop del scope.
}

// Feature flags con dos fuentes: fitz.toml [flags] + env vars FITZ_FLAG_&lt;NAME&gt;.
@flag("new-checkout")
@post("/v2/checkout")
fn v2_checkout(body: Cart) -&gt; Receipt { ... }
</code></pre>
<p>Atrás de bambalinas:</p>
<ul>
<li><p><strong>HTTP access logs</strong> auto-emitidos con <code>trace_id</code>/<code>span_id</code> propagado a cada <code>log.info(...)</code> adentro del handler.</p>
</li>
<li><p><strong>Export OpenTelemetry OTLP</strong> con un solo env var: <code>OTEL_EXPORTER_OTLP_ENDPOINT</code>. Los spans fluyen a Jaeger/Tempo/Honeycomb. Sin el env var, cero overhead, cero llamadas de red.</p>
</li>
<li><p><strong>Endpoint</strong> <code>/metrics</code> Prometheus expone counters y histogramas — <code>@server(prometheus=true)</code> lo activa.</p>
</li>
<li><p><code>@flag</code> <strong>sobre handlers HTTP/WS</strong> retorna 404 cuando la flag está off — gate del hot path ANTES de middleware/auth.</p>
</li>
</ul>
<p>Deployando:</p>
<pre><code class="language-bash"># Genera Dockerfile + docker-compose.yml a partir del shape del programa.
fitz docker init

# Build del binario, build de imagen Docker, push al registry.
fitz deploy docker --tag mycorp/api:v1

# O levantá local con compose.
fitz deploy compose
</code></pre>
<p><code>fitz docker init</code> lee tu AST. Si hay <code>db.connect(...)</code>, agrega Postgres al compose. Si hay <code>@server(N)</code>, setea <code>EXPOSE N</code>. Si hay <code>@cron</code>, agrega <code>restart: unless-stopped</code>. Si hay <code>from python import ...</code>, elige <code>python:3.12-slim-bookworm</code> en lugar de distroless. <strong>Genera lo que vos escribirías a mano</strong>, lo commiteás, lo editás cuando lo necesites.</p>
<p>No conozco otro lenguaje donde deployment sea una feature del lenguaje. Acá lo es porque cada proyecto que entregué en Python terminaba con dos días debuggeando gotchas del Dockerfile.</p>
<h2>Las herramientas</h2>
<p>Esta es la parte que subestimé cuando arranqué. Un lenguaje sin buenas herramientas nace muerto. Acá lo que hay hoy:</p>
<ul>
<li><p><code>fitz run</code> — interpreta el archivo directo. El ciclo de feedback más rápido.</p>
</li>
<li><p><code>fitz build</code> — compila a binario nativo vía un proyecto Rust generado. La paridad bit-a-bit con <code>fitz run</code> es un requisito duro.</p>
</li>
<li><p><code>fitz check</code> — solo type checker, sin ejecución.</p>
</li>
<li><p><code>fitz test</code> — test runner built-in con decorador <code>@test</code> y <code>assert</code>, <code>assert_eq</code>, <code>assert_throws</code>. Output estilo cargo.</p>
</li>
<li><p><code>fitz dev</code> — hot reload. Watchea <code>*.fitz</code> y <code>fitz.toml</code>, mata y respawnea el child al cambio.</p>
</li>
<li><p><code>fitz fmt</code> — formatter opinionado, cero config. Preserva tus comentarios y líneas en blanco.</p>
</li>
<li><p><code>fitz lint</code> — 4 lints built-in con supresión <code>// @allow(&lt;nombre&gt;)</code>. Output estilo cargo-clippy.</p>
</li>
<li><p><code>fitz repl</code> — REPL interactivo con soporte multi-línea, <code>:type</code>, <code>:load</code>, historial persistente.</p>
</li>
<li><p><code>fitz openapi</code> — emite el schema OpenAPI sin levantar el server.</p>
</li>
<li><p><code>fitz db diff</code><strong>/</strong><code>migrate</code> — tooling de migraciones de schema. Diff entre la DB viva y los types <code>@table</code> en tu código, genera migraciones idempotentes, las aplicás con <code>fitz db migrate</code>. Mismo modelo que Alembic pero con los types como fuente de verdad.</p>
</li>
<li><p><code>fitz docker init</code><strong>/</strong><code>build</code> — genera el Dockerfile + <code>.dockerignore</code> + <code>docker-compose.yml</code> a partir del shape del programa, después <code>docker build</code> wrappeado.</p>
</li>
<li><p><code>fitz deploy docker</code><strong>/</strong><code>compose</code> — wrapper fino para shippear la imagen o levantar local con un solo comando.</p>
</li>
<li><p><strong>Extensión VSCode</strong> — diagnostics + hover + go-to-definition + autocomplete + <strong>signature help</strong> + <strong>format on save</strong> + <strong>inferencia bidireccional</strong> para callbacks, distribución multi-platform.</p>
</li>
<li><p><code>fitz new</code> + <code>fitz add</code> + <code>fitz remove</code> + <code>fitz update</code> — package manager con <code>fitz.toml</code>, lockfile, path deps, git deps.</p>
</li>
</ul>
<p>El LSP es real (<code>tower-lsp</code> adentro). El formatter es real (tu código hace round-trip por él). El test runner es real. Todo está dogfooded — escribo código Fitz con la misma extensión VSCode que entrego.</p>
<h2>Siendo honesto sobre el estado</h2>
<p>Esto es un proyecto de un solo desarrollador. Empecé aprendiendo Rust para construirlo. No voy a fingir que está listo para producción para cualquiera — esto es lo verdadero hoy (junio 2026, release v0.15.0):</p>
<p><strong>Lo que funciona end-to-end, con paridad bit-a-bit</strong> <code>fitz run</code> <strong>↔</strong> <code>fitz build</code><strong>:</strong></p>
<ul>
<li><p>Server HTTP con <code>@get</code>/<code>@post</code>/<code>@put</code>/<code>@delete</code>, OpenAPI auto, UI Scalar.</p>
</li>
<li><p>Chain de middleware con <code>@middleware(fn)</code> + CORS built-in.</p>
</li>
<li><p>Auth con JWT con <code>@auth_provider</code>/<code>@authenticated</code>/<code>@admin</code> + <code>@requires("role_custom")</code> para RBAC. Hashing de passwords con Argon2id. Token blacklist sobre Postgres para logout/refresh.</p>
</li>
<li><p>WebSockets con <code>WsConn&lt;T&gt;</code>, AsyncAPI auto, heartbeat, auth pre-upgrade.</p>
</li>
<li><p>Cron jobs con <code>@cron("expr")</code> (con retry, timezone, persistencia, catch-up), jobs background con <code>@background</code> + <code>spawn(...)</code>.</p>
</li>
<li><p>ORM Postgres con <code>@table</code>/<code>@primary</code>/<code>@column</code>/<code>@belongs_to</code>/<code>@has_many</code>, closure-to-SQL, eager loading, <strong>transacciones</strong> (<code>db.transaction(fn)</code>), <strong>migraciones de schema</strong> (<code>fitz db diff</code>/<code>migrate</code>).</p>
</li>
<li><p>TLS estricto para Postgres (<code>sslmode=require</code>).</p>
</li>
<li><p>Async/await sobre tokio.</p>
</li>
<li><p>Interop Python con <code>from python import ...</code>, incluyendo bridge automático para async.</p>
</li>
<li><p><strong>CLI builder</strong> con <code>@command</code> — mismo lenguaje para CLI tools.</p>
</li>
<li><p><strong>Stack production</strong>: <code>@healthz</code>/<code>@readyz</code>, <code>Secret&lt;T&gt;</code>, <code>secret()</code>/<code>config()</code>, <code>@trace</code>/<code>@metric</code>, <code>@flag</code>, export OpenTelemetry OTLP, endpoint Prometheus <code>/metrics</code>, <code>fitz docker init/build</code>, <code>fitz deploy</code>.</p>
</li>
<li><p>Package manager con path deps y git deps.</p>
</li>
<li><p>Tooling completo: LSP (con signature help, format on save, hover sobre params y bindings), fmt, test, dev, repl, lint.</p>
</li>
</ul>
<p><strong>Lo que todavía no está en la caja:</strong></p>
<ul>
<li><p>Frontend en <code>.fitz</code> (single-file components, SSR). Roadmap (Fase 11) — la apuesta más ambiciosa del proyecto. Sin arrancar.</p>
</li>
<li><p>Un registry público de paquetes. Path deps y git deps funcionan hoy; el registry está en pausa hasta que aparezca demanda real.</p>
</li>
<li><p>Targets de <code>fitz deploy</code> más allá de <code>docker</code>/<code>compose</code> (todavía no hay wrapper de <code>fly</code>/<code>railway</code>/<code>k8s</code> — usá los CLIs nativos).</p>
</li>
<li><p>Debugging interactivo en VSCode (Debug Adapter Protocol). Workarounds: <code>print</code>, REPL <code>:type</code>/<code>:env</code>, diagnostics LSP. Trackeado como V6 en el backlog.</p>
</li>
</ul>
<p><strong>Lo que es estable</strong>: ~3030 tests unit de Rust + 13 LSP E2E + 360 compile E2E (smoke sobre cada ejemplo de la guía) + ~140 más entre otras suites corriendo en CI en cada push. Clippy <code>-D warnings</code> limpio.</p>
<h2>Cómo probarlo</h2>
<pre><code class="language-bash"># Instalación en Linux / macOS / WSL
curl -sSf https://thegreekman76.github.io/fitz/install.sh | sh

# Instalación en Windows (PowerShell)
irm https://thegreekman76.github.io/fitz/install.ps1 | iex

# O bajá un binario desde GitHub
# https://github.com/Thegreekman76/fitz/releases

# Reabrí la terminal para que el cambio de PATH aplique, después:
fitz --version
</code></pre>
<p><strong>Extensión VSCode</strong> (recomendado — syntax highlighting, hover con tipos, autocomplete, signature help, format on save): bajá el <code>.vsix</code> de tu plataforma desde la misma <a href="https://github.com/Thegreekman76/fitz/releases">página de releases</a> (<code>fitz-lang-&lt;plataforma&gt;.vsix</code>) e instalala con <code>code --install-extension fitz-lang-&lt;plataforma&gt;.vsix --force</code>. El Language Server viene incluido — no hace falta instalarlo aparte. Recargá VSCode una vez.</p>
<p>Primer server:</p>
<pre><code class="language-bash">fitz new mi-api --http
cd mi-api
fitz dev
</code></pre>
<p>Vienen ocho boilerplates en el repo bajo <code>boilerplates/</code>:</p>
<ul>
<li><p><code>api-simple</code> — API HTTP mínima.</p>
</li>
<li><p><code>api-middleware-cors</code> — chain de middleware + config de CORS.</p>
</li>
<li><p><code>api-postgres-fitz</code> — ORM + Postgres, Dockerizado.</p>
</li>
<li><p><code>api-postgres-python</code> — Postgres vía interop Python/SQLAlchemy.</p>
</li>
<li><p><code>api-websocket</code> — chat WebSocket tipado.</p>
</li>
<li><p><code>api-orm-full</code> — el showcase completo: auth + ORM + WebSockets + cron + jobs.</p>
</li>
<li><p><code>api-fullstack-postgres</code> — backend + frontend mínimo en un binario.</p>
</li>
<li><p><code>cli-tool</code> — app CLI con <code>@command</code> (sin HTTP).</p>
</li>
</ul>
<p>Cada uno corre con <code>docker compose up</code> o <code>fitz dev</code>. El README tiene la matriz completa.</p>
<h2>Por qué construí esto</h2>
<p>Vivo en El Chaltén, en la Patagonia argentina. El Fitz Roy es la torre de granito que define el horizonte acá. Borges escribió que vivimos en un país donde el pasado es incierto y solo el futuro es real. Creo que también vale para los lenguajes de programación: el pasado está lleno de workarounds acumulados por features que faltan en el lenguaje, y el futuro es lo que vos decidís construir.</p>
<p>Llevo diez años escribiendo código de APIs en Python. Amo FastAPI. Pero cada vez que arranco un proyecto nuevo, las primeras tres horas se van pegando librerías para hacer lo mismo que hice la semana pasada. En algún punto la pregunta se vuelve: ¿cómo sería un lenguaje que arrancara desde este conjunto de necesidades en 2026, en lugar de hacerlas crecer como parches sobre un lenguaje diseñado para scripting de shell en 1991?</p>
<p>Eso es Fitz.</p>
<p>No está terminado. Soy uno solo. Va a llegar.</p>
<p><strong>Repo</strong>: <a href="https://github.com/Thegreekman76/fitz">github.com/Thegreekman76/fitz</a></p>
<p><strong>Docs y curso</strong>: <a href="https://thegreekman76.github.io/fitz/">thegreekman76.github.io/fitz</a></p>
<p><strong>Guía</strong> (34 capítulos): <a href="https://thegreekman76.github.io/fitz/guide/">thegreekman76.github.io/fitz/guide/</a></p>
<p><strong>Roadmap</strong>: <a href="https://github.com/Thegreekman76/fitz/blob/main/docs/roadmap.md">docs/roadmap.md</a></p>
<p><strong>CHANGELOG</strong>: <a href="https://github.com/Thegreekman76/fitz/blob/main/CHANGELOG.md">CHANGELOG.md</a> — cada release con detalle.</p>
<p><strong>Issues</strong>: <a href="https://github.com/Thegreekman76/fitz/issues">github.com/Thegreekman76/fitz/issues</a></p>
<p>Si lo probás, quiero saber qué se rompió. Abrí un issue o una discussion en GitHub.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing Fitz: a language where HTTP, Postgres, JWT, and WebSockets are part of the syntax]]></title><description><![CDATA[TL;DR — Fitz is a new programming language built in Rust, with a gradually-typed compiler. The pitch: instead of stacking FastAPI + SQLAlchemy + python-jose + Celery + Pydantic + uvicorn + Alembic + t]]></description><link>https://buildingfitz.hashnode.dev/introducing-fitz-a-language-where-http-postgres-jwt-and-websockets-are-part-of-the-syntax</link><guid isPermaLink="true">https://buildingfitz.hashnode.dev/introducing-fitz-a-language-where-http-postgres-jwt-and-websockets-are-part-of-the-syntax</guid><category><![CDATA[Rust]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Martin Palopoli]]></dc:creator><pubDate>Tue, 07 Jul 2026 11:44:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a426107e005e00ea94a72e7/36ee4a74-c7a4-4740-a3d1-2f3cd8e71e6d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TL;DR — Fitz is a new programming language built in Rust, with a gradually-typed compiler. The pitch: instead of stacking FastAPI + SQLAlchemy + python-jose + Celery + Pydantic + uvicorn + Alembic + typer on top of Python, the things they each solve live <strong>inside the language</strong>: HTTP routing, OpenAPI/AsyncAPI generation, async/await, JWT auth, password hashing, an ORM with a pure-Rust Postgres driver, schema migrations, WebSockets, cron, background jobs, a CLI builder, healthchecks, observability with OpenTelemetry, secrets as opaque types, and a <code>fitz deploy</code> orchestrator. One binary. Zero external deps for the core stack. <strong>Repo</strong>: <a href="https://github.com/Thegreekman76/fitz">github.com/Thegreekman76/fitz</a> · <strong>Docs</strong>: <a href="https://thegreekman76.github.io/fitz/">thegreekman76.github.io/fitz</a></p>
<p>I've been building web APIs in Python for years — FastAPI plus the usual cast: SQLAlchemy, python-jose for JWT, passlib for Argon2, Celery + Redis for background jobs, Pydantic for validation, uvicorn for serving, alembic for migrations. Every API I ship needs roughly the same nine libraries, each with its own conventions, its own breaking changes, its own way to integrate with the others.</p>
<p>At some point I asked myself the obvious question: <strong>why isn't this just the language?</strong></p>
<p>That question is Fitz.</p>
<h2>What Fitz looks like</h2>
<p>Let's start with the picture, then walk through the pieces.</p>
<pre><code class="language-fitz">@server(43928)
fn main() =&gt; 0

type User { id: Int, email: Str, name: Str, role: Str }
type Credentials { email: Str, password: Str }
type LoginResponse { token: Str }

let SECRET = "demo-secret-change-me-in-prod"
let ADA_HASH = hash.password("secret-ada-123")

@auth_provider
fn check_token(headers: Map&lt;Str, Str&gt;) -&gt; Result&lt;User&gt; {
    let auth: Str = match headers.get("authorization") {
        Ok(v) =&gt; v,
        Err(_) =&gt; return Err("missing Authorization header"),
    }
    let parts = auth.split(" ")
    if (parts.len() != 2 or parts[0] != "Bearer") {
        return Err("expected 'Bearer &lt;token&gt;'")
    }
    let claims = jwt.decode(parts[1], SECRET)?
    return find_user(claims["email"])
}

@post("/login")
fn login(creds: Credentials) -&gt; LoginResponse {
    let user: User = match find_user(creds.email) {
        Ok(u) =&gt; u,
        Err(_) =&gt; return 401 { "error": "invalid credentials" },
    }
    if (not hash.verify(creds.password, ADA_HASH)) {
        return 401 { "error": "invalid credentials" }
    }
    let claims = { "email": user.email, "role": user.role }
    return LoginResponse { token: jwt.encode(claims, SECRET) }
}

@authenticated
@get("/me")
fn me(user: User) -&gt; User =&gt; user

@admin
@get("/admin/users")
fn admin_list(user: User) -&gt; List&lt;User&gt; { ... }
</code></pre>
<p>What this code does, <strong>without a single</strong> <code>import</code> <strong>or external dependency</strong>:</p>
<ul>
<li><p>Starts an HTTP server on port 43928.</p>
</li>
<li><p>Auto-generates OpenAPI 3.1 at <code>/openapi.json</code>.</p>
</li>
<li><p>Auto-serves Scalar UI at <code>/docs</code> with a working "Authorize" button.</p>
</li>
<li><p>Signs and verifies JWT tokens (HS256/384/512 supported).</p>
</li>
<li><p>Hashes passwords with <strong>Argon2id</strong> (OWASP recommendation, not bcrypt).</p>
</li>
<li><p>Statically validates that every <code>@authenticated</code>/<code>@admin</code> handler has an <code>@auth_provider</code> declared, that the provider returns the right <code>User</code> type, and that <code>@admin</code> handlers have a <code>role: Str</code> field on the <code>User</code>.</p>
</li>
<li><p>Compiles to a single native binary with <code>fitz build</code>, with bit-for-bit parity against <code>fitz run</code>.</p>
</li>
</ul>
<p>The auth, the hashing, the JWT, the OpenAPI with <code>bearerAuth</code> security scheme, the 401/403 responses — all of that is in the binary <code>fitz</code> itself. There's no <code>requirements.txt</code>, no <code>package.json</code>, no <code>Cargo.toml</code> for the user.</p>
<h2>Why "first-class" matters</h2>
<p>"First-class citizen" is one of those phrases that gets thrown around. Here's what I mean concretely.</p>
<p>In FastAPI, <code>@app.get("/users")</code> is a method on an object instance. The framework is a library you opt into. The router is a Python data structure. Authentication is a <code>Depends(...)</code>. None of those things are visible to the type checker as anything special — they're just function calls and decorators that happen to produce metadata.</p>
<p>In Fitz, <code>@get("/users")</code> is a <strong>decorator the compiler understands</strong>. The checker validates the path template, the parameter types against the path params, the body type, the return type. The OpenAPI generator inspects the AST directly — it doesn't introspect runtime objects, it doesn't need decorators that "register" themselves. The <code>User</code> you return in your handler is the same <code>User</code> that appears in the generated schema and in the Scalar UI.</p>
<p>This sounds like a small distinction until you live it for a week. Then you stop fighting "why does Pydantic disagree with SQLAlchemy about whether this field is optional" and you start writing endpoints.</p>
<h2>The pieces</h2>
<h3>HTTP + OpenAPI + Scalar UI, all auto</h3>
<pre><code class="language-fitz">type Post { id: Int, title: Str, body: Str, tags: List&lt;Str&gt; }

@get("/posts")
fn list_posts() -&gt; List&lt;Post&gt; { ... }

@post("/posts")
fn create_post(post: Post) -&gt; Post { ... }
</code></pre>
<p>That's all you need. <code>/openapi.json</code> and <code>/docs</code> (Scalar UI) appear automatically. Path params (<code>/posts/{id}</code>) are typed and coerced. JSON body deserialization checks for missing required fields, applies defaults, validates nullables, rejects extras. You can opt out with <code>@server(docs=false)</code>.</p>
<h3>WebSockets, typed, with AsyncAPI auto-generated</h3>
<pre><code class="language-fitz">type ChatMessage { from: Str, text: Str }

@server(43929, ws_heartbeat_secs=30)
fn main() =&gt; 0

@authenticated
@ws("/chat")
async fn chat(conn: WsConn&lt;ChatMessage&gt;, user: User) {
    loop {
        let msg = match conn.recv() {
            Ok(m) =&gt; m,
            Err(_) =&gt; break,
        }
        conn.broadcast(ChatMessage { from: user.name, text: msg.text })
    }
}
</code></pre>
<p>Every frame is auto-marshalled to and from the declared type. Auth runs <strong>before</strong> the WebSocket upgrade — invalid token gets a 401 without ever opening the socket. Ping/pong heartbeat keeps the connection alive past Nginx's 60s default. <code>/asyncapi.json</code> is generated automatically (the event-driven sibling of OpenAPI). I don't know of another language that auto-generates AsyncAPI from typed source.</p>
<h3>Background jobs and cron, no Redis required</h3>
<pre><code class="language-fitz">@cron("*/5 * * * *")
async fn cleanup_old_sessions() {
    db.exec("DELETE FROM sessions WHERE expires_at &lt; now()")
}

@background
async fn send_welcome_email(email: Str) {
    // expensive thing
}

@post("/signup")
fn signup(creds: Credentials) -&gt; User {
    let user = create_user(creds)
    spawn(send_welcome_email(user.email))  // fire-and-forget, typed Future&lt;Null&gt;
    return user
}
</code></pre>
<p>No Celery. No Redis. No <code>celery worker -A app</code> next to your <code>uvicorn</code> process. The scheduler is in your binary. Suitable for 90% of services — when you outgrow it, you outgrow it for a reason, and that's a Fase 11+ problem.</p>
<h3>A native ORM with a pure-Rust Postgres driver</h3>
<p>This is the piece I'm most proud of, and the one that took the longest. Fitz has its own Postgres driver written in Rust — no <code>libpq</code>, no <code>tokio-postgres</code>, no <code>sqlx</code>. The wire protocol (v3.0), SCRAM-SHA-256 auth, prepared statements, the binary format for 11 OID types — all implemented from the RFC.</p>
<pre><code class="language-fitz">@table("users")
type User {
    @primary id: Int,
    email: Str,
    name: Str,
    @has_many("Post", "user_id") posts: List&lt;Post&gt;,
}

@table("posts")
type Post {
    @primary id: Int,
    user_id: Int,
    title: Str,
    body: Str,
    @belongs_to user: User?,
}

@get("/users")
async fn list_users(db: DbConn) -&gt; List&lt;User&gt; {
    return User.all(db).preload("posts").await
}

@get("/users/{id}")
async fn get_user(db: DbConn, id: Int) -&gt; Result&lt;User&gt; {
    return User.where(fn(u) =&gt; u.id == id).first(db).await
}

@post("/users")
async fn create_user(db: DbConn, user: User) -&gt; User {
    return User.insert(db, user).await
}
</code></pre>
<p>The closure inside <code>.where(...)</code> is <strong>translated to parametrized SQL at compile time</strong> — <code>fn(u) =&gt; u.id == id</code> becomes <code>WHERE id = $1</code>. Operators like <code>.is_in([...])</code>, <code>.like(...)</code>, <code>.ilike(...)</code>, <code>.contains(...)</code>, plus JSONB operators like <code>.has_key(...)</code>, <code>.contains_json(...)</code> all map to native Postgres operators. Eager loading with <code>.preload("posts")</code> issues a single batched query. Aggregates (<code>.sum</code>/<code>.avg</code>/<code>.min</code>/<code>.max</code>/<code>.count</code>) and <code>GROUP BY</code> are supported through a separate <code>Aggregated&lt;Row&gt;</code> type.</p>
<p>This compiles to native code via <code>fitz build</code>. The generated binary makes the same Postgres calls. Zero overhead at runtime for the SQL — it's already constant by the time the binary runs, comparable in performance to Diesel or sqlx.</p>
<h4>How fast is it really? — head-to-head against SQLAlchemy</h4>
<p>The "zero overhead" claim is easy to make and easy to fake, so the repo ships a <strong>reproducible bench</strong> between two equivalent boilerplates (<a href="https://github.com/Thegreekman76/fitz/tree/main/boilerplates/api-postgres-fitz"><code>api-postgres-fitz</code></a> vs <a href="https://github.com/Thegreekman76/fitz/tree/main/boilerplates/api-postgres-python"><code>api-postgres-python</code></a>) — same Postgres, same endpoints, same response shape, same <code>docker compose</code>. Headline numbers on <strong>v0.10.13</strong> (Intel Core Ultra 7 155H, Docker 29.2.1, 30s sustained, concurrency 10):</p>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Fitz ORM</th>
<th>Python + SQLAlchemy</th>
<th>Speedup</th>
</tr>
</thead>
<tbody><tr>
<td>Memory peak</td>
<td><strong>9.2 MB</strong></td>
<td>51 MB</td>
<td><strong>5.5× leaner</strong></td>
</tr>
<tr>
<td><code>GET /users</code> p50</td>
<td><strong>4.88 ms</strong></td>
<td>37.85 ms</td>
<td><strong>7.76×</strong></td>
</tr>
<tr>
<td><code>GET /users</code> RPS</td>
<td><strong>1944</strong></td>
<td>246</td>
<td><strong>7.91×</strong></td>
</tr>
<tr>
<td><code>GET /users/{id}</code> p50</td>
<td><strong>3.60 ms</strong></td>
<td>31.87 ms</td>
<td><strong>8.85×</strong></td>
</tr>
<tr>
<td><code>GET /users/{id}</code> RPS</td>
<td><strong>2604</strong></td>
<td>296</td>
<td><strong>8.80×</strong></td>
</tr>
<tr>
<td>Cold start</td>
<td><strong>0.14 s</strong></td>
<td>0.22 s</td>
<td>1.57×</td>
</tr>
<tr>
<td>Image size</td>
<td><strong>131 MB</strong></td>
<td>258 MB</td>
<td>2× lighter</td>
</tr>
</tbody></table>
<p>That's <del>8× the throughput at ~5× less memory, on the same machine, in the same Docker network, against the same Postgres. Reproduce with <a href="https://github.com/Thegreekman76/fitz/tree/main/benchmarks/orm-vs-sqlalchemy"><code>bash benchmarks/orm-vs-sqlalchemy/run.sh</code></a> (</del>5–8 min with hot Docker cache; needs <code>oha</code> + <code>jq</code>). Full methodology, raw output, and the parts where the comparison is <em>un</em>fair to Fitz live in the <a href="https://github.com/Thegreekman76/fitz/blob/main/benchmarks/orm-vs-sqlalchemy/README.md">bench README</a>.</p>
<h3>Python interop when you do need it</h3>
<pre><code class="language-fitz">from python import math, json

let radius = 5.0
let area: Float = math.pi * radius * radius

let parsed: Result&lt;Map&lt;Str, Any&gt;&gt; = match json.loads("{\"name\": \"ada\"}") {
    Ok(d) =&gt; Ok(d),
    Err(e) =&gt; Err("malformed JSON: {e}"),
}
</code></pre>
<p>SQLAlchemy, NumPy, pandas, anything on PyPI — accessible from Fitz with <code>from python import ...</code>. The runtime embeds CPython via PyO3. Python exceptions become <code>Result::Err</code> automatically. Async Python (<code>asyncpg</code>, SQLAlchemy 2.x async) bridges to Fitz's <code>.await</code> transparently. You can even do <code>fitz build --bundle-python</code> to ship a binary with CPython embedded — no Python required on the destination machine.</p>
<p>This is intentional. Fitz isn't trying to replace Python's ecosystem — it's trying to give you a better language for the web layer while keeping the door open to everything Python has already built.</p>
<h3>Async, finally without color</h3>
<pre><code class="language-fitz">async fn fetch_user(id: Int) -&gt; Result&lt;User&gt; { ... }

async fn main() {
    let user = fetch_user(42).await?
    print("got {user.name}")
}
</code></pre>
<p><code>async</code>/<code>await</code> is in the core, on a tokio runtime. The <code>?</code> operator works through <code>Result&lt;T&gt;</code>. The type checker enforces that <code>?</code> only appears inside functions that return <code>Result&lt;...&gt;</code>. Compiles to <code>async fn</code> + <code>.await</code> in Rust — same execution model as Rust async, same multi-threaded executor.</p>
<h3>CLI builder — same language, command-line tools</h3>
<p>Fitz isn't only for HTTP services. The same compiler ships a built-in CLI builder, no library needed:</p>
<pre><code class="language-fitz">@command("greet", desc="Greet a person")
fn greet(name: Str, loud: Bool = false, count: Int = 1) -&gt; Int {
    let n = count
    while n &gt; 0 {
        if loud { print("HELLO, {name}!") } else { print("hello, {name}") }
        n = n - 1
    }
    return 0
}

@command("add", desc="Sum two numbers")
fn add(a: Int, b: Int) -&gt; Int {
    print("{a + b}")
    return 0
}
</code></pre>
<pre><code class="language-bash">$ ./mybin greet Ada --loud --count 3
HELLO, Ada!
HELLO, Ada!
HELLO, Ada!

$ ./mybin --help
USAGE: mybin &lt;command&gt; [ARGS] [OPTIONS]
COMMANDS:
    greet    Greet a person
    add      Sum two numbers
</code></pre>
<p>Convention over decoration: params without defaults are positional args, params with defaults are flags. Bool with <code>default = false</code> becomes <code>--flag</code>, other types become <code>--flag &lt;value&gt;</code>. Short flags auto-derive (<code>--loud</code> → <code>-l</code>) with conflict detection. Help auto-generated, exit codes POSIX standard. <strong>Bit-for-bit parity</strong> between <code>fitz run</code> (development) and <code>fitz build</code> (a self-contained binary you can drop into <code>/usr/local/bin</code>).</p>
<p>This is the same language. Same type checker. Same async/await. Same <code>Result&lt;T&gt;</code> for errors. If your tool needs to hit the database, the ORM is there. If it needs HTTP, <code>@get</code>/<code>@post</code> are there. The line between "web service" and "CLI tool" stops being a stack decision.</p>
<h3>Production-ready stack — from repo to production</h3>
<p>This is what separates Fitz from "interesting prototype" languages. Real services need health checks, secrets, observability, and a way to ship. All of them are part of the language:</p>
<pre><code class="language-fitz">@server(43928)
fn main() =&gt; 0

// Auto-mounted at GET /healthz and /readyz — Kubernetes-friendly.
@healthz
fn liveness() -&gt; Bool =&gt; true

@readyz
async fn readiness(db: DbConn) -&gt; Bool {
    return match db.exec("SELECT 1").await {
        Ok(_) =&gt; true,
        Err(_) =&gt; false,
    }
}

// Secret&lt;T&gt; never leaks to logs, prints "***" on Display.
let db_url: Secret&lt;Str&gt; = secret("DATABASE_URL")
let log_level: Str = config("LOG_LEVEL", "info")

// Tracing + metrics with one decorator each.
@trace(name="process_order")
@metric(name="orders")
async fn process(order: Order) -&gt; Result&lt;Receipt&gt; {
    // process_order_duration_seconds (histogram) and orders_calls_total
    // (counter) populate automatically on drop.
}

// Feature flags with two sources: fitz.toml [flags] + FITZ_FLAG_&lt;NAME&gt; env vars.
@flag("new-checkout")
@post("/v2/checkout")
fn v2_checkout(body: Cart) -&gt; Receipt { ... }
</code></pre>
<p>Behind the scenes:</p>
<ul>
<li><p><strong>HTTP access logs</strong> auto-emit with <code>trace_id</code>/<code>span_id</code> propagated to every <code>log.info(...)</code> inside the handler.</p>
</li>
<li><p><strong>OpenTelemetry OTLP</strong> export with one env var: <code>OTEL_EXPORTER_OTLP_ENDPOINT</code>. Spans flow to Jaeger/Tempo/Honeycomb. Without the env var, zero overhead, zero network calls.</p>
</li>
<li><p><strong>Prometheus</strong> <code>/metrics</code> endpoint exposes counters and histograms — <code>@server(prometheus=true)</code> enables.</p>
</li>
<li><p><code>@flag</code> <strong>on HTTP/WS handlers</strong> returns 404 when the flag is off — gate the hot path before middleware/auth.</p>
</li>
</ul>
<p>Deploying:</p>
<pre><code class="language-bash"># Generate the Dockerfile + docker-compose.yml from the program shape.
fitz docker init

# Build the binary, the Docker image, push to a registry.
fitz deploy docker --tag mycorp/api:v1

# Or bring up locally with compose.
fitz deploy compose
</code></pre>
<p><code>fitz docker init</code> reads your AST. If there's a <code>db.connect(...)</code>, it adds Postgres to the compose. If there's <code>@server(N)</code>, it sets <code>EXPOSE N</code>. If there's <code>@cron</code>, it adds <code>restart: unless-stopped</code>. If there's <code>from python import ...</code>, it picks <code>python:3.12-slim-bookworm</code> instead of distroless. <strong>It generates what you'd write by hand</strong>, you commit it, edit when you need to.</p>
<p>I'm not aware of another language where deployment is a language feature. It is here because every project I shipped in Python ended with two days of debugging Dockerfile gotchas.</p>
<h2>What's the tooling like?</h2>
<p>This is the part I underestimated when I started. A language without good tools is dead on arrival. Here's the current state:</p>
<ul>
<li><p><code>fitz run</code> — interpret the file directly. Fastest feedback loop.</p>
</li>
<li><p><code>fitz build</code> — compile to a native binary via a generated Rust project. Bit-for-bit parity with <code>fitz run</code> is a hard requirement.</p>
</li>
<li><p><code>fitz check</code> — type checker only, no execution.</p>
</li>
<li><p><code>fitz test</code> — built-in test runner with <code>@test</code> decorator and <code>assert</code>, <code>assert_eq</code>, <code>assert_throws</code>. Cargo-style output.</p>
</li>
<li><p><code>fitz dev</code> — hot reload. Watches <code>*.fitz</code> and <code>fitz.toml</code>, kills and respawns the child on change.</p>
</li>
<li><p><code>fitz fmt</code> — opinionated formatter, zero config. Preserves your comments and blank lines.</p>
</li>
<li><p><code>fitz lint</code> — 4 built-in lints with <code>// @allow(&lt;name&gt;)</code> suppression. Cargo-clippy-style output.</p>
</li>
<li><p><code>fitz repl</code> — interactive REPL with multi-line support, <code>:type</code>, <code>:load</code>, persistent history.</p>
</li>
<li><p><code>fitz openapi</code> — emit the OpenAPI schema without running the server.</p>
</li>
<li><p><code>fitz db diff</code><strong>/</strong><code>migrate</code> — schema migration tooling. Diff the live DB against the <code>@table</code> types in your code, generate idempotent migrations, apply them with <code>fitz db migrate</code>. Same model as Alembic but with the types as source of truth.</p>
</li>
<li><p><code>fitz docker init</code><strong>/</strong><code>build</code> — generate the Dockerfile + <code>.dockerignore</code> + <code>docker-compose.yml</code> from the program shape, then <code>docker build</code> wrapped.</p>
</li>
<li><p><code>fitz deploy docker</code><strong>/</strong><code>compose</code> — thin wrapper to ship the image or bring up locally with one command.</p>
</li>
<li><p><strong>VSCode extension</strong> — diagnostics + hover + go-to-definition + autocomplete + <strong>signature help</strong> + <strong>format on save</strong> + <strong>bidirectional type inference</strong> for callbacks, multi-platform distribution.</p>
</li>
<li><p><code>fitz new</code> + <code>fitz add</code> + <code>fitz remove</code> + <code>fitz update</code> — package manager with <code>fitz.toml</code>, lockfile, path deps, git deps.</p>
</li>
</ul>
<p>The LSP is real (<code>tower-lsp</code> under the hood). The formatter is real (your code round-trips through it). The test runner is real. The whole thing is dogfooded — I write Fitz code with the same VSCode extension I ship.</p>
<h2>Being honest about state</h2>
<p>This is a one-developer project. I started learning Rust to build it. I'm not going to pretend it's production-ready for everyone — here's what's true today (June 2026, release v0.15.0):</p>
<p><strong>What works end-to-end, with bit-for-bit</strong> <code>fitz run</code> <strong>↔</strong> <code>fitz build</code> <strong>parity:</strong></p>
<ul>
<li><p>HTTP server with <code>@get</code>/<code>@post</code>/<code>@put</code>/<code>@delete</code>, OpenAPI auto, Scalar UI.</p>
</li>
<li><p>Middleware chain with <code>@middleware(fn)</code> + CORS built-in.</p>
</li>
<li><p>JWT auth with <code>@auth_provider</code>/<code>@authenticated</code>/<code>@admin</code> + <code>@requires("custom_role")</code> for RBAC. Argon2id password hashing. Token blacklist over Postgres for logout/refresh.</p>
</li>
<li><p>WebSockets with <code>WsConn&lt;T&gt;</code>, AsyncAPI auto, heartbeat, auth pre-upgrade.</p>
</li>
<li><p>Cron jobs with <code>@cron("expr")</code> (with retry, timezone, persistence, catch-up), background jobs with <code>@background</code> + <code>spawn(...)</code>.</p>
</li>
<li><p>Postgres ORM with <code>@table</code>/<code>@primary</code>/<code>@column</code>/<code>@belongs_to</code>/<code>@has_many</code>, closure-to-SQL, eager loading, <strong>transactions</strong> (<code>db.transaction(fn)</code>), <strong>schema migrations</strong> (<code>fitz db diff</code>/<code>migrate</code>).</p>
</li>
<li><p>TLS strict for Postgres (<code>sslmode=require</code>).</p>
</li>
<li><p>Async/await on tokio.</p>
</li>
<li><p>Python interop with <code>from python import ...</code>, including auto-bridging async.</p>
</li>
<li><p><strong>CLI builder</strong> with <code>@command</code> — same language for CLI tools.</p>
</li>
<li><p><strong>Production stack</strong>: <code>@healthz</code>/<code>@readyz</code>, <code>Secret&lt;T&gt;</code>, <code>secret()</code>/<code>config()</code>, <code>@trace</code>/<code>@metric</code>, <code>@flag</code>, OpenTelemetry OTLP export, Prometheus <code>/metrics</code>, <code>fitz docker init/build</code>, <code>fitz deploy</code>.</p>
</li>
<li><p>Package manager with path deps and git deps.</p>
</li>
<li><p>Full tooling: LSP (with signature help, format on save, hover over params and bindings), fmt, test, dev, repl, lint.</p>
</li>
</ul>
<p><strong>What's not in the box yet:</strong></p>
<ul>
<li><p>Frontend in <code>.fitz</code> (single-file components, SSR). Roadmap (Fase 11) — the most ambitious bet of the project. Not started.</p>
</li>
<li><p>A public package registry. Path deps and git deps work today; the registry is on hold until there's real demand.</p>
</li>
<li><p><code>fitz deploy</code> targets beyond <code>docker</code>/<code>compose</code> (no <code>fly</code>/<code>railway</code>/<code>k8s</code> wrapper yet — use the native CLIs).</p>
</li>
<li><p>Interactive debugging in VSCode (Debug Adapter Protocol). Workarounds: <code>print</code>, REPL <code>:type</code>/<code>:env</code>, LSP diagnostics. Tracked as V6 in the backlog.</p>
</li>
</ul>
<p><strong>What's stable</strong>: ~3030 Rust unit tests + 13 LSP E2E + 360 compile E2E (smoke over every example in the guide) + ~140 more across other suites running in CI on every push. Clippy <code>-D warnings</code> clean.</p>
<h2>How to try it</h2>
<pre><code class="language-bash"># Install on Linux / macOS / WSL
curl -sSf https://thegreekman76.github.io/fitz/install.sh | sh

# Install on Windows (PowerShell)
irm https://thegreekman76.github.io/fitz/install.ps1 | iex

# Or grab a release binary from GitHub
# https://github.com/Thegreekman76/fitz/releases

# Reopen the terminal so the PATH change takes effect, then:
fitz --version
</code></pre>
<p><strong>VSCode extension</strong> (recommended — syntax highlighting, hover with types, autocomplete, signature help, format on save): grab the <code>.vsix</code> for your platform from the same <a href="https://github.com/Thegreekman76/fitz/releases">releases page</a> (<code>fitz-lang-&lt;platform&gt;.vsix</code>) and install it with <code>code --install-extension fitz-lang-&lt;platform&gt;.vsix --force</code>. The Language Server is bundled inside — no separate install needed. Reload VSCode once.</p>
<p>First server:</p>
<pre><code class="language-bash">fitz new my-api --http
cd my-api
fitz dev
</code></pre>
<p>Eight boilerplates ship in the repo under <code>boilerplates/</code>:</p>
<ul>
<li><p><code>api-simple</code> — minimal HTTP API.</p>
</li>
<li><p><code>api-middleware-cors</code> — middleware chain + CORS configuration.</p>
</li>
<li><p><code>api-postgres-fitz</code> — ORM + Postgres, Dockerized.</p>
</li>
<li><p><code>api-postgres-python</code> — Postgres via Python/SQLAlchemy interop.</p>
</li>
<li><p><code>api-websocket</code> — typed WebSocket chat.</p>
</li>
<li><p><code>api-orm-full</code> — the full showcase: auth + ORM + WebSockets + cron + jobs.</p>
</li>
<li><p><code>api-fullstack-postgres</code> — backend + minimal frontend in one binary.</p>
</li>
<li><p><code>cli-tool</code> — CLI app with <code>@command</code> (no HTTP).</p>
</li>
</ul>
<p>Each one runs with <code>docker compose up</code> or <code>fitz dev</code>. The README has the full matrix.</p>
<h2>Why I built this</h2>
<p>I live in El Chaltén, in Argentine Patagonia. The Fitz Roy is the granite tower that defines the skyline here. Borges wrote that we live in a country where the past is uncertain and only the future is real. I think that's true of programming languages too: the past is full of accumulated workarounds for missing language features, and the future is whatever you decide to build.</p>
<p>I've spent ten years writing API code in Python. I love FastAPI. But every time I start a new project, the first three hours are spent gluing libraries together to do the same thing I did last week. At some point the question becomes: what would a language look like that started from this set of needs in 2026, instead of growing them as patches on a language designed for shell scripting in 1991?</p>
<p>That's Fitz.</p>
<p>It's not done. I'm one person. It will get there.</p>
<p><strong>Repo</strong>: <a href="https://github.com/Thegreekman76/fitz">github.com/Thegreekman76/fitz</a></p>
<p><strong>Docs and course</strong>: <a href="https://thegreekman76.github.io/fitz/">thegreekman76.github.io/fitz</a></p>
<p><strong>Guide</strong> (34 chapters): <a href="https://thegreekman76.github.io/fitz/guide/">thegreekman76.github.io/fitz/guide/</a></p>
<p><strong>Roadmap</strong>: <a href="https://github.com/Thegreekman76/fitz/blob/main/docs/roadmap.md">docs/roadmap.md</a></p>
<p><strong>CHANGELOG</strong>: <a href="https://github.com/Thegreekman76/fitz/blob/main/CHANGELOG.md">CHANGELOG.md</a> — every release with detail.</p>
<p><strong>Issues</strong>: <a href="https://github.com/Thegreekman76/fitz/issues">github.com/Thegreekman76/fitz/issues</a></p>
<p>If you try it, I want to hear what broke. Open an issue or a discussion on GitHub.</p>
]]></content:encoded></item></channel></rss>