Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works

Random project

pascal-api-infra-faa

Infrastructure for REST APIs on Horse, for Delphi and Lazarus/FPC (dual-compiler): paging, ordering, config, file logging, rate limiting, DTO bases, messaging.

View on GitHub

Anyone may press Interesting, signed in or not. It helps others find this project.

Member rating

No ratings yet.

Sign in to rate this project.

README

pascal-api-infra-faa

Infrastructure for REST APIs on Horse, for Delphi and Free Pascal / Lazarus (FPC 3.2.2+, {$MODE DELPHI}), from one source tree.

It ports the ideas of delphi-api-infra-faa (Delphi only) to both compilers, on top of pascal-common-faa (optional types, clocks, threading), pascal-jsonmapper-faa (JSON ⇄ DTO) and pascal-db-faa (database layer, paging). It is not a drop-in replacement for the Delphi library: DTOs map published properties, because FPC 3.2.2's RTTI sees nothing else.

Version 0.32.1 (see the changelog): the core, the Horse middlewares, OpenAPI, an MCP server (protocol 2026-07-28), W3C trace context, metrics (Prometheus), health endpoints, spans exported to an OpenTelemetry collector (OTLP), opt-in multi-tenancy (docs/multitenancy-design.md), JWT signing keys that rotate by themselves, per tenant, sealed in their store (guide), and refresh tokens used once, a copy that comes back ending its login (guide), ETag/If-Match against lost updates and Idempotency-Key for retried POSTs (guide), the tenant from a public path, signed links, and a package without pascal-db-faa for a client program. The tenant catalog's cache is pascal-common-faa's IPcCache, in memory or shared by every instance through Redis. Observability plan: docs/observability-design.md. See docs/plan.md.

Guides

How to build an API with the library, one topic at a time (configuration, errors, DTOs, authentication, database, observability, OpenAPI, MCP, multi-tenancy...): docs/guide, which lists every guide and when to read it. Start with getting started; to keep your own code on both Delphi and Lazarus/FPC, read your project on both compilers.

