Documentation
¶
Overview ¶
Package lagogin is the official Gin adapter for lagodev.
It mirrors the Laravel-style DX of the `web` package — `(any, error)` handler signature, automatic status mapping, Resource() one-liner, JWT middleware, pagination, validation, and OpenAPI generation — but builds on top of github.com/gin-gonic/gin instead of net/http.
Quick start:
r := gin.Default()
r.Use(lagogin.QueryLog(conn)) // X-DB-Query-Count header
lagogin.Resource(r, "posts", &PostController{Conn: conn})
api := r.Group("/api", lagogin.AuthJWT(authManager))
api.GET("/me", lagogin.H(meHandler))
r.GET("/posts", lagogin.H(func(c *lagogin.Ctx) (any, error) {
return c.Paginate(orm.Query[Post](conn).OrderBy("id", "desc"))
}))
The handler signature `func(*lagogin.Ctx) (any, error)` removes the repetitive `if err != nil { c.JSON(...) }` boilerplate: orm.ErrNotFound is automatically mapped to 404, other errors to 500, nil to 204, and any returned value is JSON-encoded with 200 (or whatever Status() set).
Index ¶
- func APIResource(r IRouter, name string, ctrl ResourceController)
- func Auth() gin.HandlerFunc
- func AuthJWT(m *auth.Manager) gin.HandlerFunc
- func CORS(allowed ...string) gin.HandlerFunc
- func H(h Handler) gin.HandlerFunc
- func HS(handlers ...Handler) []gin.HandlerFunc
- func Instrument(conn *database.Connection) *database.Connection
- func ObserveQuery(conn *database.Connection)
- func OpenAPI(info SpecInfo, opts ...Option) map[string]any
- func QueryLog(conn *database.Connection) gin.HandlerFunc
- func QueryLogN(conn *database.Connection, threshold int) gin.HandlerFunc
- func RegisterRoute(method, path, resource, op string)
- func RequestTimeout(d time.Duration) gin.HandlerFunc
- func ResetRoutes()
- func Resource(r IRouter, name string, ctrl ResourceController)
- func ServeOpenAPI(r IRouter, info SpecInfo, opts ...Option)
- func Validate(dst any) error
- func WriteOpenAPI(path string, info SpecInfo, opts ...Option) error
- type Ctx
- func (c *Ctx) Bind(dst any) error
- func (c *Ctx) BindAndValidate(dst any) error
- func (c *Ctx) Created(v any) any
- func (c *Ctx) Ctx() context.Context
- func (c *Ctx) NoContent()
- func (c *Ctx) Param(name string) string
- func (c *Ctx) ParamInt(name string) int
- func (c *Ctx) ParamUint(name string) uint64
- func (c *Ctx) Query(name string) string
- func (c *Ctx) QueryDefault(name, fallback string) string
- func (c *Ctx) QueryInt(name string, fallback int) int
- func (c *Ctx) Role() string
- func (c *Ctx) UserID() uint64
- type Handler
- type IRouter
- type Option
- type Page
- type PaginatedQuery
- type ResourceController
- type RouteEntry
- type SpecInfo
- type ValidationError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func APIResource ¶
func APIResource(r IRouter, name string, ctrl ResourceController)
APIResource is an alias for Resource — same behavior, Laravel-flavored name.
func Auth ¶
func Auth() gin.HandlerFunc
Auth is the lightweight cousin of AuthJWT — only checks the header is present, stores the raw token, and lets the handler verify it however it likes. Use AuthJWT when you already have an auth.Manager handy.
func AuthJWT ¶
func AuthJWT(m *auth.Manager) gin.HandlerFunc
AuthJWT verifies an Authorization: Bearer <token> header. Valid claims are stored on the gin.Context as "auth_user_id", "auth_role", "auth_claims" — retrievable via Ctx.UserID(), Ctx.Role(), or c.Get(...).
Invalid or missing tokens abort with 401 + JSON {"error": "..."}.
func CORS ¶
func CORS(allowed ...string) gin.HandlerFunc
CORS returns a CORS middleware. Pass explicit origins to restrict; use "*" or no args for permissive.
func H ¶
func H(h Handler) gin.HandlerFunc
H converts a Handler into a gin.HandlerFunc. orm.ErrNotFound is mapped to 404, validation errors to 422, any other error to 500. A `(value, nil)` return is JSON-encoded with status 200; `(nil, nil)` becomes 204.
func HS ¶
func HS(handlers ...Handler) []gin.HandlerFunc
HS converts a series of handlers into gin.HandlerFunc slice for routes that take a final handler plus middleware. Equivalent to manually calling H on each.
func Instrument ¶
func Instrument(conn *database.Connection) *database.Connection
Instrument enables per-connection query counting for QueryLog. Call once at startup before installing the middleware. The returned connection is the same pointer — Instrument only registers it with the global counter table and replaces conn.Log with a counting wrapper.
The wrapper delegates Info/Warn/Error/SQL/SlowSQL to the original logger, so SQL tracing and slow-query reporting continue to work unchanged.
func ObserveQuery ¶
func ObserveQuery(conn *database.Connection)
ObserveQuery bumps the per-connection counter. Called by Connection observation hooks; tests and custom executors can call it directly.
func OpenAPI ¶
OpenAPI returns an OpenAPI 3.0 spec built from the Resource() registry. Each registered resource produces the canonical 5 endpoints plus a JSON schema (id-only by default — apps can pass a Models map to enrich it).
spec := lagogin.OpenAPI(lagogin.SpecInfo{Title: "MyAPI", Version: "1.0"})
r.GET("/openapi.json", lagogin.H(func(c *lagogin.Ctx) (any, error) { return spec, nil }))
To customize per-resource schemas (request/response bodies), pass a Schemas map keyed by resource name (the plural used in Resource()):
lagogin.OpenAPI(info, lagogin.WithSchema("posts", PostSchema()))
func QueryLog ¶
func QueryLog(conn *database.Connection) gin.HandlerFunc
QueryLog returns a middleware that counts SQL queries for the lifetime of each request and writes the total in the X-DB-Query-Count response header. If the count crosses threshold (20 by default), a WARN is logged with the request path — a cheap N+1 detector for dev environments.
Requires the connection to be passed through Instrument() once at startup:
conn = lagogin.Instrument(conn) r.Use(lagogin.QueryLog(conn))
Use QueryLogN to override the threshold.
func QueryLogN ¶
func QueryLogN(conn *database.Connection, threshold int) gin.HandlerFunc
QueryLogN is QueryLog with a custom N+1 warning threshold.
func RegisterRoute ¶
func RegisterRoute(method, path, resource, op string)
RegisterRoute records a custom route in the lagogin registry so it shows up in OpenAPI() output. Call this when you wire a handler outside of Resource() and still want it to appear in the generated spec.
r.GET("/health", lagogin.H(health))
lagogin.RegisterRoute("GET", "/health", "health", "index")
func RequestTimeout ¶
func RequestTimeout(d time.Duration) gin.HandlerFunc
RequestTimeout aborts the request after d. The handler observes a canceled context.Context; long-running DB calls bail out automatically.
func ResetRoutes ¶
func ResetRoutes()
ResetRoutes clears the route registry. Intended for tests that want a clean slate; production code should never call it.
func Resource ¶
func Resource(r IRouter, name string, ctrl ResourceController)
Resource registers 5 canonical RESTful routes for a controller in one call:
GET /posts → ctrl.Index GET /posts/:id → ctrl.Show POST /posts → ctrl.Store PUT /posts/:id → ctrl.Update PATCH /posts/:id → ctrl.Update DELETE /posts/:id → ctrl.Destroy
`name` should be the plural resource name (e.g. "posts", "users"). The registered routes are also recorded in the lagogin route registry so OpenAPI() and PrintRoutes() can introspect them later.
func ServeOpenAPI ¶
ServeOpenAPI mounts /openapi.json and a simple /docs HTML page on r. The HTML uses Swagger UI from a public CDN; for offline environments, serve the JSON yourself and host UI assets locally.
func Validate ¶
Validate inspects struct tags on dst and runs lightweight validators against each field. Supported tag form:
`validate:"required,min=3,max=200,email,oneof=admin user"`
Supported rules:
required — disallow zero values (including empty strings) min=N — int/float ≥ N, string length ≥ N, slice len ≥ N max=N — opposite of min email — basic RFC-ish email shape url — must start with http:// or https:// oneof=a b c — value must equal one of the listed tokens alpha — letters only alphanumeric — letters and digits only uuid — 8-4-4-4-12 hex pattern
On failure Validate returns *ValidationError; respond() maps it to a 422 Unprocessable Entity with `{"errors": {...}}`.
Types ¶
type Ctx ¶
Ctx wraps *gin.Context with helpers that match the lagodev web.Context API. Handlers receive *Ctx and return (any, error). The H wrapper converts the return value into a Gin response automatically.
func (*Ctx) Bind ¶
Bind decodes the JSON body into dst. On failure responds with 400 and returns the parse error so the handler can `return nil, err`.
func (*Ctx) BindAndValidate ¶
BindAndValidate combines Bind() and Validate(). Returns the same *ValidationError on rule failure (mapped to 422 by respond), or the underlying decoder error on malformed JSON (handled as 400 by Bind).
func (*Ctx) Created ¶
Created marks the response 201 and returns v so the handler can:
return c.Created(post), nil
func (*Ctx) QueryDefault ¶
QueryDefault returns the query parameter, or fallback when empty.
type Handler ¶
Handler is the lagogin-style handler signature: `(any, error)`. The H() wrapper converts it into a `gin.HandlerFunc` with automatic status mapping. Use it like:
r.GET("/users/:id", lagogin.H(func(c *lagogin.Ctx) (any, error) {
return svc.Get(c.Ctx(), c.ParamUint("id"))
}))
type IRouter ¶
type IRouter interface {
GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes
}
IRouter is the minimum gin router surface lagogin needs. *gin.Engine and *gin.RouterGroup both satisfy it.
type Option ¶
type Option func(*config)
Option configures OpenAPI().
func WithSchema ¶
WithSchema attaches a JSON schema for the named resource. Keys may be a resource name ("posts") or a model name ("Post").
type Page ¶
type Page struct {
Data any `json:"data"`
Total int64 `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
LastPage int `json:"last_page"`
From int `json:"from"`
To int `json:"to"`
}
Page describes one paginated payload — a Laravel-style envelope so clients can render pagination UI without extra round-trips.
func Paginate ¶
Paginate runs a model-aware query through page/per_page query params and returns a Page envelope. The query is cloned-by-reference: callers should not reuse the builder after calling Paginate.
Example:
r.GET("/posts", lagogin.H(func(c *lagogin.Ctx) (any, error) {
return lagogin.Paginate[Post](c, orm.Query[Post](conn).OrderBy("id", "desc"))
}))
Query params:
page — 1-indexed page number (default 1) per_page — items per page (default 25, max 200)
The function uses orm.Builder[T] generics so the result type is correct at compile time and no reflection is needed beyond what the ORM already does.
type PaginatedQuery ¶
type PaginatedQuery[T any] interface { Count(ctx context.Context) (int64, error) Limit(n int) *T Offset(n int) *T }
PaginatedQuery is the contract Paginate() expects — both *orm.Builder[T] and *query.Builder satisfy it via a thin adapter.
type ResourceController ¶
type ResourceController interface {
Index(c *Ctx) (any, error)
Show(c *Ctx) (any, error)
Store(c *Ctx) (any, error)
Update(c *Ctx) (any, error)
Destroy(c *Ctx) (any, error)
}
ResourceController is the contract for Resource(r, name, ctrl). All 5 methods must be implemented; leave any of them returning (nil, nil) if you don't need the endpoint.
type RouteEntry ¶
RouteEntry is the public view of an internal routeInfo.
func Routes ¶
func Routes() []RouteEntry
Routes returns a sorted snapshot of every Resource()-registered route. Useful for debug output or test assertions.
type SpecInfo ¶
type SpecInfo struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
Version string `json:"version"`
}
SpecInfo is the OpenAPI 3.0 info object — minimum metadata for a spec.
type ValidationError ¶
ValidationError is the error type respond() recognizes and maps to 422. The Fields map is keyed by JSON field name (or struct field name when no `json:"..."` tag is present); each value is a human-readable message.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error implements the error interface.