docs: add guide covering routing, state, HTML DSL, pubsub, and project structure
This commit is contained in:
179
docs/getting-started.md
Normal file
179
docs/getting-started.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Getting Started
|
||||
|
||||
Via is a server-side reactive web framework for Go. The browser connects over SSE (Server-Sent Events), and all state lives on the server — signals, actions, and view rendering happen in Go. The browser is a thin display layer that Datastar keeps in sync via DOM morphing.
|
||||
|
||||
## Core Loop
|
||||
|
||||
Every Via app follows the same pattern:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/ryanhamamura/via"
|
||||
"github.com/ryanhamamura/via/h"
|
||||
)
|
||||
|
||||
func main() {
|
||||
v := via.New()
|
||||
|
||||
v.Config(via.Options{
|
||||
DocumentTitle: "My App",
|
||||
})
|
||||
|
||||
v.Page("/", func(c *via.Context) {
|
||||
count := 0
|
||||
step := c.Signal(1)
|
||||
|
||||
increment := c.Action(func() {
|
||||
count += step.Int()
|
||||
c.Sync()
|
||||
})
|
||||
|
||||
c.View(func() h.H {
|
||||
return h.Div(
|
||||
h.P(h.Textf("Count: %d", count)),
|
||||
h.Label(
|
||||
h.Text("Step: "),
|
||||
h.Input(h.Type("number"), step.Bind()),
|
||||
),
|
||||
h.Button(h.Text("+"), increment.OnClick()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
v.Start()
|
||||
}
|
||||
```
|
||||
|
||||
What happens:
|
||||
|
||||
1. `via.New()` creates the app, starts an embedded NATS server, and registers internal routes (`/_sse`, `/_action/{id}`, `/_navigate`, `/_session/close`).
|
||||
2. `v.Config()` applies settings.
|
||||
3. `v.Page()` registers a route. The init function receives a `*Context` where you define signals, actions, and the view.
|
||||
4. `v.Start()` starts the HTTP server and blocks until SIGINT/SIGTERM.
|
||||
|
||||
When a browser hits the page, Via creates a new `Context`, runs the init function, renders the full HTML document, and opens an SSE connection. From that point, every `c.Sync()` re-renders the view and pushes a DOM patch to the browser.
|
||||
|
||||
## Configuration
|
||||
|
||||
```go
|
||||
v.Config(via.Options{
|
||||
DevMode: true,
|
||||
ServerAddress: ":8080",
|
||||
LogLevel: via.LogLevelDebug,
|
||||
DocumentTitle: "My App",
|
||||
Plugins: []via.Plugin{MyPlugin},
|
||||
SessionManager: sm,
|
||||
PubSub: customBackend,
|
||||
ContextTTL: 60 * time.Second,
|
||||
ActionRateLimit: via.RateLimitConfig{Rate: 20, Burst: 40},
|
||||
})
|
||||
```
|
||||
|
||||
| Field | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `DevMode` | `false` | Enables context persistence across restarts, console logger, and Datastar inspector widget |
|
||||
| `ServerAddress` | `":3000"` | HTTP listen address |
|
||||
| `LogLevel` | `InfoLevel` | Minimum log level. Use `via.LogLevelDebug`, `LogLevelInfo`, `LogLevelWarn`, `LogLevelError` |
|
||||
| `Logger` | (auto) | Replace the default logger entirely. When set, `LogLevel` and `DevMode` have no effect on logging |
|
||||
| `DocumentTitle` | `"⚡ Via"` | The `<title>` of the HTML document |
|
||||
| `Plugins` | `nil` | Slice of plugin functions executed during `Config()` |
|
||||
| `SessionManager` | in-memory | Cookie-based session manager. See [PubSub and Sessions](pubsub-and-sessions.md) |
|
||||
| `DatastarContent` | (embedded) | Custom Datastar JS bytes |
|
||||
| `DatastarPath` | `"/_datastar.js"` | URL path for the Datastar script |
|
||||
| `PubSub` | embedded NATS | Custom PubSub backend. Replaces the default NATS. See [PubSub and Sessions](pubsub-and-sessions.md) |
|
||||
| `ContextTTL` | `30s` | Max time a context survives without an SSE connection before cleanup. Negative value disables the reaper |
|
||||
| `ActionRateLimit` | `10 req/s, burst 20` | Default token-bucket rate limiter for action endpoints. Rate of `-1` disables limiting |
|
||||
|
||||
## Static Files
|
||||
|
||||
Serve files from a directory:
|
||||
|
||||
```go
|
||||
v.Static("/assets/", "./static")
|
||||
```
|
||||
|
||||
Or from an embedded filesystem:
|
||||
|
||||
```go
|
||||
//go:embed static
|
||||
var staticFS embed.FS
|
||||
|
||||
v.StaticFS("/assets/", staticFS)
|
||||
```
|
||||
|
||||
Both disable directory listing and return 404 for directory paths.
|
||||
|
||||
## Head and Foot Injection
|
||||
|
||||
Add elements to every page's `<head>` or end of `<body>`:
|
||||
|
||||
```go
|
||||
v.AppendToHead(
|
||||
h.Link(h.Rel("stylesheet"), h.Href("/assets/style.css")),
|
||||
h.Meta(h.Attr("name", "viewport"), h.Attr("content", "width=device-width, initial-scale=1")),
|
||||
)
|
||||
|
||||
v.AppendToFoot(
|
||||
h.Script(h.Src("/assets/app.js")),
|
||||
)
|
||||
```
|
||||
|
||||
These are additive and affect all pages globally.
|
||||
|
||||
## Plugins
|
||||
|
||||
A plugin is a `func(v *via.V)` that mutates the app during configuration — registering routes, injecting assets, or applying middleware.
|
||||
|
||||
```go
|
||||
func PicoCSSPlugin(v *via.V) {
|
||||
v.HTTPServeMux().HandleFunc("GET /css/pico.css", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
w.Write(picoCSSBytes)
|
||||
})
|
||||
v.AppendToHead(h.Link(h.Rel("stylesheet"), h.Href("/css/pico.css")))
|
||||
}
|
||||
|
||||
// Usage:
|
||||
v.Config(via.Options{
|
||||
Plugins: []via.Plugin{PicoCSSPlugin},
|
||||
})
|
||||
```
|
||||
|
||||
Plugins have full access to the `*V` public API: `HTTPServeMux()`, `AppendToHead()`, `AppendToFoot()`, `Config()`, etc.
|
||||
|
||||
## DevMode
|
||||
|
||||
Enable during development for a better feedback loop:
|
||||
|
||||
```go
|
||||
v.Config(via.Options{DevMode: true})
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
- **Console logger** — Human-readable log output with timestamps.
|
||||
- **Context persistence** — Saves context-to-route mappings to `.via/devmode/ctx.json`. On server restart, reconnecting browsers restore their state instead of getting a blank page. Pair with [Air](https://github.com/air-verse/air) for hot-reloading.
|
||||
- **Datastar inspector** — Injects a widget showing live signal values and SSE activity.
|
||||
|
||||
## Custom HTTP Handlers
|
||||
|
||||
Access the underlying `*http.ServeMux` for custom routes:
|
||||
|
||||
```go
|
||||
mux := v.HTTPServeMux()
|
||||
mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
```
|
||||
|
||||
Register custom handlers before calling `v.Start()`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [State and Interactivity](state-and-interactivity.md) — Signals, actions, components, validation
|
||||
- [Routing and Navigation](routing-and-navigation.md) — Multi-page apps, middleware, SPA navigation
|
||||
- [PubSub and Sessions](pubsub-and-sessions.md) — Real-time messaging, persistent sessions
|
||||
- [HTML DSL](html-dsl.md) — The `h` package reference
|
||||
- [Project Structure](project-structure.md) — Organizing files as your app grows
|
||||
Reference in New Issue
Block a user