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

tug

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 33 Imported by: 0

README

tug

A web framework for Go apps whose frontend is Inertia.js: React pages rendered with props straight from Go handlers, with no API in between, and the whole app shipped as one binary.

The name: a tugboat is small, and moves ships many times its size.

Status: early. v0.4.0 is the latest release: the framework, its CLI, background jobs, on a schedule too, and a starter with accounts, from registering to two-factor logins. The guide covers all of it, and docs/roadmap.md has what's next.

go install github.com/cuonggt/tug/cmd/tug@latest
tug new blog                                        # a new app, in ./blog; tug new -auth blog for accounts
cd blog
tug dev                                             # http://127.0.0.1:8080, rebuilt and reloaded as it changes
type PostsIndexProps struct {
	Posts []Post                   `json:"posts"`
	Stats inertia.DeferProp[Stats] `json:"stats"` // fetched after the page shows
}

var PostsIndex = tug.Page[PostsIndexProps]("Posts/Index") // resources/js/pages/Posts/Index.tsx

func index(c *tug.Ctx) error {
	return PostsIndex.Render(c, PostsIndexProps{
		Posts: store.List(),
		Stats: inertia.Defer(store.Count),
	})
}

type PostInput struct {
	Title string `json:"title" validate:"required,max=80"`
	Body  string `json:"body" validate:"required"`
}

func create(c *tug.Ctx) error {
	var in PostInput
	if err := c.BindValid(&in); err != nil {
		return err // back to the form with its errors; a field being filled in is checked here too
	}
	post := store.Add(in)
	c.Flash("success", "Post created") // usePage().flash on the next page
	return c.RedirectRoute("posts.show", post.ID)
}

examples/inertia is the whole app: React pages, a deferred prop, forms that check each field as it's left, flash messages, the Vite dev server with hot reload, and the frontend embedded in the binary for production. Handlers that aren't pages look like this:

func main() {
	app := tug.New() // ADDR or PORT, and APP_DEBUG, from the environment
	app.Use(middleware.RequestID(), middleware.Logger(), middleware.Recover(), middleware.CSRF())

	app.Get("/posts/{id}", showPost).Name("posts.show")

	if err := app.Run(); err != nil { // graceful shutdown on SIGINT and SIGTERM
		log.Fatal(err)
	}
}

func showPost(c *tug.Ctx) error {
	var in struct {
		ID int64 `path:"id"`
	}
	if err := c.Bind(&in); err != nil {
		return err // /posts/abc is a 404
	}
	post, ok := posts[in.ID]
	if !ok {
		return tug.NewHTTPError(http.StatusNotFound, "post not found")
	}
	return c.JSON(http.StatusOK, post)
}

examples/api is a complete JSON API.

