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

api

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package api is the site's content API: its published content as JSON, for apps and decoupled frontends, at /api/v1/ (Path). It is off by default; Config.Enabled, as webthings.Config.API, turns it on.

Core serves the site, its search and its pages:

GET /api/v1/site                       the site: name, languages, menu and the modules with an API
GET /api/v1/site/search?q=harbour      the site search
GET /api/v1/pages                      the published page tree
GET /api/v1/pages/resolve?path=/en/about  the page at a site address, as the site resolves it
GET /api/v1/pages/{id}                 a page by its ID

Every endpoint answers in one language: the locale parameter, such as ?locale=nl, or the site's default language without it; /pages/resolve takes the language from the address. A module adds its own endpoints below /api/v1/<key>/ with a router of NewRouter (webthings.Parts.API), such as publishing's /api/v1/news/posts.

Responses

Responses are JSON objects with snake_case names, documented by the types of this package and the modules' API types: dates in RFC 3339 (ISO 8601), addresses absolute, on webthings.Config.BaseURL or the address of the request, images as an Image with their sizes and alt text, and Markdown content rendered as HTML with absolute addresses (body_html). A list is a List, which pages with limit (DefaultLimit, at most MaxLimit) and offset. A field without a value is null rather than left out, but for the few that only one endpoint gives, such as the body_html of a module's item, which its list leaves out.

An error is an ErrorResponse: its HTTP status, a stable code and the status text in the request's language, translated with the site's translations (see frontend.CoreTexts). Content that is not published, or not in the language asked for, and an address without an endpoint, also with another method than GET or HEAD, answer 404 not_found; a language the site does not offer answers 404 unknown_locale.

Security

The API shows what visitors see on the site, nothing else: only published content, never a draft, a scheduled revision or a preview, also not to signed-in editors, so every response is the same for everyone and the output cache keeps it. Modules expose no personal data, such as subscribers, registrations or form entries. Each client may make Config.RateLimit requests per Config.RateWindow; browsers of other sites may read the API only from Config.AllowedOrigins (CORS).

Index

Constants

View Source
const (
	DefaultLimit = 20
	MaxLimit     = 100
)

The pages of a List: DefaultLimit items when the request names no limit, at most MaxLimit.

View Source
const (
	// CodeNotFound: no published content at the address, in the language
	// asked for, or no endpoint.
	CodeNotFound = "not_found"
	// CodeUnknownLocale: the site does not offer the language asked for.
	CodeUnknownLocale = "unknown_locale"
	// CodeRateLimited: the client made more requests than Config.RateLimit.
	CodeRateLimited = "rate_limited"
	// CodeInternal: the server failed; the log has the details.
	CodeInternal = "internal_error"
)

The codes of errors (Error.Code), which stay the same while their messages are translated.

View Source
const DefaultRateLimit = 300

DefaultRateLimit is how many requests one client may make per minute unless Config.RateLimit says otherwise.

View Source
const Path = "/api/v1"

Path is where the API is served: version 1 of it.

Variables

This section is empty.

Functions

func Error

func Error(w http.ResponseWriter, r *http.Request, status int, code string)

Error answers a request with an error: status, code and the status text in the request's language (see ErrorBody).

func Fail

func Fail(w http.ResponseWriter, r *http.Request, err error)

Fail logs err and answers 500 internal_error.

func HTML

func HTML(r *http.Request, source string) string

HTML renders editor content, which is Markdown, as HTML for readers away from the site: as the site shows it, in the request's language, with every address of the site absolute, images with their srcset, size and alt text included.

func IsSearch

func IsSearch(r *http.Request) bool

IsSearch reports whether r is a search of the site search with a query, which caches leave alone: its answers are as many as the queries.

func ItemDates

func ItemDates[F, T any](r *http.Request, module string, item content.Item[F, T], source string, ids ...string) []time.Time

ItemDates are the dates of what the answer of a module's single item shows, for Modified: the item's last change, the revision it shows for content with revisions, the page the instance module is on (MountModified) and the media of its Markdown source and of the images ids (MediaModified). A module adds the dates of anything else the answer shows, such as the categories of a post:

api.Modified(w, api.ItemDates(r, s.key, item, text.Body, item.Fields.Image)...)

func Locale

func Locale(r *http.Request) string

Locale is the language a request of the API is answered in: its locale parameter, or the site's default language. Outside the API it is "".

func MediaModified

func MediaModified(r *http.Request, source string, ids ...string) time.Time

MediaModified is the date of the media assets a text shows, the media references in its Markdown source, and of the images ids, such as a post's featured image (see Modified): the latest change of their files, alt texts and focal points. It is zero when one of them is not in the library, such as one that was deleted, and outside the API; without any media it is the oldest date there is, which changes nothing.

func Modified

func Modified(w http.ResponseWriter, dates ...time.Time)

Modified gives the response a Last-Modified: the latest of dates, which are the dates of everything the answer shows, such as a post's, those of its categories, of the page its module is on (MountModified) and of its media (MediaModified). A client that asks with If-Modified-Since is then answered 304 Not Modified while none of them is newer: by go-router's LastModified, or by the output cache from its entry. A zero date is something whose date is unknown, such as a category that was deleted, and then the response gets none, since a date that can miss a change would keep that change from the client; so does a date ahead of the clock. Call it before Write.