The guides say how; the design documents (docs/*-design.md) say why each decision was taken and what was measured.

Contents

Unit What it gives you
PascalApi.Config TAppConfig: settings from the environment, then a .env file, then a default
PascalApi.OrderBy TOrderBySpec: the client's "name,-state" to an ORDER BY fragment, through an allow-list
PascalApi.Pagination query-string page/limit to TPageRequest (pascal-db-faa), and the paged JSON envelope
PascalApi.RateLimitState the sliding window behind rate limiting, on a monotonic clock
PascalApi.FileLog asynchronous file logging, one file per category, rotated by size; TLogTruncate
PascalApi.Dto marker interfaces and base classes for DTOs, including paged search and paged response
PascalApi.Text UTF-8 bytes ⇄ string, MD5, UTF-8-safe prefix — the same on both compilers
PascalApi.HttpTypes THttpHeader, with no dependency, so PascalApi.Otlp builds without pascal-db-faa; PascalApi.Http keeps the same type
PascalApi.HttpTransport IHttpTransport: one HTTP exchange, each compiler's own client behind it (THTTPClient on Delphi, fphttpclient with TLS on FPC), every status an answer, EHttpTransport for no answer; the OTLP exporters and the MCP executor send through it. PascalApi.HttpTransport.Fake: a queue of answers and a record of requests, for tests
PascalApi.SignedLinks TSignedLinks: a URL that grants one thing until an expiry (HMAC-SHA256, a key per scope derived from rotating keys, your application's context, constant-time and canonical Base64url checks); needs only pascal-common-faa
PascalApi.Http HTTP exceptions (EValidationException, ENotFoundException...), exception → status mapping, CORS, Bearer, client IP, trace context, access log line (text or JSON), metric labels, health answer; replaceable client messages (English or Portuguese)
PascalApi.Crypto SHA-256, HMAC-SHA256, Base64url (FPC 3.2.2 has no SHA-256): wrappers of pascal-common-faa's PascalCommon.Crypto
PascalApi.Jwt HS256 JSON Web Tokens: sign, verify (alg, signature, exp, nbf), claims; a key id (kid) in the header
PascalApi.JwtKeys signing keys named by kid (guide): a static ring (JWT_KEYS), a ring that generates and rotates its keys in a store every instance shares (TDbJwtKeyStore; Redis in src/redis), secrets sealed with AES-256-GCM under a master key (TSealedJwtKeyStore)
PascalApi.RefreshTokens refresh tokens used once (guide): <scope>.<secret>, only the SHA-256 stored, rotation in a family, a spent token that comes back or two refreshes at once revoking the family, your user check, logout and RevokeUser; stores in a database (TDbRefreshTokenStore, your named SQL) or in memory
PascalApi.Preconditions optimistic concurrency (guide): the row's version as a strong ETag, If-Match read as RFC 9110 says (a list, strong comparison), 428/400/412 with their codes
PascalApi.Idempotency Idempotency-Key (guide): a key per scope and request (SHA-256 fingerprint), claimed with a lease, the answer stored and replayed, a client error replayed and anything else released; stores in a database (TDbIdempotencyStore, your named SQL) or in memory
PascalApi.Horse.Idempotency (src/horse) THorseIdempotentRequest: the key read, the replay sent (Idempotent-Replayed: true), the answer stored, in a handler
PascalApi.Redis.JwtKeys (src/redis) TRedisJwtKeyStore: the keys in Redis, on pascal-redis-faa (outside the package)
PascalApi.Horse.Middlewares (src/horse) Horse middlewares: error handler, CORS, request log (with your own fields: AddField), Bearer auth, JWT, rate limit (key and limit per request: KeyExtractor, LimitFor), Connection: close on FPC 3.2 (TConnectionCloseMiddleware)
PascalApi.OpenApi OpenAPI 3.0.3 document from routes and DTO types; TApiSchema.Describe for metadata
PascalApi.Horse.OpenApi (src/horse) TRouteDoc: register and document a route in one call; /swagger UI and /swagger/doc.json, with the JWT/Bearer scheme taken from the auth middleware
PascalApi.Mcp MCP (2026-07-28): the documented routes as tools, and the JSON-RPC dispatcher
PascalApi.Horse.Mcp (src/horse) TMcpEndpoint.Register('/mcp', ...): the MCP endpoint; tool calls go through the API's own middlewares
PascalApi.Otlp OTLP/HTTP JSON for traces and TOtlpHttpExporter (an IPcSpanExporter for pascal-common-faa's PascalCommon.Tracing), configured by the standard OTEL_* variables; needs neither pascal-db-faa nor Horse (a desktop client can export)
PascalApi.Horse.Observability (src/horse) TMetricsMiddleware (OpenTelemetry HTTP server metrics), TMetricsEndpoint (/metrics, Prometheus text), THealthEndpoint (/health/live, /health/ready with your checks)
PascalApi.Tenancy multi-tenancy, opt-in (guide): the tenant catalog (configuration, or a control database with a TTL cache on pascal-common-faa's IPcCache: its own in memory, or the application's, e.g. in Redis through pascal-redis-faa's TRedisCacheStore, where an Invalidate reaches every instance), ITenantDataResolver (a database per tenant or a shared one, pools named by mode, idle databases dropped), TTenantFilter.Prepare (the TENANT_ID condition and its parameter in one call), the 401/403 refusals, TTenantJwtKeyRing (a signing key ring per tenant), ValidTenantId and the tenant from a path prefix (one 404 for an unknown, suspended or impossible name). Design: docs/multitenancy-design.md
PascalApi.Horse.Tenancy (src/horse) TTenantMiddleware: the tenant from the token's tenant_id claim, checked against the catalog, after TJwtMiddleware; TPathTenantMiddleware: the tenant from /public/<tenant>/... on routes without a token; TTenantMiddleware.Current(AReq) in a handler; TenantIdOf for tenant.id in the access log and spans
PascalApi.Version PASCALAPI_VERSION, for compile-time checks

A quick look

pascal
TErrorHandlerMiddleware.Register(LOnError);            // {"error": ...} + status for any exception
THorse.Use(TLoggerMiddleware.New);                      // one access line per request; X-Request-Id = W3C trace id
THorse.Use(TMetricsMiddleware.New);                     // http.server.request.duration, .active_requests
THorse.Use(TCorsMiddleware.New('https://app.example.com'));
THorse.Use(TJwtMiddleware.New(TAppConfig.Get('JWT_SECRET'), ['/health', '/metrics', '/auth/login']));
THorse.Use('/reports', TRateLimitMiddleware.New(60, 60));

THorse.Get('/cities', GetCities);   // raise ENotFoundException / EValidationException freely;
                                    // answer with TJsonSend.Send(Res, Json) (UTF-8 on both compilers)
TMetricsEndpoint.Register('/metrics');                  // Prometheus scrape
THealthEndpoint.AddCheck('database', LChecks.Database); // a function: Boolean (method on FPC)
THealthEndpoint.Register('/health');                    // /health/live, /health/ready (200 or 503)
TPcTracing.Start(TPcTracingOptions.FromEnvironment('cities-api', '1.0.0', ReadAppConfig),
  TOtlpHttpExporter.FromEnvironment);                   // spans to an OpenTelemetry collector
THorse.Listen(9000);

Documented routes, Swagger UI and MCP tools come from the same declarations:

pascal
TRouteDoc.Get('/cities').Summary('List cities').Tag('cities')
  .QueryParams<ICityFind>.ResponsePaged<ICity>(200).Register(GetCities);
TRouteDoc.Serve('/swagger', 'Cities API', '1.0.0');
TMcpEndpoint.Register('/mcp', 'http://127.0.0.1:9000', 'cities-api', '1.0.0');  // after every route

An MCP client (protocol 2026-07-28) then lists list_citie with a JSON Schema of its arguments (state, page, limit, orderBy...) and calls it: the call is a GET /cities?state=SC to the API itself, with the client's token, through every middleware.

On FPC a Horse callback is a plain procedure, so each middleware keeps its settings in the unit: one configuration per process.

Messaging, Redis and IPC

Not in this library: use the sibling libraries, same author and dual-compiler rules, sharing pascal-common-faa with this one. They are the first choice for an API built on pascal-api-infra-faa.

Need Library Units
Message broker (RabbitMQ / AMQP 0-9-1), or a broker embedded in the program pascal-amqp-faa AMQP.*
Redis: cache, locks, counters, Pub/Sub, Streams pascal-redis-faa Redis.*
Inter-process: Named Pipe / Unix socket, TCP, TLS pascal-pipes-faa Pipes.*

How to add them to an application, minimum versions and short examples: docs/related-libraries.md; using them inside an API (one client per process, spans, a server that is down): docs/guide/messaging.md.

Samples

Sample Shows Checked by
01-api every middleware (errors, CORS, log, metrics, JWT, rate limit), health, paging and validation, refresh tokens, in memory tools/http_scenarios.sh (260 checks)
02-db SQLite through pascal-db-faa (SQLdb on FPC, FireDAC on Delphi): migrations, paging and ordering in SQL, filters, 409 from a unique key, NULL as null tools/http_scenarios_db.sh (95 checks, including a PATCH with absent/null/value; also the pool metrics pascal-db-faa records)
03-tenants multi-tenant with PascalApi.Tenancy: the tenant from a JWT claim, a database per tenant and a shared one with TENANT_ID, 401/403 for a removed or suspended tenant; JWT signing keys static (JWT_KEYS) or generated per tenant, sealed, in the control database or Redis (JWT_KEY_STORE); refresh tokens per tenant in the control database; the catalog cached in memory or in Redis (CATALOG_CACHE) tools/http_scenarios_tenants.sh (183 checks, the public route by path among them; most of them one tenant trying another's rows, also through a JOIN; also rate limit by plan, tenant.id in the log, the static keys, refresh tokens under another tenant's prefix); tools/http_scenarios_jwt_rotation.sh (two instances on one key store, rotating every 3 s, and a refresh token issued by one and rotated by the other: 36 checks on the database, 30 on Redis)
04-messaging Redis (a shared TRedisClient, cache-aside, a fallback when Redis is down) and RabbitMQ (JSON orders published with and without confirms, a consumer that rejects what it can't read) inside an API, with spans; PipesJsonSample: JSON DTOs between processes over pascal-pipes-faa. FPC only, built by hand (build_fpc.sh) against your clones of the sibling libraries not by CI: measured by hand for docs/guide/messaging.md and docs/guide/dtos-json.md

Samples 01-03 run with one source on Delphi and Lazarus/FPC. On FPC for Windows, samples 02 and 03 need sqlite.org's sqlite3.dll next to the executable (see tools/test_http.sh).

Using it

Add the library's src and its dependencies to the project, each from your single copy (git submodules of your project), never from this repository's external/:

  • Delphi: search path with src, pascal-common-faa/src, pascal-common-faa/bridges/jsonmapper, pascal-jsonmapper-faa/src, pascal-db-faa/src; for the middlewares also src/horse and Horse's src.
  • Lazarus: require packages/pascal_api_infra_faa.lpk (it requires pascal_common_faa, pascaljsonmapper_pkg and pascal_db_faa); for DTO JSON, also pascal_common_faa_jsonmapper. The middlewares are not in the package (Horse has none): add src/horse and Horse's src to the project's search path; for the Redis key store, src/redis and your pascal-redis-faa. pascal-common-faa 1.29.0 or later (the package compiles PascalApi.Tenancy, whose catalog cache is PascalCommon.Cache; on Delphi, without PascalApi.Tenancy, 1.26.0 is enough). Use Horse 3.3.3 or later: 3.3.2 doesn't compile on FPC 3.2.2 for Windows (fixed upstream in 3.3.3). Tested with 3.3.12.
  • A program that talks to an API and has no database (a desktop client): on Lazarus require packages/pascal_api_infra_faa_client.lpk instead (0.31.0; it requires pascal_common_faa and pascaljsonmapper_pkg only): PascalApi.Dto, OpenApi, OrderBy, Text, Config, HttpTypes, HttpTransport (and its fake), Otlp, Version. It is a subset of pascal_api_infra_faa: a project requires one of the two, never both. On Delphi, the search path without pascal-db-faa/src. tests/Deps/ClientNoDb is the check.

Console and service programs on FPC call SetMultiByteConversionCodePage(CP_UTF8) at startup (LCL programs already run in UTF-8); on Unix, put cthreads and cwstring first in the program's uses.

Tests

pascal
git submodule update --init
sh tools/test_fpc.sh            # FPC on Windows (lazbuild)
sh tools/test_fpc_docker.sh     # FPC 3.2.2 on Linux, in Docker
sh tools/test_http.sh           # the samples over HTTP (FPC on Windows)
sh tools/test_http_docker.sh    # the same on Linux, in Docker
sh tools/ci-test.sh             # what CI runs

On Delphi, open PascalApi.groupproj and run PascalApi.UnitTests (Win32 and Win64; "Build All" builds only the active platform). Every suite must end with 0 leaks.

License

MIT. See LICENSE.