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 query counting for QueryLog. It is idempotent and returns the same pointer: it registers conn with the counter table and installs a database query hook that bumps the counter of the request whose context the statement ran with. Logging settings are left untouched.
Previously nothing but manual ObserveQuery calls fed the counter, so X-DB-Query-Count was always 0 for real traffic.
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.
Queries are counted per request: those executed with the request's context (c.Ctx() / c.Request.Context()) on conn, plus manual ObserveQuery calls. QueryLog instruments conn itself; calling Instrument() first is optional:
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.