An answer that changes without a newer date among what it shows, such as a list, which an item leaves when it is deleted, unpublished or past, gets no Last-Modified; its ETag still answers If-None-Match.

func MountModified

func MountModified(r *http.Request, module string) time.Time

MountModified is the date of the published page a module instance is on in the request's language, for the addresses of its content (see Modified): the latest change of that page and of the pages above it, whose slugs its address holds. It is zero while the instance is on no page, as the date of the page it was taken off is gone, and outside the API.

func NewRouter

func NewRouter() *router.Router

NewRouter returns a router for a module's endpoints (webthings.Parts.API), mounted at Path/<key> on the router the API registered on, so its routes are relative to that and their names get "api.<key>." in front for the app's other routers: routes.RoutePathArgs("post", "id", id) is "/api/v1/news/posts/<id>". Every request without a route, any path or method, gets the API's 404 not_found, as JSON.

routes := api.NewRouter()
routes.Get("/posts", list).As("posts")
routes.Get("/posts/{id}", show).As("post")

func NotFound

func NotFound(w http.ResponseWriter, r *http.Request)

NotFound answers 404 not_found: no published content here, or no endpoint.

func Optional

func Optional(value string) *string

Optional is value as a field that is null when empty, such as a URL of content on no page.

func URL

func URL(r *http.Request, path string) string

URL makes path, an address of the site such as "/en/news/hello", absolute on webthings.Config.BaseURL or the address of the request; "" stays "".

func Write

func Write(w http.ResponseWriter, r *http.Request, v any)

Write answers a request with v as JSON, status 200. The app gives the response its ETag and Cache-Control and keeps it in the output cache.

Types

type API

type API struct {
	// contains filtered or unexported fields
}

API serves the content API's own endpoints and the request scope every endpoint, a module's too, reads its language and helpers from.

func New

func New(options Options) *API

New returns the API for the services of options.

func (*API) Error

func (a *API) Error(w http.ResponseWriter, r *http.Request, status int, code string)

Error answers a request outside the API's routes, such as one over the rate limit, with an error (see the function Error), in the language of its locale parameter when the site offers it, else in the site's default language.

func (*API) Register

func (a *API) Register(routes *router.Router)

Register registers the API's endpoints on routes, the router mounted at Path (see NewRouter), behind the request scope, which modules' routers mounted on it later get as well. Their names are site, search, pages, resolve and page.

type Alternate

type Alternate struct {
	Locale string `json:"locale"`
	URL    string `json:"url"`
	APIURL string `json:"api_url"`
}

Alternate is a page in another language.

type Config

type Config struct {
	// Enabled serves the API at Path. Off, the default, /api/v1/ is an
	// address of the site like any other, which has no page there (404).
	Enabled bool
	// AllowedOrigins are the sites whose pages may read the API in the
	// browser (CORS), such as "https://app.example.com"; "*" allows every
	// site and "*.example.com" its subdomains, on any scheme. Empty, the default,
	// sends no CORS headers: the site's own pages, apps and servers read the
	// API, other sites' pages cannot. A request from an origin not allowed
	// is answered 403 when there are AllowedOrigins.
	AllowedOrigins []string
	// RateLimit is how many requests one client, known by its address (see
	// webthings.Config.TrustedProxies), may make per RateWindow, answers
	// from the output cache included; more are answered 429 rate_limited.
	// Zero means DefaultRateLimit, negative no limit.
	RateLimit int
	// RateWindow is the time RateLimit counts in; zero means a minute.
	RateWindow time.Duration
}

Config is the content API of webthings.Config.API.

func (Config) Limit

func (c Config) Limit() (int, time.Duration)

Limit is the rate limit of the configuration: requests per window, zero for none.

type Crumb

type Crumb struct {
	ID     string `json:"id"`
	Title  string `json:"title"`
	URL    string `json:"url"`
	APIURL string `json:"api_url"`
}

Crumb is a page on the way from the home page to a page.

type ErrorBody

type ErrorBody struct {
	Status  int    `json:"status"`
	Code    string `json:"code"`
	Message string `json:"message"`
}

ErrorBody is an error: the response's HTTP status, a stable code (see CodeNotFound and the others) and the status text in the request's language, for people.

type ErrorResponse

type ErrorResponse struct {
	Error ErrorBody `json:"error"`
}

ErrorResponse is the body of every error: {"error": {...}}.

type Image

type Image struct {
	URL         string `json:"url"`
	Srcset      string `json:"srcset"`
	Width       int    `json:"width"`
	Height      int    `json:"height"`
	Alt         string `json:"alt"`
	OriginalURL string `json:"original_url"`
}

Image is an image of the media library as pages show it (media.Image): a copy at most media.ImageWidth wide, a srcset of smaller copies, its size, the editors' alt text in the request's language and the uploaded original, every address absolute. A field of type *Image is null without an image.

func ImageOf

func ImageOf(r *http.Request, id string) *Image

