Help us shape the future of Go! Take the 2026 Go Developer Survey

folio

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 35 Imported by: 0

README

folio

A small server-rendered web framework for Go, built on net/http and html/template, with no third-party dependencies.

  • File-based routing. Drop a template in templates/routes/ and it's a page. URL parameters work too: routes/journal/[id].tmpl serves /journal/42.
  • Load and Action hooks. These run Go code for a route before it renders, similar to SvelteKit's load/actions.
  • Nested layouts. Layouts can wrap each other, and a folder can swap in its own outer layout (a "shell").
  • Sessions and flash messages. By default they live in an encrypted cookie, or you can keep them on the server through a pluggable store.
  • CSRF protection is on by default, with no tokens to add to your forms.
  • Login-required areas. Route groups take middleware, and RequireLogin sends visitors to a login page with a safe next link back.
  • In-place navigation and forms. The bundled client script updates pages without full reloads, and forms work without JavaScript too.
  • Dev mode. Templates reload automatically, the browser refreshes when files change, and errors show on the page.
  • Production details. gzip, cacheable fingerprinted assets, graceful shutdown and JSON helpers.
  • Extras. A WebAssembly page bridge, a test client, and a folio new command to start projects.

Contents

Start a project

go install github.com/AlexDev404/folio/cmd/folio@latest
folio new myapp            # or: folio new -module github.com/me/myapp myapp
cd myapp
go run . -dev              # http://localhost:4000

The generated app includes a layout, three pages (one with a URL parameter), a validated form that uses a flash message and a redirect, and a test.

To add folio to an existing module, run go get github.com/AlexDev404/folio. folio needs Go 1.25 or newer.

Routing

Every .tmpl file under the templates directory is parsed into one template set. Only files under routes/ are pages:

File Route URL
routes/index.tmpl index /
routes/about.tmpl about /about
routes/tools/index.tmpl tools /tools
routes/journal/[id].tmpl journal/[id] /journal/42
routes/journal/new.tmpl journal/new /journal/new (wins over [id])
routes/files/[...path].tmpl files/[...path] /files/a/b.txt (path = a/b.txt)
  • Page files. A route file can be the page markup itself, or it can {{define "<route>"}} it.
  • Shared templates. Everything outside routes/, such as layouts and partials in any folder, is shared and never served directly.
  • Matching order. A fixed route always wins over one with parameters. Among parameter routes, the one with more fixed segments wins, and [...catch-all] routes are tried last.
  • Reading parameters. Hooks read them with c.Param("id"), and templates use {{.Params.id}}.

Hooks

app.LoadAll(fn)           // every page, before the route's own hooks (e.g. load the signed-in user)
app.Load(route, fn)       // GET, HEAD and POST; several allowed, run in order
app.Action(route, fn)     // POST only; runs before Load so the page shows the result

All three take a func(c *folio.Ctx) error:

Method Description
c.Set(k, v) / c.Get(k) Page data, read in templates as .Page.k
c.SetModel(v) Typed data for the template, read as .Model. A misspelled field is an error, not blank output
c.Param(name) A URL parameter
c.Status(code) The response status, e.g. 422 for invalid forms
c.Success(msg) / c.Fail(msg, fieldErrors) Set .Page.Message, or .Page.Failure and .Page.Errors
c.Flash(msg) A message for the next page, read as .Flash. Pair it with Redirect
c.Redirect(url, code) Redirects and skips rendering
c.JSON(status, v) Responds with JSON and skips rendering
c.Session() The visitor's session
c.Header() / c.Writer() Raw response access. Writing through Writer takes over the response
c.Request, c.Route, c.Logger The request, the route name, and a logger tagged with the route
  • Errors. A hook that returns an error renders the 500 page.
  • Unregistered POSTs. A POST to a route without an Action gets a 405.
  • Typo warning. Hooks registered for a route that has no template are logged as a warning at startup, so a typo like Load("tool", ...) is easy to spot.
  • Other endpoints. For APIs, health checks and the like, use app.Handle(pattern, handler) or app.HandleFunc. Patterns follow http.ServeMux, e.g. "POST /api/moods".

Templates

Every page, layout and error template receives a folio.View:

Field Description
.Route The route name, e.g. journal/[id]
.Path The URL path
.Params URL parameters
.Page Page data from hooks and Config.Globals. Always contains Message, Failure and Errors
.Model Whatever a hook passed to SetModel
.Flash A flash message from the previous request
.HTML The rendered page, or the next layout in (layouts only)
.Shell, .Status, .Dev The outer layout's name, the status code, and whether dev mode is on

