Sync local packages into the registry repo and update index.json and README.md to include all 9 packages.
5.9 KiB
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
- Create project structure:
my-site/
├── lux.toml
├── main.lux
├── content/blog/
├── static/styles.css
- Add dependency in
lux.toml:
[dependencies]
web = { version = "0.1.0", path = "../lux/packages/web" }
- Define your routes and pages in
main.lux - Add blog posts as markdown files in
content/blog/ - 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:
HttpServereffect — Accepts connections and sends responsesFileeffect — Reads static files and markdown contentSqleffect — SQLite database for form submissionsConsoleeffect — 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.