The sgcWebSockets Enterprise edition ships a new component, TsgcWSAPIServer_OpenAPI, that turns an OpenAPI 3 description into a running REST server inside your Delphi application. Drop it on a form, point it at an HTTP server, hand it a spec — and the routes, request validation, error responses and live Swagger UI documentation are wired up for you. This post walks through how the component works, the two ways you can drive it (spec-first and code-first), the configuration knobs that matter, and a complete Delphi example you can paste straight into a new project.
What the component does
TsgcWSAPIServer_OpenAPI is a thin API server that plugs into TsgcWebSocketHTTPServer through the existing API-server extension point. You attach it to a server, load an OpenAPI 3.0 specification, and it does four things on every incoming HTTP request:
- matches the URL and HTTP method against the routes declared in the spec (including
{path}parameters); - validates the request — required fields, query parameters, path parameters and request body — against the schemas in the spec;
- fires an
OnRequestevent with the resolvedoperationIdand a fully populated context object, so your code only has to write business logic; - serves two built-in endpoints out of the box — the raw spec at
/openapi.jsonand an interactive Swagger UI at/docs— with optional CORS preflight on every route.
The result is that the spec becomes the single source of truth: change a path, a parameter or a response code in the JSON, restart, and the server picks up the new contract without recompiling Delphi code.
Spec-first: load an existing OpenAPI 3 file
If you already have an OpenAPI 3 document (for example a petstore.json exported from an API designer) the wiring is essentially three lines — create the component, load the spec, attach a server. Everything else is configuration and the OnRequest handler that produces the actual responses.
uses
sgcWebSocket, sgcWebSocket_Classes,
sgcWebSocket_Server_API_OpenAPI,
sgcHTTP_OpenAPI_Server;
var
WSServer: TsgcWebSocketHTTPServer;
FOpenAPI: TsgcWSAPIServer_OpenAPI;
begin
WSServer := TsgcWebSocketHTTPServer.Create(nil);
WSServer.Port := 8080;
FOpenAPI := TsgcWSAPIServer_OpenAPI.Create(nil);
FOpenAPI.OnRequest := OnOpenAPIRequest;
FOpenAPI.OnBeforeRequest := OnOpenAPIBeforeRequest;
FOpenAPI.OnValidationError := OnOpenAPIValidationError;
// Configuration
FOpenAPI.OpenAPIOptions.Endpoint.ServeSpec := True;
FOpenAPI.OpenAPIOptions.Endpoint.ServeSwaggerUI := True;
FOpenAPI.OpenAPIOptions.CORS.Enabled := True;
FOpenAPI.OpenAPIOptions.Validation.ValidateRequest := True;
FOpenAPI.OpenAPIOptions.Validation.ValidateRequired := True;
FOpenAPI.OpenAPIOptions.Validation.ValidateRequestBody := True;
// Load spec and attach to server
FOpenAPI.LoadFromFile('petstore.json');
FOpenAPI.Server := WSServer;
WSServer.Active := True;
// Swagger UI: http://localhost:8080/docs
// Raw spec: http://localhost:8080/openapi.json
end;
The OnRequest event is dispatched per operationId — the string you wrote next to each operation in the YAML/JSON. You write one branch per operation, read inputs from the context and emit a response:
procedure TForm1.OnOpenAPIRequest(Sender: TObject;
const aOperationId: string; const aContext: TsgcOpenAPIServerContext;
var Handled: Boolean);
var
vId: Int64;
vLimit: Integer;
begin
Handled := True;
if aOperationId = 'listPets' then
begin
vLimit := aContext.QueryParamAsInteger('limit', 100);
aContext.RespondJSON(200, BuildPetsJSON(vLimit));
end
else if aOperationId = 'getPetById' then
begin
vId := aContext.PathParamAsInteger('petId');
if FindPet(vId) then
aContext.RespondJSON(200, PetAsJSON(vId))
else
aContext.RespondError(404, 'Not Found',
Format('Pet %d not found', [vId]));
end
else
Handled := False;
end;
The TsgcOpenAPIServerContext object is the workhorse of every handler. It exposes path and query parameters by name (PathParamAsString, PathParamAsInteger, QueryParamAsString, QueryParamAsInteger, QueryParamAsBoolean), header lookup via HeaderValue, the body as a string (BodyAsString) or pre-parsed JSON (BodyAsJSON), plus two response helpers: RespondJSON(code, content) for a normal payload and RespondError(code, title, detail), which produces an RFC 7807-style application/problem+json body so clients always get a consistent error shape.
Code-first: generate the spec from Delphi attributes
The second mode is the reverse flow: you declare your API as a Delphi class decorated with attributes, ask the scanner to emit the OpenAPI document at runtime, and feed that document back into the same component. There is nothing to hand-write in JSON.
uses
sgcHTTP_OpenAPI_Server_CodeFirst;
type
[sgcServiceContract('Task Manager API',
'A simple task manager', '1.0.0')]
[sgcRoute('/api/v1')]
TTaskService = class
public
[sgcHttpGet]
[sgcRoute('/tasks')]
[sgcSummary('List all tasks')]
[sgcResponse(200, 'A list of tasks')]
procedure ListTasks([sgcFromQuery] const status: string); virtual;
[sgcHttpGet]
[sgcRoute('/tasks/{taskId}')]
[sgcSummary('Get a task by ID')]
[sgcResponse(200, 'The requested task')]
[sgcResponse(404, 'Task not found')]
procedure GetTask([sgcFromPath][sgcRequired]
const taskId: Integer); virtual;
[sgcHttpPost]
[sgcRoute('/tasks')]
[sgcSummary('Create a new task')]
[sgcResponse(201, 'Task created')]
procedure CreateTask([sgcFromBody] const body: string); virtual;
end;
The class itself does not need real bodies — the scanner reads it through RTTI, the actual logic still lives in your OnRequest handler. Generating the spec and starting the server is a few lines:
var
oScanner: TsgcOpenAPICodeFirstScanner;
vSpec: string;
begin
oScanner := TsgcOpenAPICodeFirstScanner.Create;
try
vSpec := oScanner.GenerateSpec(TTaskService);
finally
oScanner.Free;
end;
FOpenAPI.LoadFromString(vSpec);
FOpenAPI.Server := WSServer;
WSServer.Active := True;
end;
The attribute set covers the common cases: routing (sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcRoute), parameter binding (sgcFromPath, sgcFromQuery, sgcFromHeader, sgcFromBody), validation (sgcRequired, sgcMinLength, sgcMaxLength, sgcRange, sgcPattern) and documentation (sgcSummary, sgcDescription, sgcTag, sgcResponse).
Configuration in detail
OpenAPIOptions is grouped into three persistent sub-objects so you can see them all in the Object Inspector:
Endpoint—BasePathprepends a prefix to every route and to the built-in/openapi.jsonand/docsendpoints;SpecFilecan be used at design time as an alternative toLoadFromFile;ServeSpecandServeSwaggerUItoggle the two built-in endpoints (both on by default).Validation— the master switch isValidateRequest;ValidateRequired,ValidateQueryParams,ValidatePathParamsandValidateRequestBodylet you narrow what is checked. When validation fails,OnValidationErrorfires with the list of problems and aContinueflag — set it toFalseto reject the request automatically.CORS— setEnabled := Trueand the server answers preflightOPTIONSrequests for every route, usingAllowOrigins,AllowHeadersandAllowMethodsas the response policy.
Beyond OnRequest, four more events let you tap into the pipeline: OnBeforeRequest (set Accept := False to short-circuit, useful for rate-limiting or logging), OnAfterRequest (post-processing once you have produced a response), OnAuthenticate (set Authenticated := True after checking a token or session) and OnException (catch-all that lets you change the HTTP status before the framework writes the error body).
Swagger UI out of the box
With ServeSwaggerUI := True the server publishes a Swagger UI page at BasePath + '/docs' that loads the spec from BasePath + '/openapi.json'. Open the URL in a browser and you get the standard try-it-out experience, fed from your own running server — no separate documentation build, no static export. Combined with CORS, this is the fastest way to hand a backend to a frontend team or to a partner integrating against your API.
Getting it
The component is part of the Enterprise edition of sgcWebSockets, registered on the SGC OpenAPI palette page. Two complete demos — one spec-first using a Petstore JSON, one code-first using a task manager service — ship in Demos\23.OpenAPI. Download the latest build from the sgcWebSockets download page.
Questions, feedback or help wiring it into an existing project? Get in touch — you will get a reply from the people who wrote the code.