ImageOf is the image asset id as an Image (see Image), nil for an empty id, an unknown asset or a request outside the API.

type Language

type Language struct {
	Locale  string `json:"locale"`
	URL     string `json:"url"`
	APIURL  string `json:"api_url"`
	Default bool   `json:"default"`
}

Language is a language the site offers: its home page and the site in that language in the API.

type List

type List[T any] struct {
	Items  []T     `json:"items"`
	Total  int     `json:"total"`
	Limit  int     `json:"limit"`
	Offset int     `json:"offset"`
	Next   *string `json:"next"`
}

List is a page of a list: Items from Offset, at most Limit of them, of Total in all, and Next, the absolute address of the next page, null on the last.

func Paginate

func Paginate[T any](r *http.Request, items []T) List[T]

Paginate returns the page of items a request asks for with its limit and offset parameters (see List): DefaultLimit items from the start when it names none, at most MaxLimit. A limit or offset that is not a whole number, or is out of range, is the default or the nearest one allowed.

type Module

type Module struct {
	Key   string
	Title string
	Type  string
	// API is set when the instance has endpoints of its own
	// (webthings.Parts.API), below Path/<key>.
	API bool
}

Module is a module instance as the API knows it, for the site's list of modules and the module of a page.

type ModuleRef

type ModuleRef struct {
	Key   string `json:"key"`
	Title string `json:"title"`
	Type  string `json:"type"`
	// URL is the page the instance is placed on, null when on none.
	URL *string `json:"url"`
	// APIBase is the address its endpoints are below, such as
	// "https://example.com/api/v1/news" for /api/v1/news/posts, null
	// when it has none.
	APIBase *string `json:"api_base"`
	// Path is the address below the module's page that a page was
	// resolved for (/pages/resolve), such as "/hello" for a post: the
	// module's own content, which its endpoints give; "/" for the page
	// itself. It is left out elsewhere.
	Path string `json:"path,omitempty"`
}

ModuleRef is a module instance: the one placed on a page, or one of the site's modules with an API.

type Node

type Node struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	// Path is the page's address without the language, "/" for the home
	// page; URL its absolute address and APIURL the page in the API.
	Path     string     `json:"path"`
	URL      string     `json:"url"`
	APIURL   string     `json:"api_url"`
	Module   *ModuleRef `json:"module"`
	Children []Node     `json:"children"`
}

Node is a published page in the page tree, with the pages below it.

type Options

type Options struct {
	Site     *site.Service
	Pages    *pages.Service
	Media    *media.Service
	Frontend *frontend.Frontend
	// BaseURL is webthings.Config.BaseURL, for absolute addresses; empty
	// uses the address of each request.
	BaseURL string
	// Translations are the site's (webthings.Config.Translations), which
	// translate the messages of errors; nil shows them as written.
	Translations *translator.Translator
	// Modules are the site's module instances, in the order they were
	// added.
	Modules []Module
}

Options are the services the API serves the site's content from.

type Page

type Page struct {
	ID          string `json:"id"`
	Locale      string `json:"locale"`
	Title       string `json:"title"`
	Description string `json:"description"`
	// Slug is the page's own part of Path, empty for the home page.
	Slug     string  `json:"slug"`
	Path     string  `json:"path"`
	URL      string  `json:"url"`
	APIURL   string  `json:"api_url"`
	ParentID *string `json:"parent_id"`
	IsHome   bool    `json:"is_home"`
	// BodyHTML is the page's text rendered from Markdown (see HTML).
	BodyHTML string `json:"body_html"`
	// UpdatedAt is when the published revision was saved.
	UpdatedAt time.Time `json:"updated_at"`
	// Alternates are the page in the other languages it is published in.
	Alternates []Alternate `json:"alternates"`
	// Breadcrumbs lead from the home page to this page, both included.
	Breadcrumbs []Crumb `json:"breadcrumbs"`
	// Module is the module instance placed on the page, null for none.
	Module *ModuleRef `json:"module"`
}

Page is a published page in one language.

type SearchResult

type SearchResult struct {
	Title   string `json:"title"`
	Summary string `json:"summary"`
	URL     string `json:"url"`
	// Type is "page" or the module type, such as "publishing"; Module the
	// module instance, null for a page.
	Type   string  `json:"type"`
	Module *string `json:"module"`
}

SearchResult is a match of the site search: a page or a module's content.

type Site

type Site struct {
	Name        string `json:"name"`
	Tagline     string `json:"tagline"`
	Description string `json:"description"`
	// Locale is the language of the response, DefaultLocale the site's.
	Locale        string `json:"locale"`
	DefaultLocale string `json:"default_locale"`
	// URL is the home page in Locale.
	URL  string `json:"url"`
	// Languages are the languages the site offers.
	Languages []Language `json:"languages"`
	// Menu is the published page tree below the home page, as the site's
	// menu shows it.
	Menu []Node `json:"menu"`
	// Modules are the module instances with endpoints of their own.
	Modules []ModuleRef `json:"modules"`
}

Site is the site in one language, at /api/v1/site.

Jump to

Keyboard shortcuts

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