Aurora
The fullstack Varian platform — Zenith backend + Lumen frontend, one language, one binary.
All vn_modules/ files — including zenith.vn, lumen.vn, lumen_ui.vn, db.vn, and http.vn — are automatically concatenated as a prelude when you run an Aurora project. new_app(), lumen_mount(), sqlite.connect(), and the entire Lumen UI registry are always in scope. No import, no require, no npm install. The toolchain auto-detects Aurora, Lumen, or Zenith project type and compiles accordingly.
Why Aurora?
Aurora's pitch is not "yet another fullstack framework." It's one language, one toolchain, one binary — from database schema to DOM morph. No separate frontend build, no JavaScript framework, no node_modules, no dependency tree.
One language, the entire stack
Backend API routes, database queries, session management, HTML templates, and UI components — all Varian. Share types, validation, and business logic between server and client with no serialization boundary.
Zero-config scaffolding
vn new myapp creates a complete fullstack project: main.vn, pages/, lib/, public/, constellation.toml. vn dev starts a live-reload dev server. No config files to write.
Server-driven UI
Lumen components render on the server. A 5 KB client forwards events over WebSocket; the server re-renders and morphs only the changed DOM nodes. Zero hydration bugs, zero client framework, zero Varian in the browser.
Comptime ORM + DB drivers
SQLite and Postgres drivers built in. The comptime ORM checks SQL strings at compile time and generates bound queries. bind() throws on parameter count mismatch — not at runtime.
Batteries included
JWT auth, signed sessions, password hashing, CORS, rate limiting, Redis driver, HTML template engine, request validation, Lumen UI component registry — all built in through vn_modules/. Zero external dependencies.
Deploys as one binary
vn build --release produces a single native executable with Zenith, Lumen, the ORM, and the database schema all compiled in. No runtime to install, no node_modules on the server, no "works on my machine."
OpenAPI for free
Every route's summary string feeds a generated OpenAPI spec at /docs. Request and response schemas are declared inline with the handler — no separate spec file, no decorator soup.
Multi-core by default
Zenith clusters across threads with independent VMs, SIMD request parsing, writev single-syscall responses, and per-request arena allocation. Lumen's WebSocket event loop handles concurrent connections without threads.
rocket_launch Scaffolding an Aurora project
A single command creates a complete fullstack project:
$ vn new myapp cd myapp vn dev # live-reload dev server on :8090
This creates a project tree with every file you need:
myapp/ main.vn # Zenith entry point — app config, routes, middleware pages/ index.lumen # Lumen landing page (live reactive component) components/ # shared UI components (ProductCard, Nav, etc.) lib/ config.vn # env-based configuration db.vn # SQLite schema, migration, seed middleware.vn # logging, CORS, rate limiter api_products.vn # product listing REST endpoints api_cart.vn # session-backed shopping cart api_orders.vn # checkout + order creation auth.vn # registration, login, logout jobs.vn # email worker pool + cron pages.vn # Lumen page mounting with SSR data public/ # static assets (images, favicons) constellation.toml # package manifest (kind = "aurora")
vn dev detects the Aurora project type automatically, starts the Zenith server, mounts all Lumen pages, and watches for changes with live reload. vn build --release compiles everything into a single native binary.
code Full-stack examples
Every Aurora project follows the same architecture. Here is the complete walkthrough of a production fullstack app — the Aurora storefront reference implementation.
input Entry point — main.vn
The main.vn wires together every module: config, database, middleware, API routes, auth, background jobs, and Lumen page mounting. It is the single entry point for the entire application.
use "lib/config.vn" use "lib/db.vn" use "lib/middleware.vn" use "lib/api_products.vn" use "lib/api_cart.vn" use "lib/api_orders.vn" use "lib/auth.vn" use "lib/jobs.vn" use "lib/pages.vn" use ".gen/pages.vn" let c = cfg() let conn = db_init(c.db_path) let app = new_app() app.title = "Aurora" install_middleware(app, c) register_product_api(app, conn, c) register_cart_api(app, conn, c) let email_pool = start_jobs(conn, c) register_order_api(app, conn, c, email_pool) register_auth(app, conn, c) mount_pages(app, conn, c) app.enable_docs("/docs") app.serve_static("/public", "public") app.on_error(|err, req| { return http.create_struct( ["status", "body", "content_type"], [500, "<!DOCTYPE html>...", "text/html"] ) }) app.listen(c.port)
Notice: no import for new_app(), http.create_struct, sqlite — all auto-in-scope through the vn_modules/ prelude.
settings Configuration — lib/config.vn
Centralized env-based configuration. Every env.get() call lives in one place — no magic numbers or hardcoded secrets scattered across modules.
fn cfg() { let port = 8080 let port_str = env.get("PORT", "8080") try { port = json_decode(port_str) } catch e {} let secret = env.get("SESSION_SECRET", "") if secret == "" { secret = auth.sha1_base64("" + time.now_ms()) print("[config] WARNING: SESSION_SECRET not set") } return http.create_struct( ["port", "session_secret", "db_path", "per_page"], [port, secret, env.get("DB_PATH", "aurora.db"), 12] ) }
storage Database — lib/db.vn
SQLite schema initialization, migration, and seed data. The comptime ORM (db.vn in vn_modules/) provides a builder API (select(), bind(), run_sqlite()) that validates queries at compile time.
fn db_init(db_path) { let conn = sqlite.connect(db_path) sqlite.query(conn, "CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price_cents INTEGER NOT NULL, stock INTEGER NOT NULL DEFAULT 0, image TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', featured INTEGER NOT NULL DEFAULT 0)") sqlite.query(conn, "CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, pass_hash TEXT NOT NULL, name TEXT NOT NULL DEFAULT '', created TEXT NOT NULL DEFAULT (datetime('now')))") sqlite.query(conn, "CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, total_cents INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created TEXT NOT NULL DEFAULT (datetime('now')))") let existing = sqlite.query(conn, "SELECT COUNT(*) AS c FROM products") if existing[0].c == 0 { seed_data(conn) } return conn } fn format_price(cents) { let dollars = cents / 100 let c = cents % 100 if c < 10 { return "$" + dollars + ".0" + c } return "$" + dollars + "." + c }
link Middleware — lib/middleware.vn
Middleware is applied as a chain with app.add_middleware(). Each middleware receives the request and a next callback.
fn install_middleware(app, c) { app.add_middleware(|req, next| { let start = time.now_ms() let resp = next(req) let ms = time.now_ms() - start print("[Aurora] " + req.method + " " + req.path + " -> " + resp.status + " (" + ms + "ms)") return resp }) app.add_middleware(cors(["*"], ["GET", "POST", "PUT", "DELETE"], ["*"])) app.add_middleware(rate_limit(c.rate_max, c.rate_window)) }
api REST API — lib/api_products.vn
Product listing and detail endpoints. Routes are registered with a summary string for auto-generated OpenAPI docs. Query parameters parsed with query(), database queries use bound parameters.
fn register_product_api(app, conn, c) { app.get("/api/products", |req| { let q = query(req, "q", "") let page = 0 let pstr = query(req, "page", "1") if pstr != null and pstr != "" { try { page = json_decode(pstr) - 1 } catch e {} } let offset = page * c.per_page let rows if q != "" { rows = sqlite.query(conn, "SELECT * FROM products WHERE name LIKE ? ORDER BY id LIMIT ? OFFSET ?", ["%" + q + "%", c.per_page, offset]) } else { rows = sqlite.query(conn, "SELECT * FROM products ORDER BY id LIMIT ? OFFSET ?", [c.per_page, offset]) } return json_res(rows, 200) }, "List products", http.create_struct( ["query_params", "response_schema", "response_status"], [http.create_struct(["q", "page"], ["string", "integer"]), "array", 200] )) app.get("/api/products/:id", |req| { let id = _validate.get_field(req.params, "id") if id == null { return err_res("Missing id", 400) } let rows = sqlite.query(conn, "SELECT * FROM products WHERE id = ?", [id]) if rows.len() == 0 { return err_res("Not found", 404) } return json_res(rows[0], 200) }, "Get product by id", http.create_struct( ["response_schema", "response_status", "error_status"], ["object", 200, 404] )) }
shopping_cart Session-backed Cart — lib/api_cart.vn
The cart is stored in the session cookie. session_get and session_set handle JWT-signed cookie encryption automatically.
fn register_cart_api(app, conn, c) { app.get("/api/cart", |req| { return json_res(get_cart(req, c.session_secret), 200) }, "Get cart", http.create_struct( ["response_schema", "response_status"], ["array", 200] )) app.post("/api/cart/:id", |req| { let id = _validate.get_field(req.params, "id") if id == null { return err_res("Missing product id", 400) } let qty = req.json.qty if qty == null { qty = 1 } let cart = get_cart(req, c.session_secret) let found = false for i in 0..cart.len() { if cart[i].product_id == id { cart[i].qty = cart[i].qty + qty found = true; break } } if !found { cart = cart.push(http.create_struct( ["product_id", "qty"], [id, qty])) } return save_cart(json_res(cart, 200), cart, req, c.session_secret) }, "Add to cart", http.create_struct( ["request_schema", "response_schema", "response_status"], ["object", "array", 200] )) app.delete("/api/cart/:id", |req| { let id = _validate.get_field(req.params, "id") if id == null { return err_res("Missing product id", 400) } let cart = get_cart(req, c.session_secret) let new_cart = [] for i in 0..cart.len() { if cart[i].product_id != id { new_cart = new_cart.push(cart[i]) } } return save_cart(json_res(new_cart, 200), new_cart, req, c.session_secret) }, "Remove from cart", null) }
lock Authentication — lib/auth.vn
Registration, login, and logout with password hashing and JWT-signed session cookies. Routes include OpenAPI schema annotations for auto-generated docs.
fn register_auth(app, conn, c) { app.post("/api/register", |req| { let json = req.json let email = _validate.get_field(json, "email") let pass = _validate.get_field(json, "password") if email == null or pass == null { return err_res("Email and password required", 400) } let existing = sqlite.query(conn, "SELECT id FROM users WHERE email = ?", [email]) if existing.len() > 0 { return err_res("Email already registered", 409) } let hash = auth.sha1_base64(pass) sqlite.query(conn, "INSERT INTO users (email, pass_hash, name) VALUES (?, ?, ?)", [email, hash, _validate.get_field(json, "name")]) let rows = sqlite.query(conn, "SELECT last_insert_rowid() AS id") let session_data = http.create_struct( ["uid", "email"], [rows[0].id, email]) return session_set(json_res(http.create_struct( ["uid", "email"], [rows[0].id, email]), 201), session_data, c.session_secret, null) }, "Register user", http.create_struct( ["request_schema", "response_schema", "response_status"], ["object", "object", 201] )) app.post("/api/login", |req| { let json = req.json let email = _validate.get_field(json, "email") let pass = _validate.get_field(json, "password") let rows = sqlite.query(conn, "SELECT id, name FROM users WHERE email = ? AND pass_hash = ?", [email, auth.sha1_base64(pass)]) if rows.len() == 0 { return err_res("Invalid credentials", 401) } let u = rows[0] return session_set(json_res(http.create_struct( ["uid", "email"], [u.id, email]), 200), http.create_struct(["uid", "email", "name"], [u.id, email, u.name]), c.session_secret, null) }, "Login user", http.create_struct( ["request_schema", "response_schema", "error_status"], ["object", "object", 401] )) }
publish Mounting Lumen Pages with SSR Data — lib/pages.vn
Lumen pages mount onto Zenith routes with the lumen_mount_data API. The callback runs on every request, fetches data from the database, and passes it to the Lumen component as initial state — full server-side rendering with zero client data fetching.
fn mount_pages(app, conn, c) { // Shop page — DB-driven products with search let shop_comp = _lumen.get_component("Shop") if shop_comp != null { lumen_mount_data(app, "/shop", shop_comp, |req| { let q = query(req, "q", "") let items = [] let rows if q != "" { rows = sqlite.query(conn, "SELECT * FROM products WHERE name LIKE ? ORDER BY id", ["%" + q + "%"]) } else { rows = sqlite.query(conn, "SELECT * FROM products ORDER BY id", []) } for i in 0..rows.len() { let p = rows[i] items = items.push(http.create_struct( ["id","name","price","stock","image"], [p.id, p.name, format_price(p.price_cents), p.stock, p.image])) } return http.create_struct( ["hasProducts", "products", "query"], [items.len() > 0, items, q]) }) } // Product detail — DB-backed SSR with :id param let detail_comp = _lumen.get_component("ProductDetail") if detail_comp != null { lumen_mount_data(app, "/product/:id", detail_comp, |req| { let id = _validate.get_field(req.params, "id") if id == null { return http.create_struct(["id"], [0]) } let rows = sqlite.query(conn, "SELECT * FROM products WHERE id = ?", [id]) if rows.len() == 0 { return ... } // 404 fallback let p = rows[0] return http.create_struct( ["id","name","price","stock", "stockText","stockBadgeTone","description","image"], [p.id, p.name, format_price(p.price_cents), p.stock, p.stock > 0 ? "In Stock" : "Out of Stock", p.stock > 0 ? "success" : "danger", p.description, p.image]) }) } // Cart — session-driven SSR let cart_comp = _lumen.get_component("Cart") if cart_comp != null { lumen_mount_data(app, "/cart", cart_comp, |req| { let cart_items = get_cart(req, c.session_secret) let items = []; let total_cents = 0 for i in 0..cart_items.len() { let ci = cart_items[i] let rows = sqlite.query(conn, "SELECT * FROM products WHERE id = ?", [ci.product_id]) if rows.len() > 0 { total_cents = total_cents + rows[0].price_cents * ci.qty items = items.push(...) } } return http.create_struct( ["hasItems", "items", "total"], [items.len() > 0, items, format_price(total_cents)]) }) } }
widgets Lumen UI Component — ProductCard.lumen
A Lumen component lives in pages/components/ — it is automatically compiled as a component struct that gets imported by other pages. This one renders a product card that fetches cart data from the /api endpoints.
<template> <div class="product-card"> <div class="card-img-wrap"> <img src="" alt="" loading="lazy" /> <div class="card-stock" data-tone="danger"> <span class="stock-dot"></span> Out of Stock </div> </div> <div class="card-body"> <h3 class="card-title"></h3> <p class="card-price"></p> <div class="card-footer"> <button @click="addToCart">+ Add</button> </div> </div> </div> </template> <style scoped> .product-card { background: var(--lumen-surface); border: 1px solid var(--lumen-border); border-radius: 12px; overflow: hidden; } .product-card:hover { transform: translateY(-4px); } </style> <script> fn state() { return { id: 0, name: "Product", price: "$0.00", stock: 0, image: "" } } fn addToCart(s, v) { let resp = post("/api/cart/" + s.id, "{\"qty\":1}") return s } </script>
compare_arrows Aurora vs Next.js
Aurora is to Varian what Next.js is to JavaScript — but the setup story is fundamentally different because Aurora ships everything built in.
timer Time to "Hello World"
check_circle Aurora
schedule Next.js
table Feature comparison
| Concern | Aurora (Varian) | Next.js (JavaScript) |
|---|---|---|
| Scaffolding | vn new myapp — 1 command |
npx create-next-app — interactive prompts, 200MB+ download |
| Language parity | One language — Varian for backend API, database, templates, and UI components | Two languages — JavaScript/TypeScript for frontend, Node/JS for API routes (or Python/Go/Rust for a separate backend) |
| Dependencies | Zero — everything built in via vn_modules/ prelude |
package.json with 1000+ transitive deps; separate DB driver, ORM, auth library, validation library, testing library |
| Database | Built-in — SQLite and Postgres drivers with comptime ORM | Choose Prisma, Drizzle, Kysely, or raw drivers — all npm install with their own deps |
| Auth | Built-in — JWT signing, session cookies (HttpOnly, SameSite=Lax), password hashing, CSRF protection | NextAuth.js, Lucia, Clerk, or self-built — each with its own setup and dependency tree |
| UI model | Server-driven — HTML-over-WebSocket, 5 KB client, zero hydration bugs | React Server Components + client components — 100KB+ React runtime, hydration mismatch bugs |
| Build output | Single native binary — vn build --release, no runtime to install |
Node.js project + node_modules + build artifacts — deploy requires Node runtime |
| API docs | Auto-generated OpenAPI spec from route summaries — app.enable_docs("/docs") |
Swagger/OpenAPI setup requires additional libraries and decorator configuration |
| Live reload | Built-in — vn dev hot-pushes updated components over WebSocket |
Turbopack or webpack HMR — fast but needs configuration |
| Form validation | Built-in — _validate.get_field(), _validate.get_keys(), schema annotations on routes |
Zod, Yup, or custom validation — all npm install |
| CORS / rate limiting | Built-in — cors() and rate_limit() middleware, one-liner setup |
cors + express-rate-limit or similar — npm install each |
| Supply chain risk | Near zero — one C codebase, no transitive dependency tree | 1000+ packages from npm, each a potential vector (event-stream, colors.js, etc.) |
flash_on What setup looks like in practice
Next.js setup checklist (real-world ecommerce app):
- Install Node.js (
nvm install) - Create project (
npx create-next-app) - Install database driver (
npm install @prisma/client+npm install prisma --dev) - Set up Prisma schema + migrations (
npx prisma init,npx prisma migrate) - Install auth (
npm install next-auth+ providers) - Configure NextAuth route handler, session provider, callbacks
- Install form validation (
npm install zod+@hookform/resolvers) - Set up CORS middleware for API routes
- Configure
next.config.js,tailwind.config.js,tsconfig.json - Deploy: install Node on server, copy
node_modules, runnpm run build+npm start
Aurora setup checklist (same ecommerce app):
vn new myapp— createsmain.vn,pages/,lib/,public/, schema, config, middleware, auth scaffoldvn dev— live-reload dev server with everything mounted- Deploy:
vn build --release→ copy one binary to server, run it
category When to use Aurora vs Lumen vs Zenith
The toolchain auto-detects the project type, but here is how to think about which one fits your project:
| Project shape | Use | Scaffold |
|---|---|---|
| Fullstack web app (backend + UI with reactive components) | Aurora | vn new |
| Frontend-only (static or interactive site, no server API) | Lumen | vn lumen new |
| API server or microservice (no frontend) | Zenith | manual |
| CLI tool or script | Varian | vn init |
vn build prints the detected type: "Aurora" when both pages/ and a Zenith entry exist, "Lumen" for frontend-only, "Zenith" for server-only.