Sync local packages into the registry repo and update index.json and README.md to include all 9 packages.
205 lines
5.9 KiB
Markdown
205 lines
5.9 KiB
Markdown
# 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
|
|
|
|
```lux
|
|
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
|
|
|
|
```lux
|
|
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
|
|
|
|
```lux
|
|
web.get(pattern, handler): Route
|
|
web.post(pattern, handler): Route
|
|
web.put(pattern, handler): Route
|
|
web.delete(pattern, handler): Route
|
|
```
|
|
|
|
### `web.page` — Layout & Components
|
|
|
|
```lux
|
|
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
|
|
|
|
```lux
|
|
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
|
|
|
|
```lux
|
|
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
|
|
|
|
```lux
|
|
web.saveSubscriber(dbPath, email): Result<Unit, String> with {Sql}
|
|
web.getSubscribers(dbPath): List<String> with {Sql}
|
|
```
|
|
|
|
### `web.seo` — SEO Helpers
|
|
|
|
```lux
|
|
web.metaTags(config): String
|
|
web.structuredData(name, description, url): String
|
|
web.sitemap(pages): String
|
|
web.robots(sitemapUrl): String
|
|
```
|
|
|
|
### `web.static` — Static File Serving
|
|
|
|
```lux
|
|
web.serveStatic(basePath, requestPath): Response with {File}
|
|
web.mimeType(path): String
|
|
web.cacheHeaders(maxAge): List<(String, String)>
|
|
```
|
|
|
|
### `web.middleware` — Composable Middleware
|
|
|
|
```lux
|
|
web.logging(): Middleware with {Console}
|
|
web.cors(origin): Middleware
|
|
web.securityHeaders(): Middleware
|
|
```
|
|
|
|
### Response Helpers
|
|
|
|
```lux
|
|
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
|
|
```
|
|
|
|
2. Add dependency in `lux.toml`:
|
|
```toml
|
|
[dependencies]
|
|
web = { version = "0.1.0", path = "../lux/packages/web" }
|
|
```
|
|
|
|
3. Define your routes and pages in `main.lux`
|
|
4. Add blog posts as markdown files in `content/blog/`
|
|
5. Run: `lux main.lux`
|
|
|
|
## Guide: Add a Blog
|
|
|
|
```lux
|
|
// 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
|
|
|
|
```lux
|
|
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.
|