Templates can also use these built-ins:

  • {{template "folio/head" .}}: put this in the shell's <head>. It adds the client script and the route metadata it needs.
  • {{asset "app.css"}}: a cache-busting static URL, e.g. /static/app.css?v=3f2a91c0de.
  • folio/title: define this template to control the document title. Use it in your <title>; folio also sends it when navigating in place, so the title updates correctly.
{{define "folio/title"}}{{with .Page.Title}}{{.}} · {{end}}{{.Page.AppName}}{{end}}

Register your own functions with Config.Funcs. Set StrictTemplates: true to make a misspelled .Page key fail the page instead of printing nothing. For keys that are legitimately optional, use {{index .Page "key"}} or {{index .Page.Errors "field"}}.

Layouts

Pages are wrapped from the inside out:

  1. layout/<folder> templates for each folder above the page, nearest first. A page under docs/guides/ uses layout/docs/guides, then layout/docs.
  2. The shell, which is the outermost layout. By default it's Config.Layout (layout/app). A shell/<folder> template replaces it for everything under that folder, e.g. a bare shell/print for printable pages.

Each layout outputs {{.HTML}} where the inner content goes. The shell must contain the element that in-place navigation swaps: id="content" or data-folio-content.

Forms and validation

app.Action("journal/new", func(c *folio.Ctx) error {
	v := validator.NewValidator()
	title := c.Request.PostFormValue("title")
	v.Check(validator.NotBlank(title), "title", "Title is required")
	v.Check(validator.MaxLength(title, 100), "title", "100 characters at most")
	if !v.ValidData() {
		c.Status(http.StatusUnprocessableEntity)
		c.Fail("Please fix the errors below.", v.Errors)
		c.Set("title", title) // refill the form
		return nil
	}
	id, err := entries.Insert(title)
	if err != nil {
		return err
	}
	c.Flash("Saved!")
	c.Redirect("/journal/"+id, http.StatusSeeOther)
	return nil
})
<form method="post" data-folio-form>
  <input name="title" value="{{.Page.title}}">
  {{with index .Page.Errors "title"}}<p class="error">{{.}}</p>{{end}}
  <button>Save</button>
</form>
  • Accepted encodings. Both URL-encoded and multipart forms (file uploads) are parsed.
  • Without JavaScript. The form above posts normally.
  • With the client script. data-folio-form submits it in place.

Sessions, flash messages and login

app, _ := folio.New(folio.Config{SecretKey: []byte(os.Getenv("SESSION_SECRET"))}) // 32+ bytes

app.Action("login", func(c *folio.Ctx) error {
	user, err := users.Authenticate(c.Request.PostFormValue("email"), c.Request.PostFormValue("password"))
	if err != nil {
		c.Fail("Invalid email or password", nil)
		return nil
	}
	c.Session().Renew() // new session ID after login
	c.Session().Set("user_id", user.ID)
	c.Redirect(folio.SafeNext(c.Request, "/"), http.StatusSeeOther)
	return nil
})

app.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) {
	folio.GetSession(r).Clear()
	http.Redirect(w, r, "/login", http.StatusSeeOther)
})

// Make the user available to every page (e.g. for the header).
app.LoadAll(func(c *folio.Ctx) error {
	if id := c.Session().Get("user_id"); id != "" {
		user, err := users.Get(id)
		if err != nil {
			return err
		}
		c.Set("User", user)
	}
	return nil
})

// Everything except these paths needs a login...
app.Use(app.RequireLogin(folio.LoginConfig{
	SessionKey: "user_id",
	LoginURL:   "/login",
	Public:     []string{"/register", "/about"},
}))
// ...or protect just one section:
// app.Group("/admin", app.RequireLogin(folio.LoginConfig{SessionKey: "user_id"}))

Where sessions are stored. By default a session is an encrypted, signed cookie. That means no database is needed, but it has two limits:

  • No server-side logout. If a cookie is stolen, or you need to end someone's session after a password change, the server can't cancel it. It stays valid until it expires, after 30 days by default.
  • Size. Everything in the session has to fit in about 4 KB.

When either limit matters, set Config.SessionStore. The cookie then holds only a random ID, and Clear deletes the session on the server.

folio.Config{SessionStore: folio.NewMemoryStore()} // single instance; lost on restart

To keep sessions in your database, implement the three methods of folio.SessionStore (Load, Save, Delete).

Other session details:

  • Session cookie. It's HttpOnly, uses SameSite=Lax, and is marked Secure behind HTTPS.
  • Missing SecretKey. A random key is generated at startup, so every restart logs everyone out. folio logs a warning when this happens.
  • Login redirects. RequireLogin sends visitors to LoginURL?next=<the page they wanted>, and SafeNext reads that value back. Only paths on your own site are accepted, so //evil.com, /\evil.com and https://… all fall back to the default. Requests that ask for JSON get a 401 instead of a redirect.

