Files
pkgs.lux/packages/web/README.md
Brandon Lucas cbb66fbb73 Add frontmatter, markdown, path, xml, rss, and web packages
Sync local packages into the registry repo and update index.json
and README.md to include all 9 packages.
2026-02-24 21:04:20 -05:00

5.9 KiB

Lux Web Framework

A full-stack web framework for building websites with Lux. Provides routing, layouts, a blog engine, form processing, SEO helpers, static file serving, and middleware — all built on Lux's effect system.

Quick Start

import web

fn main(): Unit with {Console, HttpServer, File} = {
    let app = web.app("My Site", "A Lux website", "./static", "./content")
    let app = web.addRoute(app, web.get("/", homePage))
    let app = web.addRoute(app, web.get("/about", aboutPage))
    web.serve(app, 8080)
}

fn homePage(req: web.Request): web.Response =
    web.htmlResponse(200,
        web.layout(myLayout, "Home | My Site", "Welcome",
            web.hero("Welcome", "Build something great", "/about", "Learn More")
        )
    )

Features

Feature Description
Effects-based Every handler declares its side effects (File, Sql, HttpServer)
Type-safe Elm-style Html builder catches broken markup at compile time
Zero JS Server-rendered HTML. No client bundle. Pages work without JavaScript
Blog engine Markdown + frontmatter blog with zero config
SQLite built-in Sql effect is a language primitive. No ORM setup
Single binary Compile to C via lux compile for single binary deployment

API Reference

web.app — Application Builder

web.app(title, description, staticDir, contentDir): App
web.addRoute(app, route): App
web.addMiddleware(app, middleware): App
web.serve(app, port): Unit with {Console, HttpServer, File}

Route Builders

web.get(pattern, handler): Route
web.post(pattern, handler): Route
web.put(pattern, handler): Route
web.delete(pattern, handler): Route

web.page — Layout & Components

web.layout(config, title, description, body): String
web.hero(title, subtitle, ctaUrl, ctaText): String
web.miniHero(title, subtitle): String
web.card(icon, title, description): String
web.cardGrid(cards): String
web.statsBar(stats): String
web.emailSignup(heading, buttonText, action): String
web.ctaSection(heading, ctaUrl, ctaText): String

web.blog — Blog Engine

web.loadPosts(contentDir, section): List<Post> with {File}
web.latestPosts(contentDir, section, count): List<Post> with {File}
web.loadPost(contentDir, section, slug): Option<Post> with {File}
web.blogIndexHtml(posts): String
web.blogPostHtml(post): String
web.rssFeed(title, url, description, posts): String

web.form — Form Processing

web.parseBody(body): List<(String, String)>
web.getField(fields, name): Option<String>
web.requireField(fields, name): Result<String, String>
web.validateEmail(email): Result<String, String>
web.sanitize(input): String

web.db — Database Helpers

web.saveSubscriber(dbPath, email): Result<Unit, String> with {Sql}
web.getSubscribers(dbPath): List<String> with {Sql}

web.seo — SEO Helpers

web.metaTags(config): String
web.structuredData(name, description, url): String
web.sitemap(pages): String
web.robots(sitemapUrl): String

web.static — Static File Serving

web.serveStatic(basePath, requestPath): Response with {File}
web.mimeType(path): String
web.cacheHeaders(maxAge): List<(String, String)>

web.middleware — Composable Middleware

web.logging(): Middleware with {Console}
web.cors(origin): Middleware
web.securityHeaders(): Middleware

Response Helpers

web.htmlResponse(status, body): Response
web.jsonResponse(status, body): Response
web.textResponse(status, body): Response
web.redirect(location): Response
web.redirectPermanent(location): Response

Guide: Build a Marketing Site

  1. Create project structure:
my-site/
├── lux.toml
├── main.lux
├── content/blog/
├── static/styles.css
  1. Add dependency in lux.toml:
[dependencies]
web = { version = "0.1.0", path = "../lux/packages/web" }
  1. Define your routes and pages in main.lux
  2. Add blog posts as markdown files in content/blog/
  3. Run: lux main.lux

Guide: Add a Blog

// Load and display blog posts
let app = web.addRoute(app, web.get("/blog", fn(req: web.Request): web.Response with {File} => {
    let posts = web.loadPosts("./content", "blog")
    web.htmlResponse(200, web.layout(myLayout, "Blog", "Our blog",
        web.miniHero("Blog", "Latest posts") + web.blogIndexHtml(posts)
    ))
}))

// Individual post pages
let app = web.addRoute(app, web.get("/blog/:slug", fn(req: web.Request): web.Response with {File} => {
    let slug = web.getPathParam(req.path, "/blog/:slug", "slug")
    match slug {
        Some(s) => match web.loadPost("./content", "blog", s) {
            Some(post) => web.htmlResponse(200, web.layout(myLayout, post.title, post.excerpt, web.blogPostHtml(post))),
            None => web.htmlResponse(404, "Not found")
        },
        None => web.htmlResponse(404, "Not found")
    }
}))

Guide: Handle Form Submissions

let app = web.addRoute(app, web.post("/api/subscribe", fn(req: web.Request): web.Response with {Sql} => {
    let fields = web.parseBody(req.body)
    match web.getField(fields, "email") {
        Some(email) => match web.validateEmail(email) {
            Ok(validEmail) => {
                web.saveSubscriber("data/subscribers.db", validEmail)
                web.redirect("/thanks")
            },
            Err(msg) => web.htmlResponse(400, msg)
        },
        None => web.htmlResponse(400, "Email required")
    }
}))

Architecture

The framework is built on Lux's algebraic effect system:

  • HttpServer effect — Accepts connections and sends responses
  • File effect — Reads static files and markdown content
  • Sql effect — SQLite database for form submissions
  • Console effect — Request logging

Effects are declared in function signatures, making it clear what each handler does. For testing, swap effect handlers to mock the database, filesystem, or HTTP server.