OpenAPI Server for Delphi: TsgcWSAPIServer_OpenAPI

· Components

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:

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:

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.