Security

  • CSRF. Every app is protected by Go's http.CrossOriginProtection. It uses the Sec-Fetch-Site and Origin headers that browsers send to reject form posts and fetch calls from other sites. You don't need hidden tokens in your forms or headers in your scripts. Allow specific other origins with TrustedOrigins, or turn the check off with DisableCSRF.
  • Escaping. Templates escape output for HTML, URLs and JavaScript.
  • Routing. Only files under routes/ can be reached as pages. Static file serving doesn't list directories.

Middleware

app.Use(folio.RequestLogger(logger), folio.Recoverer(logger), folio.Compress())
app.Group("/admin", requireAdmin, auditLog)
  • Use applies to every request. The first middleware added is the outermost.
  • Group(prefix, ...) applies only to prefix and the paths under it, so /admin covers /admin/users but not /administrator.
  • Built-in middleware. RequestLogger logs method, path, status and duration. Recoverer turns panics into 500s. Compress gzips text, JSON, SVG and WebAssembly responses. RequireLogin is described above.

Client script

{{template "folio/head" .}} loads /_folio/folio.js. It's a classic script, not a module, so inline page scripts can use it straight away. Everything it does is opt-in:

Feature How to use it
Links that load in place Add data-folio-link to a link or to any ancestor, e.g. <nav data-folio-link>. Use data-folio-link="false" to opt a link back out
Forms that submit in place Add data-folio-form to the form. GET forms navigate, and POST forms show the response and follow redirects
Navigating from code folio.navigate(url), folio.submit(form)
Page setup Listen for appLoad on document. It fires on the first load and after every navigation, with detail: {path, route}
Cleanup on navigation Timers, intervals and animation frames are cleared automatically. Use folio.trackEventListener(target, type, fn) and folio.trackObserver(observer) for listeners and observers you want removed the same way

What the server sends. When the browser already shows the right shell, the server sends only the page content, not the whole document. If the next page uses a different shell, or the response is an error page, the browser falls back to a normal page load.

Inside the swapped content. Scripts run again, the page scrolls to the top (or to the #hash), and the title updates.

Static files

  • Where they're served. Config.Static is served under /static/. ETags are always sent and directories aren't listed.
  • Caching. URLs built with {{asset "file"}} include a content hash, so they're cached for a year and still update when the file changes. Other URLs must revalidate with the server.
  • Embedding. To ship templates and static files inside the binary:
//go:embed templates static
var files embed.FS

tmpl, _ := fs.Sub(files, "templates")
static, _ := fs.Sub(files, "static")
app, err := folio.New(folio.Config{Templates: tmpl, Static: static})

folio new sets this up, reading from disk with -dev and from the embedded copies otherwise.

JSON

app.HandleFunc("POST /api/moods", func(w http.ResponseWriter, r *http.Request) {
	var in struct{ Text string }
	if err := folio.DecodeJSON(w, r, &in); err != nil { // 1 MB limit, unknown fields rejected
		folio.JSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		return
	}
	folio.JSON(w, http.StatusCreated, in)
})

folio.WantsJSON(r) reports whether the client asked for JSON.

WebAssembly

The folio/wasm package runs Go code for each page, both on the first load and after in-place navigation:

//go:build js && wasm

func main() {
	wasm.Always(setUpNavigation) // every page
	wasm.Page("index", func() func() {
		// attach handlers for the home page
		return func() { /* optional cleanup before the next page */ }
	})
	wasm.Run()
}

Load the module as usual (wasm_exec.js plus WebAssembly.instantiateStreaming). The folio client script needs to be on the page too.

Testing

foliotest drives an app in memory and keeps cookies between requests, like a browser does:

func TestJournal(t *testing.T) {
	c := foliotest.New(t, app.Handler())
	c.Get("/journal/new").ExpectStatus(200)
	c.PostForm("/journal/new", url.Values{"title": {""}}).ExpectStatus(422).ExpectContains("Title is required")
	c.PostForm("/journal/new", url.Values{"title": {"Hi"}}).
		ExpectRedirect("/journal/1").
		Follow().
		ExpectContains("Saved!")
}

Development mode

Run with Reload: true (the -dev flag in generated apps) and folio will:

  • re-parse templates on every request;
  • reload the browser when templates or static files change, and again after the server restarts (e.g. when a watcher like air or gow rebuilds the binary);
  • show errors and their messages on the 500 page.

Config reference

Field Default Description
Templates os.DirFS("templates") Template tree
Static os.DirFS("static") Static files
StaticPrefix /static/ URL prefix for static files
Layout layout/app Default shell
Funcs — Extra template functions
Globals — Values copied into every page's .Page
Reload false Development mode
StrictTemplates false Fail on missing .Page keys
SecretKey random 32+ bytes that encrypt the session cookie
SessionStore — (cookie) Server-side session storage
SessionCookie folio_session Cookie name
SessionMaxAge 30 days Session lifetime
TrustedOrigins — Other origins allowed to POST
DisableCSRF false Turn off cross-origin protection
MaxFormMemory 32 MB Memory for multipart forms; larger files go to disk
Logger text to stdout *slog.Logger

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

View Source
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" .}}
View Source
const MaxJSONBody = 1 << 20

MaxJSONBody is the largest request body DecodeJSON accepts.

Variables

This section is empty.

Functions

func DecodeJSON

func DecodeJSON(w http.ResponseWriter, r *http.Request, v any) error

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.

func SafeNext

func SafeNext(r *http.Request, fallback string) string

SafeNext returns the request's "next" value (from the query or form) if it is a path on this site, and fallback otherwise. Use it to send users back after login without creating an open redirect.

func WantsJSON

func WantsJSON(r *http.Request) bool

WantsJSON reports whether the client prefers a JSON response, e.g. a fetch() call sending Accept: application/json.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is a folio application. Create one with New.

func New

func New(cfg Config) (*App, error)

New parses the templates and returns an App ready for hooks to be registered.

func (*App) Action

func (a *App) Action(route string, fn LoadFunc)

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

func (a *App) Handle(pattern string, h http.Handler)

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

func (a *App) HandleFunc(pattern string, h func(http.ResponseWriter, *http.Request))

HandleFunc is Handle for a function.

func (*App) Handler

func (a *App) Handler() http.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

func (a *App) ListenAndServe(addr string) error

ListenAndServe serves the app on addr and shuts down gracefully on SIGINT/SIGTERM.

func (*App) Load

func (a *App) Load(route string, fn LoadFunc)

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

func (a *App) LoadAll(fn LoadFunc)

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) Routes

