Documentation
¶
Overview ¶
Package folio is a small server-rendered web framework built on net/http and html/template.
Pages are files under the templates directory's routes/ folder. Every other .tmpl file (layouts, partials) is shared but never served directly:
templates/
root.tmpl {{define "layout/app"}} ... {{.HTML}} ... {{end}}
partials/header.tmpl {{define "header"}} ... {{end}}
routes/index.tmpl -> /
routes/about.tmpl -> /about
routes/tools/index.tmpl -> /tools
routes/journal/[id].tmpl -> /journal/42 (c.Param("id") == "42")
Go code attaches behaviour to routes with Load (runs on every request to the route) and Action (runs on POST, before Load).
Index ¶
- Constants
- func DecodeJSON(w http.ResponseWriter, r *http.Request, v any) error
- func JSON(w http.ResponseWriter, status int, v any) error
- func SafeNext(r *http.Request, fallback string) string
- func WantsJSON(r *http.Request) bool
- type App
- func (a *App) Action(route string, fn LoadFunc)
- func (a *App) Group(prefix string, mw ...Middleware)
- func (a *App) Handle(pattern string, h http.Handler)
- func (a *App) HandleFunc(pattern string, h func(http.ResponseWriter, *http.Request))
- func (a *App) Handler() http.Handler
- func (a *App) ListenAndServe(addr string) error
- func (a *App) Load(route string, fn LoadFunc)
- func (a *App) LoadAll(fn LoadFunc)
- func (a *App) RequireLogin(cfg LoginConfig) Middleware
- func (a *App) Routes() []string
- func (a *App) Use(mw ...Middleware)
- type Config
- type Ctx
- func (c *Ctx) Done() bool
- func (c *Ctx) Fail(message string, fieldErrors map[string]string)
- func (c *Ctx) Flash(message string)
- func (c *Ctx) Get(key string) any
- func (c *Ctx) Header() http.Header
- func (c *Ctx) JSON(status int, v any) error
- func (c *Ctx) Param(name string) string
- func (c *Ctx) Redirect(url string, code int)
- func (c *Ctx) Session() *Session
- func (c *Ctx) Set(key string, value any)
- func (c *Ctx) SetModel(v any)
- func (c *Ctx) Status(code int)
- func (c *Ctx) Success(message string)
- func (c *Ctx) Writer() http.ResponseWriter
- type LoadFunc
- type LoginConfig
- type MemoryStore
- type Middleware
- type Session
- type SessionStore
- type View
Constants ¶
const ClientPrefix = "/_folio/"
ClientPrefix is where folio serves its own files. Add the client script to your shell layout's <head> with:
{{template "folio/head" .}}
const MaxJSONBody = 1 << 20
MaxJSONBody is the largest request body DecodeJSON accepts.
Variables ¶
This section is empty.
Functions ¶
func DecodeJSON ¶
DecodeJSON reads a single JSON value from the request body into v. It rejects unknown fields, trailing data and bodies over MaxJSONBody.
func JSON ¶
func JSON(w http.ResponseWriter, status int, v any) error
JSON writes v as a JSON response with the given status.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is a folio application. Create one with New.
func (*App) Action ¶
Action registers the POST handler for route. It runs before the route's Load functions, so the page reflects whatever the action changed. A route without an action answers POST with 405.
func (*App) Group ¶
func (a *App) Group(prefix string, mw ...Middleware)
Group adds middleware that only runs for URLs under prefix, e.g. app.Group("/admin", requireAdmin) covers /admin and /admin/users but not /administrator. Groups run inside the app-wide middleware, in the order they were added.
func (*App) Handle ¶
Handle registers a plain http.Handler for a pattern, for things that aren't pages (JSON endpoints, health checks, ...). Patterns follow http.ServeMux.
func (*App) HandleFunc ¶
HandleFunc is Handle for a function.
func (*App) Handler ¶
Handler returns the app wrapped in its middleware. Middleware and groups added after the call don't apply to the returned handler.
func (*App) ListenAndServe ¶
ListenAndServe serves the app on addr and shuts down gracefully on SIGINT/SIGTERM.
func (*App) Load ¶
Load registers a function that runs on every GET, HEAD and POST request to route, in registration order. Route names are the file path under routes/ without the extension or a trailing /index: "index" for the root page, "tools", "journal/[id]".
func (*App) LoadAll ¶
LoadAll registers a function that runs before the route's own hooks on every page, e.g. to load the signed-in user for the header. It may redirect, which skips the page.
func (*App) RequireLogin ¶
func (a *App) RequireLogin(cfg LoginConfig) Middleware
RequireLogin sends visitors without cfg.SessionKey in their session to the login page; requests that want JSON get 401 instead. Use it with Use for a mostly-private app, or with Group to protect one section.
func (*App) Use ¶
func (a *App) Use(mw ...Middleware)
Use appends middleware for every request. The first one added is the outermost.
type Config ¶
type Config struct {
// Templates holds the template tree (see the package docs). Pass an
// embed.FS sub-tree to ship templates inside the binary.
// Default: os.DirFS("templates").
Templates fs.FS
// Static is served under StaticPrefix. Default: os.DirFS("static").
Static fs.FS
// StaticPrefix is the URL prefix for Static. Default: "/static/".
StaticPrefix string
// Layout is the outermost layout (the "shell") wrapped around every page
// that doesn't have a shell/<prefix> template of its own. If it isn't
// defined, pages are written without one. Default: "layout/app".
Layout string
// Funcs is made available to every template, alongside the built-in
// "asset" function.
Funcs template.FuncMap
// Globals are copied into every page's data before hooks run.
Globals map[string]any
// Reload turns on development mode: templates are re-parsed on every
// request, the browser reloads when templates or static files change,
// and 500 pages show the error.
Reload bool
// StrictTemplates makes a template fail when it reads a page-data key
// that was never set, instead of printing nothing. Use {{index .Page
// "key"}} for keys that are legitimately optional.
StrictTemplates bool
// SecretKey encrypts and signs the session cookie. Use at least 32
// random bytes and keep it stable across restarts and instances. If it's
// empty a random key is generated, so sessions end when the process
// restarts.
SecretKey []byte
// SessionStore keeps sessions on the server, with only a random ID in
// the cookie, so they can be ended server-side. Default: nil, meaning
// sessions live in an encrypted cookie (see Session).
SessionStore SessionStore
// SessionCookie is the session cookie's name. Default: "folio_session".
SessionCookie string
// SessionMaxAge is how long a session lasts. Default: 30 days.
SessionMaxAge time.Duration
// TrustedOrigins may make cross-origin POST requests, e.g.
// "https://admin.example.com". Same-origin requests are always allowed.
TrustedOrigins []string
// DisableCSRF turns off cross-origin request protection.
DisableCSRF bool
// MaxFormMemory caps the memory used to parse a multipart form; larger
// files are stored on disk. Default: 32 MB.
MaxFormMemory int64
// Logger defaults to a text logger on stdout.
Logger *slog.Logger
}
Config configures an App. The zero value is usable: templates are read from ./templates and static files from ./static, relative to the working directory.
type Ctx ¶
type Ctx struct {
Request *http.Request
// Route is the matched route name, e.g. "index" or "journal/[id]".
Route string
// Page is the page data, available to templates as .Page.
Page map[string]any
Logger *slog.Logger
// contains filtered or unexported fields
}
Ctx is the per-request state passed to Load and Action functions.
func (*Ctx) Flash ¶
Flash stores a message that the next rendered page receives as .Flash. Pair it with Redirect after a successful form post.
func (*Ctx) Param ¶
Param returns a [param] value from the URL, e.g. c.Param("id") for routes/journal/[id].tmpl.
func (*Ctx) Redirect ¶
Redirect sends a redirect and skips rendering. Use http.StatusSeeOther after a successful form post to avoid resubmission on refresh.
func (*Ctx) SetModel ¶
SetModel passes a typed value to the template as .Model. Unlike .Page keys, a misspelled field on a struct is an error when the template runs.
func (*Ctx) Writer ¶
func (c *Ctx) Writer() http.ResponseWriter
Writer returns the raw ResponseWriter. Anything written through it takes over the response: remaining hooks are skipped and the page isn't rendered.
type LoginConfig ¶
type LoginConfig struct {
// SessionKey is the session value that marks a signed-in user, e.g.
// "user_id". Login sets it; logout clears the session.
SessionKey string
// LoginURL is where signed-out visitors are sent. The page they wanted
// is passed as ?next=, ready for SafeNext. Default: "/login".
LoginURL string
// Public paths don't need a login. An entry ending in "/" covers
// everything under it. LoginURL, the static prefix and folio's own
// files are always public.
Public []string
}
LoginConfig configures RequireLogin.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is a SessionStore kept in process memory. Sessions are lost on restart and aren't shared between instances; use it for development or single-instance apps.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty MemoryStore.
type Middleware ¶
Middleware wraps an http.Handler.
func Compress ¶
func Compress() Middleware
Compress gzips text responses (HTML, CSS, JS, JSON, SVG, WebAssembly) for clients that accept it.
func Recoverer ¶
func Recoverer(logger *slog.Logger) Middleware
Recoverer turns a panic in a handler into a 500 response instead of a dropped connection.
func RequestLogger ¶
func RequestLogger(logger *slog.Logger) Middleware
RequestLogger logs each request's method, path, status and duration.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is a small key-value store for one visitor. Changes are saved when the response headers are written.
By default the values live in an encrypted, signed cookie: no server storage is needed, but a session can't be ended from the server before it expires and everything must fit in 4 KB. Set Config.SessionStore to keep values on the server instead; the cookie then holds only a random ID.
func GetSession ¶
GetSession returns the request's session. Requests that didn't go through App.Handler get an empty session whose changes are discarded.
func (*Session) Clear ¶
func (s *Session) Clear()
Clear removes every value and ends the session; with a SessionStore the server-side record is deleted too. Call it on logout.
type SessionStore ¶
type SessionStore interface {
// Load returns the values for id, or nil if there's no live session.
Load(ctx context.Context, id string) (map[string]string, error)
// Save stores values for id until expires.
Save(ctx context.Context, id string, values map[string]string, expires time.Time) error
// Delete removes the session; deleting a missing session is not an error.
Delete(ctx context.Context, id string) error
}
SessionStore keeps session values on the server. Implement it over your database to share sessions between instances or to end a user's sessions from the server (e.g. after a password change).
type View ¶
type View struct {
// Route is the matched route name, e.g. "index" or "journal/[id]".
Route string
// Path is the request's URL path.
Path string
// Params holds the route's [param] values.
Params map[string]string
// Page is the page data set by hooks and Config.Globals. It always has
// Message, Failure and Errors (a map of field name to message).
Page map[string]any
// Model is whatever a hook passed to Ctx.SetModel.
Model any
// Flash is the message a previous request stored with Ctx.Flash.
Flash string
// HTML is the rendered page, or the next layout in. Layouts only.
HTML template.HTML
// Shell is the outermost layout's name.
Shell string
// Status is the response status code.
Status int
// Dev is true when Config.Reload is on.
Dev bool
// Error describes what went wrong. Error pages in dev mode only.
Error string
}
View is what every page, layout and error template receives as its dot.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
folio
command
Command folio creates new folio projects.
|
Command folio creates new folio projects. |
|
Package foliotest drives a folio app (or any http.Handler) in tests, keeping cookies between requests like a browser.
|
Package foliotest drives a folio app (or any http.Handler) in tests, keeping cookies between requests like a browser. |
|
Package validator collects per-field form validation errors.
|
Package validator collects per-field form validation errors. |
|
Package wasm runs Go WebAssembly code for folio pages.
|
Package wasm runs Go WebAssembly code for folio pages. |