What's here

  • The CLI, tug: tug new makes an app, ready to run, with a .env and a fresh APP_KEY. tug dev runs it with Vite: Go is rebuilt and restarted as it changes, and the browser reloaded. tug gen writes the TypeScript of each page's props and of the named routes, with a typed route(), so the frontend is checked against the Go. tug build makes one static binary with the frontend in it, and the app comes with a Dockerfile for a distroless image.
  • Inertia pages (package inertia), to the whole v3 protocol: a first visit gets HTML with the page object, later visits get JSON. tug.Page[Props] ties a component to the props it takes, and props nest at any depth.
    • Loading: Lazy, Optional, Always and Defer props, worked out concurrently. Defer(...).Rescue() turns a failure into Inertia's rescue slot rather than a failed page.
    • Merging: Merge props, prepended or deep-merged, and matched on a key. Scroll pages a list for <InfiniteScroll>.
    • Once: Once props stay on the client until they expire.
    • Partial reloads reach nested props by path.
    • Redirects: a browser running an old build reloads. A 302 after a PUT, PATCH or DELETE becomes a 303, and a redirect to a #fragment keeps it.
    • Error pages: errors show as a page (Config.ErrorPage) with their own status.
    • Empty lists: nil slices go out as [], never null.
  • Forms: c.BindValid binds and checks a request by validate tags (package validate, go-playground/validator's rules) and checks of the handler's own. A form that doesn't validate goes back with its errors in the errors prop, under an error bag when the form names one; an API client gets a 422. Precognition, a form checking each field as it's left, is answered without running the rest of the handler.
  • Sessions (package session): in an encrypted cookie, keyed by APP_KEY, with flash data. c.Flash reaches the next page shown, after a redirect or not, and only that one.
  • Accounts: tug new -auth makes an app where people register and verify their email, log in, with a code from an authenticator app too once they turn that on, reset a forgotten password by email, and change their profile, password and appearance in settings, with the users in SQLite and a frontend of Tailwind and shadcn/ui, as Laravel's React starter kit has. Its handlers are the app's own code, on package auth, which has the parts where a slip is a security hole: argon2id password hashes, logins that end when the password changes, signed tokens for reset and verification links, two-factor codes and recovery codes kept encrypted, asking for the password again, and a throttle on guessing.
  • Mail (package mail): through an SMTP server, or in development written out where tug dev shows it, links and all.
  • Background jobs (package queue): work a request starts and doesn't wait for, such as a mail, kept by a store such as a table in the app's database, so a failure or a restart doesn't lose it. A job that fails runs again after a wait that grows, and the workers run beside the server with app.Go, finishing what they have as the app stops. Jobs run on a schedule too, with Every or a cron expression, once across all instances. The auth starter sends its mail this way, with its jobs in SQLite.
  • Vite (package vite): tags from the dev server while it runs, with the React refresh preamble, and from the build's manifest otherwise, with CSS and preloads. The built files are served, and cached for a year.
  • Routing on net/http's ServeMux: method routes, groups with their own middleware, and named routes with app.URL("posts.show", 42). Paths match exactly, so / is only the home page, and {name...} takes everything under a path. A trailing slash redirects to the route without it, and a wrong method is a 405 with Allow.
  • Handlers return errors. tug.NewHTTPError(404, "post not found") picks the status. Any other error is a 500 whose details stay in the log, or show in the response with APP_DEBUG=true. A panic is a 500 with its stack in the log. Errors are JSON for a client that asks for JSON.
  • c.Bind fills a struct from path values, the query, and a JSON or form body, files included, by struct tags. A value that doesn't parse is a 400 that names the field, "age must be a whole number"; a bad path value is a 404.
  • Middleware is func(http.Handler) http.Handler: RequestID, Logger (through slog), Recover, and CSRF, which is Go's http.CrossOriginProtection, so there are no tokens.
  • app.Run listens on ADDR or PORT, and on SIGTERM stops taking connections and lets the requests in flight finish.

tug needs Go 1.26. Its dependencies are go-playground/validator, for package validate, and golang.org/x/crypto, for argon2id in package auth; the rest is the standard library.

Development

go test -race ./...
go test -run '^$' -bench . -benchmem .    # tug next to ServeMux alone
ADDR=127.0.0.1:8080 go run ./examples/api

The Inertia example, from examples/inertia:

npm install
npm run dev                               # the Vite dev server
ADDR=127.0.0.1:8080 go run .              # in another terminal
npm run build && npx playwright test      # end to end, in Chrome

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultErrorHandler

func DefaultErrorHandler(c *Ctx, err error)

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. Anything else is a 500 that keeps its details in the log, unless Config.Debug is on. 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.

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

func New(config ...Config) *App

New returns an App. Without a Config it reads one from the environment (ConfigFromEnv); a Config passed in is used as it is.

func (*App) Go added in v0.3.0

func (a *App) Go(fn func(ctx context.Context) error)

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

func (a *App) Run() error

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.

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.

func (*App) Serve

func (a *App) Serve(ctx context.Context, ln net.Listener) error

Serve serves on ln until ctx is done, then shuts down as Run does. It closes ln.

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

func (a *App) URL(name string, params ...any) (string, error)

URL builds the path of the route named name, filling its wildcards with params in order: URL("posts.show", 42) is "/posts/42". Values are escaped, except for the slashes in a {name...} wildcard's value.

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
}

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".

func (*BindError) Error

func (e *BindError) Error() string

func (*BindError) Unwrap

func (e *BindError) Unwrap() error

type Config

type Config struct {
	// Addr is where Run listens. Default ":8080".
	Addr string

	// Debug puts error details and panic stacks into 500 responses. It is
	// for development: in production they belong in the log only.
	Debug bool

	// 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
}

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, and APP_DEBUG.

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

func (c *Ctx) Bind(dst any) error

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.

func (*Ctx) BindValid