func (a *App) Routes() []string

Routes lists the page routes found in the templates, sorted.

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) Done

func (c *Ctx) Done() bool

Done reports whether the response has already been written by a hook.

func (*Ctx) Fail

func (c *Ctx) Fail(message string, fieldErrors map[string]string)

Fail sets the page's Failure message and, optionally, per-field errors.

func (*Ctx) Flash

func (c *Ctx) Flash(message string)

Flash stores a message that the next rendered page receives as .Flash. Pair it with Redirect after a successful form post.

func (*Ctx) Get

func (c *Ctx) Get(key string) any

Get returns a stored value.

func (*Ctx) Header

func (c *Ctx) Header() http.Header

Header gives access to the response headers (cookies, caching, ...).

func (*Ctx) JSON

func (c *Ctx) JSON(status int, v any) error

JSON writes v as a JSON response and skips rendering.

func (*Ctx) Param

func (c *Ctx) Param(name string) string

Param returns a [param] value from the URL, e.g. c.Param("id") for routes/journal/[id].tmpl.

func (*Ctx) Redirect

func (c *Ctx) Redirect(url string, code int)

Redirect sends a redirect and skips rendering. Use http.StatusSeeOther after a successful form post to avoid resubmission on refresh.

func (*Ctx) Session

func (c *Ctx) Session() *Session

Session returns the request's session.

func (*Ctx) Set

func (c *Ctx) Set(key string, value any)

Set stores a value for the template.

func (*Ctx) SetModel

func (c *Ctx) SetModel(v any)

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) Status

func (c *Ctx) Status(code int)

Status sets the HTTP status the page is rendered with.

func (*Ctx) Success

func (c *Ctx) Success(message string)

Success sets the page's Message.

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 LoadFunc

type LoadFunc func(c *Ctx) error

LoadFunc fills in page data for a route. Returning an error renders the 500 page.

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.

func (*MemoryStore) Delete

func (m *MemoryStore) Delete(_ context.Context, id string) error

func (*MemoryStore) Load

func (m *MemoryStore) Load(_ context.Context, id string) (map[string]string, error)

func (*MemoryStore) Save

func (m *MemoryStore) Save(_ context.Context, id string, values map[string]string, expires time.Time) error

type Middleware

type Middleware func(http.Handler) http.Handler

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

func GetSession(r *http.Request) *Session

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.

func (*Session) Delete

func (s *Session) Delete(key string)

Delete removes a value.

func (*Session) Get

func (s *Session) Get(key string) string

Get returns the value for key, or "" if it isn't set.

func (*Session) Pop

func (s *Session) Pop(key string) string

Pop returns the value for key and removes it.

func (*Session) Renew

func (s *Session) Renew()

Renew keeps the values but moves them to a new session ID. Call it right after login so an ID planted before login (session fixation) stops working.

func (*Session) Set

func (s *Session) Set(key, value string)

Set stores a value.

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.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL