Documentation
¶
Overview ¶
Package tug is a web framework for Go apps whose frontend is Inertia.js.
This is the HTTP core the rest builds on. Routing is net/http's ServeMux, handlers return errors, and middleware has net/http's own shape, func(http.Handler) http.Handler, so anything written for net/http plugs in:
app := tug.New()
app.Use(middleware.RequestID(), middleware.Logger(), middleware.Recover())
app.Get("/posts/{id}", showPost).Name("posts.show")
if err := app.Run(); err != nil {
log.Fatal(err)
}
A handler gets a *Ctx for its request and returns an error. The app's ErrorHandler turns that error into the response, so a handler says what went wrong, as NewHTTPError(http.StatusNotFound), and never writes an error page itself.
The rest is in packages of their own, which don't import this one: inertia renders pages, session keeps sessions in a cookie, validate checks structs, vite puts a Vite build into pages, auth has the parts of accounts where a slip is a security hole, mail sends mail, and middleware has RequestID, Logger, Recover and CSRF. The guide is in the repository's docs directory: https://github.com/cuonggt/tug/tree/main/docs.
Index ¶
- func DefaultErrorHandler(c *Ctx, err error)
- func Generating() bool
- func RouteOf(r *http.Request) string
- type App
- func (a *App) AbsoluteURL(name string, params ...any) (string, error)
- func (a *App) Command(name, summary string, run func(ctx context.Context, args []string) error)
- func (a *App) Go(fn func(ctx context.Context) error)
- func (a *App) Locale(r *http.Request) string
- func (a *App) Run() error
- func (a *App) Serve(ctx context.Context, ln net.Listener) error
- func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (a *App) SignedURL(name string, expires time.Time, params ...any) (string, error)
- func (a *App) URL(name string, params ...any) (string, error)
- type BindError
- type Certificates
- type Config
- type Ctx
- func (c *Ctx) AbsoluteURL(name string, params ...any) (string, error)
- func (c *Ctx) Bind(dst any) error
- func (c *Ctx) BindValid(dst any, checks ...func(validate.Errors)) error
- func (c *Ctx) Blob(code int, contentType string, b []byte) error
- func (c *Ctx) Choice(text string, n int, args ...any) string
- func (c *Ctx) ClearHistory()
- func (c *Ctx) Context() context.Context
- func (c *Ctx) Download(name string, content io.Reader) error
- func (c *Ctx) Events(fn func(ctx context.Context, send func(Event) error) error) error
- func (c *Ctx) File(path string) error
- func (c *Ctx) FileFS(fsys fs.FS, name string) error
- func (c *Ctx) Flash(key string, value any)
- func (c *Ctx) HTML(code int, html string) error
- func (c *Ctx) Head(head ...inertia.HeadElement)
- func (c *Ctx) IP() string
- func (c *Ctx) Inertia(component string, props any) error
- func (c *Ctx) JSON(code int, v any) error
- func (c *Ctx) Locale() string
- func (c *Ctx) Location(url string) error
- func (c *Ctx) NoContent(code int) error
- func (c *Ctx) Param(name string) string
- func (c *Ctx) PreserveFragment()
- func (c *Ctx) Query(name string) string
- func (c *Ctx) Redirect(to string) error
- func (c *Ctx) RedirectBack() error
- func (c *Ctx) RedirectRoute(name string, params ...any) error
- func (c *Ctx) Request() *http.Request
- func (c *Ctx) Response() http.ResponseWriter
- func (c *Ctx) Session() *session.Session
- func (c *Ctx) SignedURL(name string, expires time.Time, params ...any) (string, error)
- func (c *Ctx) Stream(contentType string, write func(w io.Writer) error) error
- func (c *Ctx) StreamDownload(name, contentType string, write func(w io.Writer) error) error
- func (c *Ctx) String(code int, s string) error
- func (c *Ctx) T(text string, args ...any) string
- func (c *Ctx) URL(name string, params ...any) (string, error)
- func (c *Ctx) Validate(v any, checks ...func(validate.Errors)) error
- func (c *Ctx) Written() bool
- type CursorPaginated
- type ErrorPageProps
- type Event
- type FlashOf
- type HTTPError
- type HandlerFunc
- type Limiter
- type Middleware
- type PageLink
- type PageOf
- type PageOption
- type Paginated
- type PanicError
- type Props
- type Route
- type Router
- func (r *Router) Any(path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Delete(path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Get(path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Group(prefix string, mw ...Middleware) *Router
- func (r *Router) Handle(method, path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Options(path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Patch(path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Post(path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Put(path string, h HandlerFunc, mw ...Middleware) *Route
- func (r *Router) Use(mw ...Middleware)
- type SimplePaginated
- type TLS
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultErrorHandler ¶
DefaultErrorHandler answers an error from a handler. validate.Errors go back to the form they came from, as Inertia expects, or to an API client as a 422 (see BindValid). An *HTTPError chooses the status and message. So does an error that says its status with a StatusCode method, as package auth's no does, with its own Error as the message under 500, and the status's text from 500 up, as its Error may not be the client's. Anything else is a 500 that keeps its details in the log. Server errors are logged through slog.Default(), with the stack for a panic.
The body is JSON, {"message": "..."}, when the request's Accept header asks for JSON first, and plain text otherwise. With Config.Debug on, a server error is shown with its details instead: as a page, to a client that takes HTML, as a browser and Inertia's client do, of the error and each it wraps, a panic's stack, the app's frames with the lines of source around them, the route that answered and where the app added it, and the request, its secrets [REDACTED]; with the same as JSON, to one that asks for JSON first; and as the text, the error and a panic's stack, to the rest.
func Generating ¶ added in v0.12.0
func Generating() bool
Generating reports whether tug gen started the app, to learn its pages and routes: Run writes their TypeScript then, and returns without serving. main runs up to Run either way, and what only serving needs, such as a database, it can leave alone while Generating says so, so that tug gen, and tug new and tug build, which run it, need nothing else running.
func RouteOf ¶ added in v0.29.0
RouteOf returns the route that answered r, as its String says it, "GET /posts/{id}": for the App's own middleware, around the router, to read once the handler has run, as the label of the request's metrics, which has as many values as the app has routes, where its path has as many as the app has posts. The route answers as it's matched, so its own middleware, and its group's, answer as it. A request no route answered, a 404, a 405, or a redirect to its path without its trailing slash, has "", as does one no router has seen.
Types ¶
type App ¶
type App struct {
*Router
// contains filtered or unexported fields
}
App is a web application: its routes, middleware and settings. It is an http.Handler, so an http.Server set up by hand can serve it too.
func New ¶
New returns an App. Without a Config it reads one from the environment (ConfigFromEnv); a Config passed in is used as it is. It panics on a Config.URL that isn't the app's address, as a mistyped APP_URL should stop the app as it starts rather than make links that go nowhere.
func (*App) AbsoluteURL ¶ added in v0.14.0
AbsoluteURL builds the whole link to the route named name, as URL builds its path, from Config.URL, the app's own address: AbsoluteURL("posts.show", 42) is "https://example.com/posts/42". It's for a link that leaves the app, as in mail; within the app, the path is enough. Without Config.URL it's an error.
func (*App) Command ¶ added in v0.17.0
Command adds a command to the app's binary, for what it does besides serving: a fix to its data, an import, a user made an admin. Run, given the command's name as its first argument, runs it in place of serving, with the arguments after the name and a context canceled on SIGINT or SIGTERM, and returns its error: ./blog users:admin ann@example.com runs users:admin with ann@example.com. ./blog help lists the app's commands, with their summaries, and a name that isn't one is an error that says so.
app.Command("users:admin", "make a user an admin: users:admin <email>", func(ctx context.Context, args []string) error {
if len(args) != 1 {
return errors.New("users:admin takes the user's email")
}
return users.MakeAdmin(ctx, args[0])
})
A command runs in the app main made, with its routes, for the links it makes, its queue, for the jobs it pushes, and its database, but neither the server nor what Go runs: a job it pushes runs on the instances that serve. Its arguments are its own, to read with package flag or by hand. An app without commands serves whatever its arguments, and under tug gen, Run writes the types, whatever they are.
Command panics on a name that's taken, help, which lists them, or one that isn't a word of letters, digits and :-_, and once the app is serving.
func (*App) Go ¶ added in v0.3.0
Go runs fn beside the server, for work that isn't a request, such as a job queue's workers: Run and Serve start it as they start serving, with a context that's done as the app shuts down, and wait for it to return. An error it returns before then shuts the app down, and Serve returns it: an app whose jobs have stopped shouldn't carry on as though they hadn't. ServeHTTP, as tests call it, starts nothing, and nor does tug gen.
Go panics once the app is serving.
func (*App) Locale ¶ added in v0.15.0
Locale returns the language of r: the one Config.Locale chooses, or else the best of Config.Lang's for the browser's Accept-Language, or else the app's default, as "vi" or "pt-BR". It's English without Config.Lang. For a handler, Ctx.Locale is the same; this is for code that has the request alone, as a props function shared with every page.
func (*App) Run ¶
Run serves on Config.Addr until SIGINT or SIGTERM, then shuts down gracefully: it stops taking connections, waits up to Config.ShutdownTimeout for the requests in flight to finish, and waits for what Go runs to return. With Config.TLS, it serves HTTPS there, and HTTP on TLS.HTTPAddr, and shuts both down together.
Run is also where tug gen learns about the app: started by it, with TUG_GEN set to a file, Run writes the TypeScript for the app's pages and named routes there, and returns without serving. And for an app with commands, given one, Run runs it in place of serving: see Command.
func (*App) Serve ¶
Serve serves on ln until ctx is done, then shuts down as Run does. It closes ln. A listener of TLS's serves HTTPS, and HTTP/2 when its tls.Config offers h2 in NextProtos, which http.Server adds for ServeTLS alone: Run's own HTTPS, with Config.TLS, offers it.
func (*App) ServeHTTP ¶
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP answers a request. The first request fixes the routes and middleware: adding either after it panics.
func (*App) SignedURL ¶ added in v0.14.0
SignedURL builds the whole link to the route named name, as AbsoluteURL does, signed with the app's key, Config.Keys, until expires: a link that only the app can have made, for a route that Signed wraps, such as an invitation's, mailed to whoever is invited.
The signature is over the link's path and its expiry, which the query carries as expires and signature. A link works until it expires, as often as it's followed: what must happen once, as an invitation accepted, is the app's to record.
type BindError ¶
type BindError struct {
Field string // the key the value came under: a form or query key, JSON path or path wildcard
Reason string // what the value has to be, as "must be a whole number"
Err error // the parse error underneath
// contains filtered or unexported fields
}
BindError is a request value that doesn't fit the field it was bound to, such as "abc" for an int. Its message is for the person who sent the value: "age must be a whole number".
type Certificates ¶ added in v0.40.0
type Certificates interface {
GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
}
Certificates is a source of certificates for Run's HTTPS, as package letsencrypt's Manager, which gets them from Let's Encrypt: GetCertificate returns the one for a handshake, as tls.Config's does. It may have more, which Run looks for:
- Hosts() []string, the names it has certificates for: Run's plain HTTP sends a request for one of them to HTTPS at its own host, and any other, as a scanner's by the machine's address, to Config.URL's, whose host has to be one of them, or Run doesn't start. Without it, a request goes to HTTPS at its own host, whatever it is.
- HTTPHandler(fallback http.Handler) http.Handler, which Run's plain HTTP answers through, with the redirect as fallback, for what the source answers there, as Let's Encrypt's HTTP-01 challenges.
- TLSConfig() *tls.Config, whose NextProtos the handshake offers beside h2 and http/1.1, as acme-tls/1, for Let's Encrypt's TLS-ALPN-01 challenges.
- Run(ctx context.Context) error, which Run runs beside the server once both ports listen, as Go's work, as letsencrypt's asks for each domain's certificate as the app starts.
HTTPHandler and TLSConfig are autocert.Manager's own.
type Config ¶
type Config struct {
// Addr is where Run listens. Default ":8080", or with TLS, where it
// serves HTTPS, ":443".
Addr string
// TLS has Run serve HTTPS itself, on Addr, with certificates from files
// or from Let's Encrypt, by package letsencrypt, and plain HTTP beside
// it, which sends each request to HTTPS. Its zero value serves plain
// HTTP alone, for TLS ended in front of the app, at a proxy, a load
// balancer or the platform.
TLS TLS
// Debug shows a server error with its details: a page of the error,
// a panic's stack, with the lines of the app's source around its
// frames, the route that answered, and the request, for a browser and
// Inertia's client, and the same as JSON or text for the rest. It is
// for development: in production they belong in the log only.
Debug bool
// Editor is the editor that Debug's page opens a frame's file in, at
// its line, by a link: vscode, cursor, zed, goland or sublime, or a
// link of the app's with {file} and {line} in it, as
// "myeditor://open?file={file}&line={line}". ConfigFromEnv reads it
// from APP_EDITOR.
Editor string
// ErrorHandler answers the errors handlers return, and the 404s and
// 405s of requests no route takes. Default DefaultErrorHandler.
ErrorHandler func(c *Ctx, err error)
// BodyLimit is the largest request body Bind reads, in bytes; a larger
// one is a 413. Default 32 MiB.
BodyLimit int64
// ShutdownTimeout is how long Run waits for requests in flight after
// SIGINT or SIGTERM. Default 10 seconds.
ShutdownTimeout time.Duration
// Inertia renders the app's pages, for Ctx.Inertia and Page.Render.
// With it set, every request also goes through its Middleware, inside
// the App's own middleware.
Inertia *inertia.Inertia
// ErrorPage is the Inertia page component that errors are shown with,
// such as "Error", with the props status and message: for a 404, a
// 403, or a 500 in production. API clients still get JSON, and with
// Debug on a 500 still shows its details. Without it, errors are plain
// text, which Inertia's client shows in a dialog.
ErrorPage string
// Session keeps each visitor's session, for Ctx.Session. It's what
// carries flash data and validation errors from a request to the page
// after it. With it set, every request goes through its Middleware,
// inside the App's own middleware.
Session *session.Store
// URL is the app's own address, a scheme and a host, as
// https://example.com: what the links AbsoluteURL and SignedURL make
// begin with, for links that leave the app, as in mail. It's never the
// request's Host, which can name any site. New panics on one that isn't
// an address.
URL string
// Keys sign the links SignedURL makes, and check them: the first signs,
// and each of them checks, so the links made with a key being rotated
// out still work until it's dropped. session.KeysFromEnv reads them from
// APP_KEY and APP_PREVIOUS_KEYS.
Keys [][]byte
// Lang is the app's languages, which lang.Load reads: what tug says to
// a person, a form's errors, a 429 or an error page's status, is said
// in the request's language, as Ctx.Locale picks it, and so is what the
// app says with Ctx.T. Without it, everything is in English.
Lang *lang.Catalog
// Locale is the language the app has chosen for a request, as a user's
// choice, kept in their session or their account: one of Lang's, as
// lang.Catalog.Find finds it, or "" to leave it to the browser's
// Accept-Language. For a handler, the ErrorHandler and the pages it
// renders, it runs inside the session's middleware, so it can read the
// session; in the App's own middleware, there's none yet.
Locale func(r *http.Request) string
// Zone is the app's own time zone, an IANA name as "Asia/Ho_Chi_Minh",
// which its pages write their times in: lang.Texts carries it to them
// beside the language, for the date() tug gen writes. Empty is UTC. New
// stops for a name that isn't a zone, which it reads from the host's own
// files, as an image needs them too.
Zone string
// DevTools records each request for Inertia's DevTools, a panel of the
// browser's, as an extension, which shows each visit's props, route,
// headers and bodies, and where the page was rendered: an entry each,
// kept in .tug/devtools, which it reads from two endpoints of the
// app's, /_inertia/devtools/entries and the entries below it. It's for
// development: tug dev turns it on, by TUG_DEV, which ConfigFromEnv
// reads. Values under keys such as password and token, and headers
// such as Cookie, are kept as [REDACTED].
DevTools bool
}
Config is how an App behaves. Every field has a default, so the zero value works.
func ConfigFromEnv ¶
func ConfigFromEnv() Config
ConfigFromEnv reads the settings a deployment sets: ADDR, or PORT as platforms such as Cloud Run and Fly.io set it, APP_DEBUG, and APP_URL; TLS_CERT, TLS_KEY and HTTP_ADDR, for a certificate of the app's own; APP_EDITOR, for Debug's page; and DevTools, from TUG_DEV, which tug dev sets.
type Ctx ¶
type Ctx struct {
// contains filtered or unexported fields
}
Ctx is a request and its response, as a handler sees them. It belongs to its request: don't keep it after the handler returns.
func (*Ctx) AbsoluteURL ¶ added in v0.14.0
AbsoluteURL builds the whole link to a named route, from the app's address, as App.AbsoluteURL does.
func (*Ctx) Bind ¶
Bind fills the struct dst points to from the request. Each field's tags say where its value comes from:
type UpdatePost struct {
ID int64 `path:"id"` // the {id} in "/posts/{id}"
Draft bool `query:"draft"` // ?draft=1
Title string `json:"title"` // the body
}
The body is read by its Content-Type. JSON goes through encoding/json and its json tags. A form, urlencoded or multipart, fills a field by its form tag, or else its json tag's name, or else its name; a key that repeats, or ends in "[]", fills a slice. Files go to fields of type *multipart.FileHeader or []*multipart.FileHeader. An empty form value leaves its field alone, as an empty input means no value rather than zero.
Inertia's <Form> sends a form as JSON, with every value a string: "on" from a checkbox, "42" from a number input, "2026-09-25" from a date input, and "" from one left empty. So a JSON string where a bool, a number or a time.Time goes is read as a form's value is, and "" leaves its field alone; a JSON true or 42 binds as it is.
Query values are bound after the body, and path values last, so a body can't overwrite the ID in the URL.
A value that doesn't parse is a 400, or a 404 when it's a path value, since /posts/abc is a page that isn't there. The error wraps a *BindError naming the field. The fields after it are still bound, so that checking them, as BindValid does, finds what's really wrong with them rather than what's missing. A body over Config.BodyLimit is a 413, and a body Bind can't read is a 415. The messages are in the request's language (see Ctx.Locale), and name a field by its label tag, or else by its key made into words.
func (*Ctx) BindValid ¶
BindValid binds the request into dst, as Bind does, then checks dst against its validate tags (see package validate) and against checks, which can add errors of their own, such as a title that's taken:
var in PostInput
err := c.BindValid(&in, func(errs validate.Errors) {
if posts.TitleTaken(in.Title) {
errs.Add("title", "title is taken")
}
})
if err != nil {
return err
}
When something's wrong it returns validate.Errors, which the ErrorHandler answers: a form goes back where it came from, with the errors, and an API client gets a 422. A value Bind can't parse is one of the errors: "age must be a whole number". The messages are in the request's language (see Ctx.Locale); the checks' are theirs to say, with Ctx.T.
A Precognition request, sent by a form checking its fields as they're filled in, is answered here with a 204 or a 422, and BindValid returns an error, so that the handler stops before it changes anything: return the error as it is.
func (*Ctx) Choice ¶ added in v0.15.0
Choice says text for a count in the request's language, as lang.Words.Choice does: c.Choice(":count post deleted|:count posts deleted", n).
func (*Ctx) ClearHistory ¶
func (c *Ctx) ClearHistory()
ClearHistory has the next page shown tell the client to clear the history it has encrypted, as signing out should. See inertia.Config.EncryptHistory.
func (*Ctx) Context ¶
Context returns the request's context, which is canceled when the client goes away.
func (*Ctx) Download ¶ added in v0.20.0
Download sends content as a file to save, as name: an invoice made as a PDF, bytes.NewReader(pdf), or a file a disk's Open reads, under the name it was uploaded with. The browser saves it rather than show it, in Content-Disposition's words, which say the name however it's written: in Vietnamese, with quotes, or with a line break that can't end the header. What's before the name's last slash or backslash is dropped, as the browser saves a name, not a path.
Its type is its name's extension's, or else what its first bytes say. Content that seeks, as a file or a bytes.Reader does, goes through http.ServeContent, with ranges, and, when it has a Stat, as a file does, its time for If-Modified-Since. Other content goes as it's read, as Stream's body does. Download reads content to its end, and leaves it open: closing it is the caller's.
func (*Ctx) Events ¶ added in v0.20.0
Events sends a stream of server-sent events, which the browser's EventSource reads, as fn sends them: an import's progress, say, or a change a page should show, which it reloads its props for with Inertia's router.reload. The stream lasts as long as fn does: fn's context is done when the client goes away, and when the app shuts down, as Serve's Shutdown waits for the requests in flight, which a stream would hold on to. fn returns then; send fails once it's done.
app.Get("/imports/{id}/progress", func(c *tug.Ctx) error {
return c.Events(func(ctx context.Context, send func(tug.Event) error) error {
for p := range imports.progress(ctx, c.Param("id")) {
if err := send(tug.Event{Name: "progress", Data: p}); err != nil {
return err
}
}
return nil
})
})
An EventSource connects again when a stream ends, the app's shutdown's among them, after a few seconds, with the last event's ID in Last-Event-ID, for the app to go on from: a page that has what it waited for closes it. A quiet stream sends a comment every 20 seconds, which keeps the proxies in front of the app from closing it.
fn's error is Events', for the ErrorHandler, which logs it, as the response has started, unless the client went away or the app is shutting down, which is no failure. An event whose Name or ID has a line break, or whose Data JSON can't hold, is send's error, and isn't sent.
func (*Ctx) File ¶ added in v0.20.0
File sends the file at path, one of the app's own, such as a report it made, shown as its type says, HTML as a page: http.ServeContent sends it, with ranges, so a download that broke off goes on where it stopped, and If-Modified-Since. A file that isn't there, or a directory, is a 404 for the ErrorHandler.
path is the app's, never a request's, which could name any file: a file a request names is FileFS's, from a directory, as os.DirFS or an os.Root has it, which keeps it inside. A file someone uploaded goes by its disk's route, which sends it as what its bytes are, or by Download, to be saved.
func (*Ctx) FileFS ¶ added in v0.20.0
FileFS sends the file name in fsys, as File sends one: a file embedded in the app, or one of a directory a request names a file of, as FileFS(os.DirFS("docs"), c.Param("name")), where a name that would leave the directory, as "../app.db", isn't there.
func (*Ctx) Flash ¶
Flash adds flash data for the next page shown, whether this request renders it or redirects to it: a message such as "Post created". The client reads it as usePage().flash, shows it once, and doesn't keep it in history. Surviving a redirect takes Config.Session. A key the app declares with tug.Flash, for tug gen to type, panics for a value of another type than its own.
func (*Ctx) HTML ¶
HTML writes html as a page, exactly as given: anything from a user in it must already be escaped.
func (*Ctx) Head ¶ added in v0.37.0
func (c *Ctx) Head(head ...inertia.HeadElement)
Head gives the page this request renders the elements of its <head>: its title, its description, and the tags a link's preview is made from.
c.Head(inertia.Title(post.Title), inertia.Meta("description", post.Summary))
They go out as the page's head prop, which Inertia's client puts in the document's head with its serverHead option on, and in a first visit's HTML, through the root template's {{ .InertiaHead }}, for a search engine's crawler, or the app that makes a link's preview, which run no script. An element replaces the one before it of the same key, the handler's those of middleware, which gives them with inertia.WithHead.
func (*Ctx) IP ¶ added in v0.14.0
IP returns the address the request came from, without its port, as "203.0.113.9": a key for Limit that counts by address. Behind a proxy it's the proxy's, unless middleware.TrustProxies names the proxy, and reads the client's past it.
func (*Ctx) Inertia ¶
Inertia renders an Inertia page component with props: an HTML page for a first visit, and the page object as JSON for a visit from Inertia's client. props is a struct or a map; see inertia.Inertia.Render. The app needs Config.Inertia.
func (*Ctx) Locale ¶ added in v0.15.0
Locale returns the request's language, as App.Locale picks it: the one the app chose for it with Config.Locale, or else the browser's, of the app's languages, or else the app's default.
func (*Ctx) Location ¶
Location sends the client to url with a full page load, which is how an Inertia app leaves for another site: see inertia.Location.
func (*Ctx) Param ¶
Param returns the value of the path wildcard name: Param("id") is "42" for /posts/42 on a "/posts/{id}" route.
func (*Ctx) PreserveFragment ¶
func (c *Ctx) PreserveFragment()
PreserveFragment has the next page shown tell the client to keep the #fragment of the visit that led to it, across the redirect: a comment sent from /posts/1#comments comes back to the comments.
func (*Ctx) Redirect ¶
Redirect sends the client to another URL: with 302 Found after a GET or HEAD, and 303 See Other after any other method. A 303 makes browsers, and Inertia, follow a PUT, PATCH or DELETE with a GET, where a 302 may repeat the method.
func (*Ctx) RedirectBack ¶ added in v0.2.0
RedirectBack redirects to the page the request came from, by its Referer, when that's a page of this app, and to "/" otherwise: a Referer can name any site. It's for a form that more than one page has, which goes back to whichever it was sent from.
func (*Ctx) RedirectRoute ¶
RedirectRoute redirects to the named route, filling its wildcards as App.URL does.
func (*Ctx) Response ¶
func (c *Ctx) Response() http.ResponseWriter
Response returns the response writer.
func (*Ctx) Session ¶
Session returns the request's session, or nil when the app has no Config.Session.
func (*Ctx) SignedURL ¶ added in v0.14.0
SignedURL builds a signed link to a named route, as App.SignedURL does.
func (*Ctx) Stream ¶ added in v0.20.0
Stream sends a body as write makes it, of the type contentType, such as "text/csv", for a body too long to hold, made as it's read: every post as CSV, a row at a time, from rows as a query returns them. Each write goes to the client as it's made, flushed, so a client that reads as it comes, as a fetch of lines does, gets each one; a writer of many small writes wraps w in a bufio.Writer, as csv.Writer does itself.
write's error before its first write is Stream's, for the ErrorHandler, as any handler's is. Once the body has started, there's no error page to show: the error goes to the log, and the connection is cut, with http.ErrAbortHandler, so the client sees the body end before its time, where a browser would save half a file as the whole.
func (*Ctx) StreamDownload ¶ added in v0.20.0
StreamDownload sends a body as write makes it, as Stream does, as a file to save, as name, which Download writes as it writes its own:
return c.StreamDownload("posts.csv", "text/csv; charset=utf-8", func(w io.Writer) error {
out := csv.NewWriter(w)
...
out.Flush()
return out.Error()
})
func (*Ctx) T ¶ added in v0.15.0
T says text in the request's language, as lang.Words.T does, from Config.Lang: c.T("Welcome back, :name", "name", user.Name), for a flash message. A text the language has no words for says itself, in English.
type CursorPaginated ¶ added in v0.16.0
type CursorPaginated[T any] struct { Data []T `json:"data"` Path string `json:"path"` PerPage int `json:"per_page"` NextCursor *string `json:"next_cursor"` NextPageURL *string `json:"next_page_url"` // contains filtered or unexported fields }
CursorPaginated is a page of a list by cursor, as CursorPaginate makes it, under the keys Laravel's cursor paginator writes: the cursor of the page after it, and its link, null on the last.
func CursorPaginate ¶ added in v0.16.0
func CursorPaginate[T, C any](c *Ctx, perPage int, fetch func(after *C, limit int) ([]T, error), cursor func(item T) C, opts ...PageOption) (CursorPaginated[T], error)
CursorPaginate is one page of a list by cursor, for a list whose items come and go as it's read, as a feed's: fetch gets the items after the one at the cursor, nil for the first page, limit of them, one more than a page, to know whether there's a next; cursor is where an item sits in the query's order, the app's own value, such as its time and its ID:
posts, err := tug.CursorPaginate(c, 20,
func(after *PostCursor, limit int) ([]Post, error) { return store.PostsAfter(after, limit) },
func(p Post) PostCursor { return PostCursor{At: p.CreatedAt, ID: p.ID} })
The link to the next page carries the last item's cursor, as JSON, in base64url, under ?cursor. It isn't signed, as Laravel's aren't: it's only where to start, and one made up starts somewhere else within what the query lets the page see. One that doesn't decode is the first page. Cursors go forward: a page before one runs the query the other way, which is the app's to write.
func (CursorPaginated[T]) Paging ¶ added in v0.16.0
func (p CursorPaginated[T]) Paging() inertia.Paging
Paging is where the page sits, for inertia.Scroll: the cursor it was asked for with, and the next page's.
type ErrorPageProps ¶
ErrorPageProps are the props of Config.ErrorPage.
type Event ¶ added in v0.20.0
type Event struct {
// Name is the event's type, which a page listens for with
// addEventListener, as "progress". Without one, it's a message, which
// onmessage gets.
Name string
// ID is the event's, which the browser sends back when it connects
// again, in Last-Event-ID, for the app to go on from after it. Optional.
ID string
// Data is what the event carries, as JSON, which the page reads with
// JSON.parse: a value encoding/json can encode, or nil for none.
Data any
}
Event is one of the server-sent events Ctx.Events sends.
type FlashOf ¶ added in v0.33.0
type FlashOf[T any] struct { // contains filtered or unexported fields }
FlashOf is a key of the flash data, and the type of the value it carries. Flash declares one.
func Flash ¶ added in v0.33.0
Flash declares a key of the flash data a handler leaves for the next page shown, and the type of its value:
var Success = tug.Flash[string]("success")
func store(c *tug.Ctx) error {
...
Success.Set(c, "Post created")
return c.RedirectRoute("posts.show", post.ID)
}
Declared this way, a key carries only values of its type, and tug gen writes the TypeScript of the keys declared, FlashData, as the type of a page's flash, usePage().flash, which a key no handler declares isn't in. Ctx.Flash of a declared key with a value of another type panics, as the frontend's type would say otherwise, and a key declared twice with different types panics.
type HTTPError ¶
HTTPError is an error with a status. Return one from a handler to choose the response: NewHTTPError(http.StatusNotFound) is a 404.
Message is shown to the client, so it says what went wrong in their terms, and in their language, with Ctx.T; without one, the status's text is shown, in the request's language. Err is the cause, for the log; only Config.Debug shows it, in a server error's response.
func NewHTTPError ¶
NewHTTPError returns an HTTPError with the given status. Without a message it says the status's text, such as "Not Found".
type HandlerFunc ¶
HandlerFunc handles a request. A returned error is answered by the app's ErrorHandler: return an *HTTPError to choose the status.
func Limit ¶ added in v0.13.0
func Limit(l Limiter, key func(c *Ctx) string, h HandlerFunc) HandlerFunc
Limit wraps h so that each request counts a try with l, by the key that key makes of it: the address it came from, Ctx.IP, which behind a proxy takes middleware.TrustProxies, or the user's ID. A request over the limit doesn't reach h: it's a 429 for the app's ErrorHandler, with Retry-After in seconds, so an Inertia visit gets the error page, and an API's client JSON. An error from l is as though h returned it.
searches := &auth.Throttle{Name: "searches", Max: 30, Window: time.Minute}
app.Get("/search", tug.Limit(searches, (*tug.Ctx).IP, search))
func Signed ¶ added in v0.14.0
func Signed(h HandlerFunc) HandlerFunc
Signed wraps h so that only a link SignedURL made reaches it, before it expires. Any other request is a 403 for the app's ErrorHandler, which says whether the link has expired or isn't one the app made: one that was changed, or has anything in its query besides expires and signature, which Bind would give the handler.
app.Get("/invitations/{id}", tug.Signed(acceptInvitation)).Name("invitations.accept")
func WrapHandler ¶
func WrapHandler(h http.Handler) HandlerFunc
WrapHandler turns an http.Handler into a HandlerFunc, to put something written for net/http, such as http.FileServer, on a route.
type Limiter ¶ added in v0.13.0
A Limiter counts a try for a key, and says how long a key that has had its tries waits, or 0: Limit's one call, which *auth.Throttle has.
type Middleware ¶
Middleware wraps a handler. It is net/http's own shape, so middleware written for net/http works as it is.
type PageLink ¶ added in v0.16.0
type PageLink struct {
URL *string `json:"url"`
Label string `json:"label"`
Active bool `json:"active"` // it's the page shown
}
PageLink is a pager's link to a page, by its number, or its "...", which has no URL, for the pages it leaves out.
type PageOf ¶
type PageOf[P any] struct { // contains filtered or unexported fields }
PageOf is an Inertia page component, and the props it takes. Page declares one.
func Page ¶
Page declares an Inertia page component, and the props it takes:
type PostsIndexProps struct {
Posts []Post `json:"posts"`
Stats inertia.DeferProp[Stats] `json:"stats"`
}
var PostsIndex = tug.Page[PostsIndexProps]("Posts/Index")
func index(c *tug.Ctx) error {
return PostsIndex.Render(c, PostsIndexProps{Posts: posts, Stats: inertia.Defer(countWords)})
}
Declared this way, a page can only be rendered with the props it takes, and tug gen writes their TypeScript, for the page component to take them too. A component declared twice with different props panics.
type PageOption ¶ added in v0.16.0
type PageOption func(*pageOptions)
A PageOption changes how Paginate, SimplePaginate and CursorPaginate read the page asked for, and link the others.
func PageName ¶ added in v0.16.0
func PageName(name string) PageOption
PageName names the query parameter that picks the page, "page" by default, and "cursor" by cursor: another is for a second list on one page, as "comments_page", whose links leave the first list's page as it is.
type Paginated ¶ added in v0.16.0
type Paginated[T any] struct { CurrentPage int `json:"current_page"` Data []T `json:"data"` FirstPageURL string `json:"first_page_url"` From *int `json:"from"` // the first item's place in the list, from 1, or null for a page with none LastPage int `json:"last_page"` LastPageURL string `json:"last_page_url"` Links []PageLink `json:"links"` // the pages around this one, for a pager NextPageURL *string `json:"next_page_url"` Path string `json:"path"` PerPage int `json:"per_page"` PrevPageURL *string `json:"prev_page_url"` To *int `json:"to"` // the last item's place, or null Total int `json:"total"` // contains filtered or unexported fields }
Paginated is a page of a list in numbered pages, as Paginate makes it: its items, and where it sits among the pages, under the keys Laravel's paginators write it with as JSON, so that a pager written for theirs reads it. The links are paths, with the query the list was shown with.
func Paginate ¶ added in v0.16.0
func Paginate[T any](c *Ctx, perPage int, count func() (int, error), fetch func(limit, offset int) ([]T, error), opts ...PageOption) (Paginated[T], error)
Paginate is one page of a list in numbered pages of perPage items: the one ?page asks for. count says how many items the list has, and fetch gets a page of them, by the limit and offset it's given, as SQL's LIMIT and OFFSET take them: the query is the app's, so tug has no SQL.
posts, err := tug.Paginate(c, 20, store.CountPosts, store.Posts)
A page that isn't a number, or is under 1, is the first, as Laravel has it, and a page past the last is empty, not a 404, as a list can shrink between two visits: count runs first, so fetch isn't asked for a page that has no items. The page size is the app's alone: a client that could pick it would pick how much the database reads.
func (Paginated[T]) Paging ¶ added in v0.16.0
Paging is where the page sits, for inertia.Scroll, so that one query feeds numbered pages and <InfiniteScroll> alike:
inertia.Scroll(func() ([]Post, inertia.Paging, error) {
posts, err := tug.Paginate(c, 20, store.CountPosts, store.Posts)
return posts.Data, posts.Paging(), err
})
type PanicError ¶
PanicError is a handler's panic, recovered by the app and passed to its ErrorHandler like any other error, with the stack for the log.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
func (*PanicError) Unwrap ¶
func (e *PanicError) Unwrap() error
Unwrap returns the panic's value when that was an error.
type Route ¶
type Route struct {
// contains filtered or unexported fields
}
Route is a route added to a Router. Name it to build its URL.
func (*Route) Name ¶
Name names the route, for App.URL and Ctx.RedirectRoute. A name belongs to one route: naming a second route "posts.show" panics.
func (*Route) String ¶
String describes the route by its method and path as it was added, "GET /posts/{id}", or "ANY /files/{path...}" for a route of any method.
func (*Route) Takes ¶ added in v0.32.0
Takes declares the struct the route's handler binds, by a value of it, LoginInput{}, which a wrapper around the handler, as one that checks someone has logged in, keeps tug from seeing. tug gen writes it as what the route is sent, Inputs['login.store'] in routes.ts, which a form typed by it is checked against, as React's <Form<Inputs['login.store']>> is: its fields' keys, and its errors'.
The route needs its name first, as Inputs is by name: Takes panics on a route with none, and on a value that isn't a struct or a pointer to one. Bind fails, as a 500 whose error names both, a request of the route whose handler binds a struct of body fields other than input: Takes has drifted from the handler, and would type its forms against a struct the handler no longer reads.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router adds routes. The App is the root Router, and Group makes one for a path prefix, with middleware of its own.
A path matches exactly: "/posts" is /posts and nothing under it, and "/" is only the home page. End a path with a {name...} wildcard to take everything under it. Beyond that, paths are net/http ServeMux patterns, with its wildcards and its rule that the more specific pattern wins.
func (*Router) Any ¶
func (r *Router) Any(path string, h HandlerFunc, mw ...Middleware) *Route
Any adds a route for requests to path under any method.
func (*Router) Delete ¶
func (r *Router) Delete(path string, h HandlerFunc, mw ...Middleware) *Route
Delete adds a route for DELETE requests to path.
func (*Router) Get ¶
func (r *Router) Get(path string, h HandlerFunc, mw ...Middleware) *Route
Get adds a route for GET requests to path, which also takes HEAD.
func (*Router) Group ¶
func (r *Router) Group(prefix string, mw ...Middleware) *Router
Group returns a Router for routes under prefix, such as "/admin", that run mw after the middleware of r. An empty prefix groups routes for the middleware alone.
func (*Router) Handle ¶
func (r *Router) Handle(method, path string, h HandlerFunc, mw ...Middleware) *Route
Handle adds a route for method requests to path; an empty method takes them all. mw wraps this route alone, inside its groups' middleware.
Handle panics when path is not a valid pattern, or takes the same requests as a route already added, as ServeMux does: both are mistakes to fix before the app can start.
func (*Router) Options ¶
func (r *Router) Options(path string, h HandlerFunc, mw ...Middleware) *Route
Options adds a route for OPTIONS requests to path.
func (*Router) Patch ¶
func (r *Router) Patch(path string, h HandlerFunc, mw ...Middleware) *Route
Patch adds a route for PATCH requests to path.
func (*Router) Post ¶
func (r *Router) Post(path string, h HandlerFunc, mw ...Middleware) *Route
Post adds a route for POST requests to path.
func (*Router) Put ¶
func (r *Router) Put(path string, h HandlerFunc, mw ...Middleware) *Route
Put adds a route for PUT requests to path.
func (*Router) Use ¶
func (r *Router) Use(mw ...Middleware)
Use adds middleware, which runs in the order added. On the App it wraps every request, including those no route takes, which suits logging and recovery. On a group it wraps the group's routes, inside the middleware of the groups around it, including routes added before the Use.
type SimplePaginated ¶ added in v0.16.0
type SimplePaginated[T any] struct { CurrentPage int `json:"current_page"` CurrentPageURL string `json:"current_page_url"` Data []T `json:"data"` FirstPageURL string `json:"first_page_url"` From *int `json:"from"` NextPageURL *string `json:"next_page_url"` Path string `json:"path"` PerPage int `json:"per_page"` PrevPageURL *string `json:"prev_page_url"` To *int `json:"to"` // contains filtered or unexported fields }
SimplePaginated is a page of a list in numbered pages without a count, as SimplePaginate makes it, under the keys Laravel's simple paginator writes: it knows whether there's a next page, and not how many there are.
func SimplePaginate ¶ added in v0.16.0
func SimplePaginate[T any](c *Ctx, perPage int, fetch func(limit, offset int) ([]T, error), opts ...PageOption) (SimplePaginated[T], error)
SimplePaginate is one page of a list in numbered pages, as Paginate is, for a list too long to count at each visit: fetch is asked for one item more than a page, to know whether there's a page after it.
func (SimplePaginated[T]) Paging ¶ added in v0.16.0
func (p SimplePaginated[T]) Paging() inertia.Paging
Paging is where the page sits, for inertia.Scroll.
type TLS ¶ added in v0.40.0
type TLS struct {
// CertFile and KeyFile are a certificate, with its chain after it, and
// its key, in PEM files, as Cloudflare's origin certificates or a
// company's own CA's are. Run reads them as it starts, and again at a
// handshake, once a minute at most, when either's time has changed, as
// certbot's renewal replaces them; a pair that doesn't load leaves the
// last in use, and the log says why. Both or neither, and not with
// Certificates. ConfigFromEnv reads them from TLS_CERT and TLS_KEY.
CertFile string
KeyFile string
// Certificates is a source of certificates, in place of the files, as
// package letsencrypt's Manager. A nil one is none, as the nil Manager
// letsencrypt.FromEnv returns without TLS_DOMAINS, so what it returns
// can go straight in.
Certificates Certificates
// HTTPAddr is where Run serves plain HTTP beside HTTPS: each request is
// sent to its path over HTTPS, by a 308, but for what the source of
// certificates answers there, as Let's Encrypt's challenges. Default
// ":80". ConfigFromEnv reads it from HTTP_ADDR.
HTTPAddr string
}
TLS has Run serve HTTPS itself, as on a machine of the app's own with no proxy in front to end TLS: HTTPS, and HTTP/2, on Config.Addr, ":443" unless it says another, and plain HTTP on HTTPAddr, which sends every request to HTTPS. The certificates are the files', CertFile and KeyFile, or a source's, Certificates, as package letsencrypt's, which gets them from Let's Encrypt. The zero value is off: Run serves plain HTTP on Config.Addr, for TLS ended in front of the app.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package auth has the parts of logging users in where a slip is a security hole, for an app that keeps its users where it likes:
|
Package auth has the parts of logging users in where a slip is a security hole, for an app that keeps its users where it likes: |
|
passkeytest
Package passkeytest is a passkey authenticator in software, for tests of an app's passkeys.
|
Package passkeytest is a passkey authenticator in software, for tests of an app's passkeys. |
|
throttletest
Package throttletest is for testing with auth.Throttle: TestStore checks that an auth.ThrottleStore keeps the promises a Throttle depends on.
|
Package throttletest is for testing with auth.Throttle: TestStore checks that an auth.ThrottleStore keeps the promises a Throttle depends on. |
|
Package broadcast carries events between an app's instances, to the pages open on any of them: a post one instance saves, the pages on the others hear of, through a route of tug's Ctx.Events.
|
Package broadcast carries events between an app's instances, to the pages open on any of them: a post one instance saves, the pages on the others hear of, through a route of tug's Ctx.Events. |
|
broadcasttest
Package broadcasttest is for testing with package broadcast: Memory is a Store in memory, and TestStore checks that a Store keeps the promises a Hub depends on.
|
Package broadcasttest is for testing with package broadcast: Memory is a Store in memory, and TestStore checks that a Store keeps the promises a Hub depends on. |
|
Package cache keeps what's slow to work out, such as a dashboard's counts or what another service answered, for a while, in a store every instance of the app shares, such as a table in its database, and has locks for what mustn't run twice at once, on any of them.
|
Package cache keeps what's slow to work out, such as a dashboard's counts or what another service answered, for a while, in a store every instance of the app shares, such as a table in its database, and has locks for what mustn't run twice at once, on any of them. |
|
cachetest
Package cachetest is for testing with package cache: TestStore checks that a cache.Store keeps the promises a cache.Cache depends on.
|
Package cachetest is for testing with package cache: TestStore checks that a cache.Store keeps the promises a cache.Cache depends on. |
|
cmd
|
|
|
tug
command
Command tug makes and runs tug apps:
|
Command tug makes and runs tug apps: |
|
Package crypt encrypts the app's own values for keeping, such as a token for another service, which a user connected, kept in a column: a copy of the database, as a leaked backup, then has the token sealed, and nothing to open it with.
|
Package crypt encrypts the app's own values for keeping, such as a token for another service, which a user connected, kept in a column: a copy of the database, as a leaked backup, then has the token sealed, and nothing to open it with. |
|
examples
|
|
|
api
command
Command api is a small JSON API on tug: posts kept in memory, behind named routes in a group, with the usual middleware, errors that come back as JSON to a client that asks for it, and a limit on how fast a client writes, by its address, behind the proxies TRUSTED_PROXIES names.
|
Command api is a small JSON API on tug: posts kept in memory, behind named routes in a group, with the usual middleware, errors that come back as JSON to a client that asks for it, and a limit on how fast a client writes, by its address, behind the proxies TRUSTED_PROXIES names. |
|
inertia
command
Command inertia is tug's example of an Inertia app: posts, shown by React pages that get their props from Go handlers, and written with forms that check themselves as they're filled in.
|
Command inertia is tug's example of an Inertia app: posts, shown by React pages that get their props from Go handlers, and written with forms that check themselves as they're filled in. |
|
Package inertia is the server side of the Inertia.js v3 protocol, for net/http.
|
Package inertia is the server side of the Inertia.js v3 protocol, for net/http. |
|
internal
|
|
|
devtools
Package devtools is the server's side of Inertia's DevTools, a panel of the browser's own, as an extension, that records each visit of an Inertia app: what the client did, paired with an entry of what the server says of each response, by the protocol at https://inertiajs.com/docs/v3/advanced/devtools-protocol, whose reference is inertia-laravel's.
|
Package devtools is the server's side of Inertia's DevTools, a panel of the browser's own, as an extension, that records each visit of an Inertia app: what the client did, paired with an entry of what the server says of each response, by the protocol at https://inertiajs.com/docs/v3/advanced/devtools-protocol, whose reference is inertia-laravel's. |
|
filetype
Package filetype tells what a file is by its first bytes, as http.DetectContentType does, not by its name or by what a browser says it is, which are the user's to say.
|
Package filetype tells what a file is by its first bytes, as http.DetectContentType does, not by its name or by what a browser says it is, which are the user's to say. |
|
frames
Package frames tells whose a frame of a goroutine's stack is: the app's own, tug's, the standard library's, or a dependency's.
|
Package frames tells whose a frame of a goroutine's stack is: the app's own, tug's, the standard library's, or a dependency's. |
|
label
Package label names a field as a person reads it in a message: by its label tag, or else by the key a client sends it under, made into words.
|
Package label names a field as a person reads it in a message: by its label tag, or else by the key a client sends it under, made into words. |
|
mailbox
Package mailbox keeps the mail an app sends under tug dev, rather than send it: package mail's Mailbox keeps each one in a directory, Dir, and the App shows them at Path, as a mail program would.
|
Package mailbox keeps the mail an app sends under tug dev, rather than send it: package mail's Mailbox keeps each one in a directory, Dir, and the App shows them at Path, as a mail program would. |
|
nonce
Package nonce carries a response's Content-Security-Policy nonce in its request's context: package middleware makes it, and package inertia hands it to the root template, and neither imports the other.
|
Package nonce carries a response's Content-Security-Policy nonce in its request's context: package middleware makes it, and package inertia hands it to the root template, and neither imports the other. |
|
route
Package route keeps the route that answered a request in its context: tug's App puts a place for it there as the request comes in, before any middleware, its router fills the place in as a route answers, and tug.RouteOf and package middleware's Logger read it once the handler has run, whatever copies of the request the middleware in between made.
|
Package route keeps the route that answered a request in its context: tug's App puts a place for it there as the request comes in, before any middleware, its router fills the place in as a route answers, and tug.RouteOf and package middleware's Logger read it once the handler has run, whatever copies of the request the middleware in between made. |
|
rw
Package rw wraps an http.ResponseWriter to remember what went through it: the status, and so whether the response has started, which an error handler has to know before it writes an error page, and the size, for the log.
|
Package rw wraps an http.ResponseWriter to remember what went through it: the status, and so whether the response has started, which an error handler has to know before it writes an error page, and the size, for the log. |
|
seal
Package seal encrypts values with AES-256-GCM, under a key for one use alone, derived by HKDF from each of the app's keys: the first seals, and each of them opens, so the app's key can be rotated.
|
Package seal encrypts values with AES-256-GCM, under a key for one use alone, derived by HKDF from each of the app's keys: the first seals, and each of them opens, so the app's key can be rotated. |
|
typegen
Package typegen writes the TypeScript that mirrors a tug app's Go: the props each page takes, the props every page shares, and the app's named routes, with what each that takes one is sent.
|
Package typegen writes the TypeScript that mirrors a tug app's Go: the props each page takes, the props every page shares, and the app's named routes, with what each that takes one is sent. |
|
ulid
Package ulid makes IDs that sort as they were made: ULIDs, 48 bits of the millisecond and 80 random ones, in 26 characters of Crockford's base32.
|
Package ulid makes IDs that sort as they were made: ULIDs, 48 bits of the millisecond and 80 random ones, in 26 characters of Crockford's base32. |
|
Package lang says an app's texts in its languages: what tug says to a person, as a form's errors, and what the app does, as its flash messages and mail.
|
Package lang says an app's texts in its languages: what tug says to a person, as a form's errors, and what the app does, as its flash messages and mail. |
|
Package letsencrypt gets an app's certificates from Let's Encrypt, for tug's Run to serve HTTPS itself, as on a machine of the app's own with no proxy in front to end TLS:
|
Package letsencrypt gets an app's certificates from Let's Encrypt, for tug's Run to serve HTTPS itself, as on a machine of the app's own with no proxy in front to end TLS: |
|
Package mail sends email: through an SMTP server, or, while developing, into a mailbox the app shows under tug dev, or out to the terminal.
|
Package mail sends email: through an SMTP server, or, while developing, into a mailbox the app shows under tug dev, or out to the terminal. |
|
mailtest
Package mailtest is for testing with package mail: Outbox is a Mailer that keeps what it's sent, for a test to read.
|
Package mailtest is for testing with package mail: Outbox is a Mailer that keeps what it's sent, for a test to read. |
|
Package middleware is net/http middleware for tug apps.
|
Package middleware is net/http middleware for tug apps. |
|
Package migrate runs an app's migrations: files of SQL in its migrations/ directory, each a change to its database's tables, run once, in the order of their names, which say when they were made:
|
Package migrate runs an app's migrations: files of SQL in its migrations/ directory, each a change to its database's tables, run once, in the order of their names, which say when they were made: |
|
migratetest
Package migratetest has the promises a migrate.Store keeps, as tests, which each Store's own tests run on its database.
|
Package migratetest has the promises a migrate.Store keeps, as tests, which each Store's own tests run on its database. |
|
Package queue runs jobs in the background: work a request starts and doesn't wait for, such as sending a mail.
|
Package queue runs jobs in the background: work a request starts and doesn't wait for, such as sending a mail. |
|
queuetest
Package queuetest is for testing with package queue: Memory is a Store for tests, and TestStore checks that a Store keeps the promises the queue depends on.
|
Package queuetest is for testing with package queue: Memory is a Store for tests, and TestStore checks that a Store keeps the promises the queue depends on. |
|
Package session keeps a visitor's session in an encrypted cookie, with flash data: values one request leaves for the next, such as a message after a redirect or the validation errors a form is sent back with.
|
Package session keeps a visitor's session in an encrypted cookie, with flash data: values one request leaves for the next, such as a message after a redirect or the validation errors a form is sent back with. |
|
sessiontest
Package sessiontest is for testing with package session: TestStore checks that a session.Keeper keeps the promises a session.Store depends on.
|
Package sessiontest is for testing with package session: TestStore checks that a session.Keeper keeps the promises a session.Store depends on. |
|
Package ssr renders Inertia pages on the server, for their first visits, so a page's HTML is in the response before its scripts run: for search engines, link previews, and pages that show sooner.
|
Package ssr renders Inertia pages on the server, for their first visits, so a page's HTML is in the response before its scripts run: for search engines, link previews, and pages that show sooner. |
|
Package storage keeps files, such as what people upload: in a directory on the app's own disk, or in S3, or any service that speaks S3's API, such as Cloudflare R2 or MinIO.
|
Package storage keeps files, such as what people upload: in a directory on the app's own disk, or in S3, or any service that speaks S3's API, such as Cloudflare R2 or MinIO. |
|
Package tugtest tests an app's pages as Inertia's client uses them, without a browser or a frontend build.
|
Package tugtest tests an app's pages as Inertia's client uses them, without a browser or a frontend build. |
|
Package validate checks a struct against the rules in its validate tags, and says what's wrong in words for the person who filled in the form:
|
Package validate checks a struct against the rules in its validate tags, and says what's wrong in words for the person who filled in the form: |
|
Package vite puts a frontend built with Vite into pages that Go renders: the tags that load it, from the dev server while that runs and from the build's manifest otherwise, and a handler that serves the built files.
|
Package vite puts a frontend built with Vite into pages that Go renders: the tags that load it, from the dev server while that runs and from the build's manifest otherwise, and a handler that serves the built files. |