func (c *Ctx) BindValid(dst any, checks ...func(validate.Errors)) error

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".

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

func (c *Ctx) Blob(code int, contentType string, b []byte) error

Blob writes b as the body, with the given content type.

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

func (c *Ctx) Context() context.Context

Context returns the request's context, which is canceled when the client goes away.

func (*Ctx) Flash

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

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.

func (*Ctx) HTML

func (c *Ctx) HTML(code int, html string) error

HTML writes html as a page, exactly as given: anything from a user in it must already be escaped.

func (*Ctx) Inertia

func (c *Ctx) Inertia(component string, props any) error

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

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

JSON writes v as JSON.

func (*Ctx) Location

func (c *Ctx) Location(url string) error

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

func (c *Ctx) NoContent(code int) error

NoContent writes a status and no body, such as 204.

func (*Ctx) Param

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

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

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

Query returns the first value of the query parameter name.

func (*Ctx) Redirect

func (c *Ctx) Redirect(to string) error

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

func (c *Ctx) RedirectBack() error

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

func (c *Ctx) RedirectRoute(name string, params ...any) error

RedirectRoute redirects to the named route, filling its wildcards as App.URL does.

func (*Ctx) Request

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

Request returns the request.

func (*Ctx) Response

func (c *Ctx) Response() http.ResponseWriter

Response returns the response writer.

func (*Ctx) Session

func (c *Ctx) Session() *session.Session

Session returns the request's session, or nil when the app has no Config.Session.

func (*Ctx) String

func (c *Ctx) String(code int, s string) error

String writes s as plain text.

func (*Ctx) URL

func (c *Ctx) URL(name string, params ...any) (string, error)

URL builds the path of a named route, as App.URL does.

func (*Ctx) Validate

func (c *Ctx) Validate(v any, checks ...func(validate.Errors)) error

Validate checks v as BindValid checks what it binds, for values that don't come straight from the request. It answers Precognition requests the same way.

func (*Ctx) Written

func (c *Ctx) Written() bool

Written reports whether the response has started. After that its status and headers are on their way and can't change.

type ErrorPageProps

type ErrorPageProps struct {
	Status  int    `json:"status"`
	Message string `json:"message"`
}

ErrorPageProps are the props of Config.ErrorPage.

type HTTPError

type HTTPError struct {
	Code    int
	Message string
	Err     error
}

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. Err is the cause, for the log; only Config.Debug shows it, in a server error's response.

func NewHTTPError

func NewHTTPError(code int, message ...string) *HTTPError

NewHTTPError returns an HTTPError with the given status. Without a message it says the status's text, such as "Not Found".

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

type HandlerFunc

type HandlerFunc func(c *Ctx) error

HandlerFunc handles a request. A returned error is answered by the app's ErrorHandler: return an *HTTPError to choose the status.

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 Middleware

type Middleware = func(http.Handler) http.Handler

Middleware wraps a handler. It is net/http's own shape, so middleware written for net/http works as it is.

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

func Page[P any](component string) PageOf[P]

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.

func (PageOf[P]) Component

func (p PageOf[P]) Component() string

Component returns the page's component name, as "Posts/Index".

func (PageOf[P]) Render

func (p PageOf[P]) Render(c *Ctx, props P) error

Render renders the page with props, as Ctx.Inertia does.

type PanicError

type PanicError struct {
	Value any
	Stack []byte
}

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 Props

type Props = inertia.Props

Props are a page's props, for a page without a struct of its own.

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

func (rt *Route) Name(name string) *Route

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

func (rt *Route) String() string

String describes the route by its method and path, "GET /posts/{id}".

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.

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:
cmd
tug command
Command tug makes and runs tug apps:
Command tug makes and runs tug apps:
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, and errors that come back as JSON to a client that asks for it.
Command api is a small JSON API on tug: posts kept in memory, behind named routes in a group, with the usual middleware, and errors that come back as JSON to a client that asks for it.
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
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.
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.
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.
Package mail sends email: through an SMTP server, or, while developing, out to the terminal, where a link in it can be clicked.
Package mail sends email: through an SMTP server, or, while developing, out to the terminal, where a link in it can be clicked.
Package middleware is net/http middleware for tug apps.
Package middleware is net/http middleware for tug apps.
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.
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.

Jump to

Keyboard shortcuts

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