Documentation
¶
Overview ¶
Package resource is a dependency-free API-resource / serializer layer for turning domain models into clean JSON representations — the JSON-API equivalent of Laravel's API Resources or Django REST Framework serializers.
The problem it removes is hand-mapping: every endpoint that returns a model otherwise repeats the same "copy these fields, rename that one, hide the password, add a computed URL" boilerplate. A Resource describes that mapping once, and the Item / Collection / Paginated wrappers render it consistently.
Defining a resource ¶
A resource is anything that maps one Model to an output value (typically a Fields map). Implement the interface, or wrap a function with Func:
type User struct {
ID int
Name string
Email string
Password string // never serialised
Admin bool
AvatarURL string
}
var UserResource = resource.Func(func(u User) any {
return resource.Fields{
"id": u.ID,
"name": u.Name,
"email": u.Email,
// computed field
"avatar": u.AvatarURL,
}.
When(u.Admin, "is_admin", true) // conditional: only admins carry it
})
Rendering ¶
resource.Item(user, UserResource) // -> single object
resource.Collection(users, UserResource) // -> {"data": [...]}
resource.Paginated(paginator, UserResource) // -> {"data": [...], "meta": {...}}
Each returns a plain value (map / struct) ready for an HTTP layer to encode, e.g. c.JSON(200, resource.Item(user, UserResource)). resource is a leaf package: it never imports web, so there is no import cycle.
Web integration ¶
func (h *Handler) Show(c *web.Context) (any, error) {
u, err := h.users.Find(c.Context(), id)
if err != nil {
return nil, err
}
return resource.Item(u, UserResource), nil
}
Index ¶
- func Embed[Model any](m Model, r Resource[Model]) any
- func EmbedMany[Model any](models []Model, r Resource[Model]) []any
- func Item[Model any](m Model, r Resource[Model]) any
- type CollectionResponse
- type Fields
- func (f Fields) Except(keys ...string) Fields
- func (f Fields) Merge(other Fields) Fields
- func (f Fields) OmitEmpty(keys ...string) Fields
- func (f Fields) Only(keys ...string) Fields
- func (f Fields) Rename(from, to string) Fields
- func (f Fields) Set(key string, val any) Fields
- func (f Fields) Unless(cond bool, key string, val any) Fields
- func (f Fields) When(cond bool, key string, val any) Fields
- func (f Fields) WhenFunc(cond bool, key string, val func() any) Fields
- type Func
- type Links
- type Meta
- type PaginatedResponse
- type Resource
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Embed ¶
Embed renders a single related model through its own resource, returning the transformed value for nesting under a relation key. A nil related pointer yields nil, so the JSON renders as null rather than panicking.
resource.Fields{
"id": post.ID,
"title": post.Title,
"author": resource.Embed(post.Author, UserResource),
}
func EmbedMany ¶
EmbedMany renders a slice of related models through their resource, returning a []any suitable for nesting under a relation key (e.g. a User embedding its Posts). A nil slice yields an empty, non-nil slice so the JSON is [] not null.
resource.Fields{
"id": user.ID,
"posts": resource.EmbedMany(user.Posts, PostResource),
}
Types ¶
type CollectionResponse ¶
type CollectionResponse struct {
Data []any `json:"data"`
Meta map[string]any `json:"meta,omitempty"`
}
CollectionResponse is the envelope returned by Collection: a "data" array of transformed models, plus optional top-level "meta". meta is omitted from the JSON when empty.
func Collection ¶
func Collection[Model any](models []Model, r Resource[Model]) CollectionResponse
Collection renders a slice of models through their resource, wrapping the results in {"data": [...]}. A nil slice renders as {"data": []}, never null.
c.JSON(200, resource.Collection(users, UserResource))
func (CollectionResponse) WithMeta ¶
func (c CollectionResponse) WithMeta(meta map[string]any) CollectionResponse
WithMeta attaches top-level meta to a CollectionResponse and returns it for chaining. Repeated calls merge.
type Fields ¶
Fields is the working output of a resource: an ordered-by-key, JSON-object mapping of response keys to values. encoding/json marshals it like any map[string]any (keys sorted), so output is deterministic.
The methods are chainable and return Fields so a resource body can read as a single expression. Every method is nil-safe: calling on a nil Fields lazily allocates, so the zero value is usable.
func (Fields) Except ¶
Except returns a new Fields with the listed keys removed (a blacklist). The receiver is not modified.
func (Fields) Merge ¶
Merge copies all keys from other into f (other wins on collisions). Useful for splicing in a shared block of fields.
func (Fields) OmitEmpty ¶
OmitEmpty deletes keys whose value is a Go zero value (nil, "", 0, false, empty slice/map). Use it to drop optional fields instead of rendering them as null/zero — the manual, opt-in equivalent of the json `,omitempty` tag.
func (Fields) Only ¶
Only returns a new Fields containing just the listed keys (a whitelist). Missing keys are skipped. The receiver is not modified.
func (Fields) Rename ¶
Rename moves the value at from to to, dropping the old key. It is a no-op if from is absent or from == to.
func (Fields) Set ¶
Set adds or overwrites key with val and returns the (possibly newly allocated) Fields for chaining.
type Func ¶
Func adapts an ordinary func(Model) any into a Resource. It is the common way to define a resource inline without declaring a named type.
type Links ¶
type Links struct {
First string `json:"first,omitempty"`
Last string `json:"last,omitempty"`
Prev string `json:"prev,omitempty"`
Next string `json:"next,omitempty"`
}
Links carries the URL helpers a paginated client typically needs. All fields are omitempty, so an unset link is absent rather than null. They are only populated when a caller provides a base URL via PaginatedLinks.
type Meta ¶
type Meta struct {
Total int64 `json:"total"`
Page int `json:"page"`
PerPage int `json:"per_page"`
LastPage int `json:"last_page"`
HasMore bool `json:"has_more"`
}
Meta describes a paginated result set. The keys mirror orm.Paginator so a client can drive a pager directly from the response.
type PaginatedResponse ¶
type PaginatedResponse struct {
Data []any `json:"data"`
Meta Meta `json:"meta"`
Links *Links `json:"links,omitempty"`
With map[string]any `json:"with,omitempty"`
}
PaginatedResponse is the envelope returned by Paginated: the page's "data", derived "meta", and optional "links". Both meta and links always render (meta is always meaningful for a page); extra top-level data merges into meta.
func Paginated ¶
func Paginated[Model any](p *orm.Paginator[Model], r Resource[Model]) PaginatedResponse
Paginated renders an orm.Paginator page through a resource, deriving the meta block (total, page, per_page, last_page, has_more) straight from the paginator. A nil paginator yields an empty page with zero-value meta.
page, _ := orm.Query[User](conn).Paginate(ctx, 2, 15) c.JSON(200, resource.Paginated(page, UserResource))
Importing orm here is cycle-free: orm imports only database, never resource.
func (PaginatedResponse) WithLinks ¶
func (p PaginatedResponse) WithLinks(base string) PaginatedResponse
WithLinks builds first/last/prev/next links from base (e.g. the request path) using a "?page=N" query and attaches them. base should already include any other query parameters or end clean; the page param is appended with the appropriate separator.
func (PaginatedResponse) WithMeta ¶
func (p PaginatedResponse) WithMeta(extra map[string]any) PaginatedResponse
WithMeta merges extra top-level data into the "with" block and returns the response for chaining.