Kitto.Web.Rest
Opt-in REST/JSON API for a KittoX application. Exposes the same views used by the HTML/HTMX GUI as a parallel tree under /api/v4/{ViewName} with real HTTP verbs, sharing the CRUD service layer (Kitto.Web.Data.Service) — same model, same business rules, same ACL as the GUI.
This unit is NOT part of the core umbrella (Kitto.Html.All): an application enables the REST API by adding it to its UseKitto.pas, exactly as it opts into Kitto.Web.Enterprise. It depends only on the RTL System.JSON (no third-party JSON framework); delphi-neon support (typed DTOs, OpenAPI) is a separate opt-in unit added later.
Endpoints (base path /api/v4/{ViewName}), all exempt from the navigation guard ([TKXNavigable] — a REST client does not send X-KittoX): GET /api/v4/{V} list -> { "data": [...], "total": N } GET /api/v4/{V}/{id} read one -> { ... } | 404 POST /api/v4/{V} create -> 201 + created record PUT /api/v4/{V}/{id} full update -> 200 + updated record PATCH /api/v4/{V}/{id} partial upd. -> 200 + updated record DELETE /api/v4/{V}/{id} delete -> 204
Errors on /api are rendered as a JSON envelope { error, code, field? } with a real HTTP status (see TKXApiErrorFilter). See KittoX_RestServer.md.
IKXApiSerializer interface
Serialization seam between the REST handler and a concrete JSON provider. The default provider uses System.JSON; a Neon-based provider can replace it via SetKXApiSerializer. The serializer is PURE — it never fires business rules (those run in the service layer); ParseInto only maps JSON to record field values.
function SerializeStore(const AStore: TKViewTableStore;Serializes a store page as a JSON list envelope { "data": [...], "total": N }.
function SerializeRecord(const ARecord: TKViewTableRecord): string;Serializes a single record as a JSON object.
procedure ParseInto(const AJSON: string;Parses a JSON object body and applies its members to the record's fields (through TKXDataService.ApplyFieldValue). No business rules here.
TKXSystemJSONSerializer class
Default serializer built on the RTL System.JSON DOM (correct escaping, zero deps).
function SerializeStore(const AStore: TKViewTableStore;Serializes a store page as a JSON list envelope { "data": [...], "total": N }.
function SerializeRecord(const ARecord: TKViewTableRecord): string;Serializes a single record as a JSON object.
procedure ParseInto(const AJSON: string;Parses a JSON object body and applies its members to the record's fields (via TKXDataService.ApplyFieldValue); unknown members are ignored.
TKXApiHandlerBase class
REST API handler for a data view. All endpoints are virtual so an application can subclass and RegisterOverride to customize a single verb.
procedure GetList([TKXPathParam('ViewName')] const AViewName: string);GET list: envelope { data, total } with ?start=&limit=&sort=&dir=.
procedure GetItem([TKXPathParam('ViewName')] const AViewName: string;GET one record by key (404 if absent).
procedure PostItem([TKXPathParam('ViewName')] const AViewName: string);POST create: body = JSON record; 201 + created record.
procedure PutItem([TKXPathParam('ViewName')] const AViewName: string;PUT full update: 200 + updated record.
procedure PatchItem([TKXPathParam('ViewName')] const AViewName: string;PATCH partial update (only the fields present in the body): 200 + updated record.
procedure DeleteItem([TKXPathParam('ViewName')] const AViewName: string;DELETE by key: 204.
TKXApiAuthHandler class
Token endpoint for stateless REST clients. POST /api/v4/token with a JSON body { "username", "password", "database"? } authenticates via the app's authenticator and returns a Bearer JWT: { "token_type":"Bearer", "access_token":"<jwt>" }. Anonymous (no token needed to obtain one). Requires Auth: JWT (the only authenticator that issues tokens).
procedure PostToken;Authenticates the JSON body {username,password,database?} and returns a Bearer JWT; 401 on invalid credentials.
TKXApiCorsHandler class
Matches the CORS preflight (OPTIONS) on the two /api/v4 path shapes so the request enters the filter chain, where TKXCorsFilter answers it. If CORS is disabled (or the origin is not allowed) the filter does nothing and these no-op methods return a bare 204. Anonymous: a preflight carries no credentials.
procedure PreflightCollection([TKXPathParam('ViewName')] const AViewName: string);CORS preflight for the collection path /api/v4/{ViewName}.
procedure PreflightItem([TKXPathParam('ViewName')] const AViewName: string;CORS preflight for the item path /api/v4/{ViewName}/{id}.
TKXCorsFilter class
Adds CORS headers to /api/v4 responses and answers the preflight, when the request's Origin is allowed by Server/CORS/AllowedOrigins in Config.yaml ('*' or a comma-separated list; empty = CORS disabled). Optional Server/CORS/AllowCredentials: True emits Access-Control-Allow-Credentials.
procedure BeforeInvoke(const AContext: IKXRequestContext);For an allowed cross-origin /api request, emits the CORS response headers and, on an OPTIONS preflight, answers 204 and short-circuits the chain.
procedure AfterInvoke(const AContext: IKXRequestContext);No-op.
function OnException(const AContext: IKXRequestContext;Does not handle exceptions (always returns False).
TKXApiErrorFilter class
Renders any exception escaping an /api/v4 request as a JSON error envelope { error, code, field? } with a real HTTP status (EKXDataError carries its own; rule/validation errors map to 422; anything else to 500). Registered after the core filters so its OnException runs first: for /api it wins, for every other path it returns False and the HTML error dialog handles it.
procedure BeforeInvoke(const AContext: IKXRequestContext);No-op.
procedure AfterInvoke(const AContext: IKXRequestContext);No-op.
function OnException(const AContext: IKXRequestContext;For an /api request, renders E as a JSON error envelope { error, code, field? } with a real HTTP status and returns True; returns False for any other path.
Routines
function KXApiSerializer: IKXApiSerializer;The active API serializer (lazily defaults to TKXSystemJSONSerializer).
procedure SetKXApiSerializer(const AValue: IKXApiSerializer);Overrides the active API serializer (e.g. a Neon-based one).
