Skip to content

InstantObjects.WiRL.Server ​

WiRL REST framework integration for InstantObjects.

Overview ​

The InstantObjects.WiRL.Server package provides comprehensive integration with the WiRL REST framework, enabling you to expose InstantObjects as RESTful web services with automatic JSON serialization, authentication, and OpenAPI documentation.

Key Units:

  • InstantObjects.WiRL.Server.Resources.Base - Base resource classes
  • InstantObjects.WiRL.Server.Resources - Generic CRUD resources
  • InstantObjects.WiRL.Server.Exceptions - Exception handling
  • InstantObjects.WiRL.Data - Database connection injection
  • InstantObjects.Neon.MessageBodyProvider - JSON serialization provider

Key Features:

  • Generic CRUD resources for all InstantObject classes
  • Automatic JSON serialization via Neon
  • JWT authentication and authorization
  • OpenAPI/Swagger documentation support
  • Flexible query support (WHERE, ORDER BY, pagination)
  • Structured exception handling
  • Dependency injection for database access

Key Classes ​

TBaseResource ​

Base class for all WiRL Server resources providing common functionality.

Inheritance: TBaseResource (abstract)

Context Properties ​

PropertyTypeDescription
URLTWiRLURLRequest URL information
RequestTWiRLRequestHTTP request interface
ResponseTWiRLResponseHTTP response interface

Validation Methods ​

pascal
// Validate required string field
procedure CheckRequiredField(const AFieldName: string; out AValue: string);

// Validate required numeric field
procedure CheckRequiredNumericField(const AFieldName: string; out AValue: Double);

// Validate required integer field
procedure CheckRequiredIntegerField(const AFieldName: string; out AValue: Integer);

// Validate multiple required fields
procedure CheckRequiredFields(const AFieldNames: TArray<string>);

// Validate string length
procedure CheckStringMaxLength(const AFieldName, AValue: string;
  const AMaxLength: Integer);

// Validate IP address format
procedure CheckIPAddress(const AFieldName, AValue: string);

Utility Methods ​

pascal
// Get client IP address
function GetClientIPAddress: string;

// Check if debug output enabled
function DebugOutput: Boolean;

// Get InstantObject class by name
function GetInstantObjectClass(const AClassName: string): TInstantObjectClass;

// Add transient object for automatic cleanup
function AddTransientObject<T: class>(const AObject: T): T;

// Filter accepted class names (override to restrict access)
procedure AcceptedClassName(const AClassName: string; var AAccepted: boolean);

TInstantObjectResource ​

Generic resource providing CRUD operations for any InstantObject class.

Inheritance: TBaseResource → TInstantObjectResource

Path: /data

Produces: application/json

Methods ​

GET /data/{AClassName}/ ​

Retrieve a single object by class name and ID.

pascal
[GET, Path('/{AClassName}/{AId}')]
[RolesAllowed('reader')]
[Produces('application/json')]
function RetrieveInstantObject(
  [PathParam] AClassName: string;
  [PathParam] AId: string): TInstantObject;

Example:

GET http://localhost:8080/rest/app/data/TUser/USER001
Authorization: Bearer {token}
GET /data/ ​

Retrieve list of objects with optional filtering and pagination.

pascal
[GET, Path('/{AClassName}')]
[RolesAllowed('reader')]
[Produces('application/json')]
function RetrieveInstantObjectList(
  [PathParam] AClassName: string;
  [QueryParam] Where: string = '';
  [QueryParam] OrderBy: string = '';
  [QueryParam] ChangedFrom: string = '';
  [QueryParam] ChangedTo: string = '';
  [QueryParam] PageCount: integer = 0;
  [QueryParam] RecordCount: integer = 0): TInstantObjectList<TInstantObject>;

Query Parameters:

  • Where - IQL WHERE clause (e.g., Name = 'John')
  • OrderBy - IQL ORDER BY clause (e.g., Name ASC)
  • ChangedFrom - Filter by update date from
  • ChangedTo - Filter by update date to
  • PageCount - Page number (0-based)
  • RecordCount - Records per page

Example:

GET http://localhost:8080/rest/app/data/TUser?Where=Active=True&OrderBy=Name
Authorization: Bearer {token}
POST /data/{AClassName}/ ​

Create a new object.

pascal
[POST, Path('/{AClassName}/{AId}')]
[Consumes('application/json')]
[Produces('application/json')]
[RolesAllowed('standard')]
function Post(
  [PathParam] const AId: string;
  [BodyParam] AObject: TInstantObject;
  [QueryParam] const ACheckIfExists: boolean = True): TInstantObject;

Example:

bash
POST http://localhost:8080/rest/app/data/TUser/USER001
Authorization: Bearer {token}
Content-Type: application/json

{
  "email": "john@example.com",
  "firstName": "John",
  "lastName": "Doe"
}
PUT /data/{AClassName}/ ​

Update an existing object.

pascal
[PUT, Path('/{AClassName}/{AId}')]
[Consumes('application/json')]
[Produces('application/json')]
[RolesAllowed('standard')]
function Put(
  [PathParam] const AId: string;
  [BodyParam] AObject: TInstantObject): TInstantObject;
DELETE /data/{AClassName}/ ​

Delete an object.

pascal
[DELETE, Path('/{AClassName}/{AId}')]
[Produces('application/json')]
[RolesAllowed('standard')]
function Delete(
  [PathParam] AClassName: string;
  [PathParam] AId: string): TInstantObject;

TInstantObjectMetadataResource ​

Resource providing class metadata information.

Path: /classmetadata

Produces: text/plain

Methods ​

pascal
[GET, Path('/Model')]
[Produces('application/json')]
function RetrieveModel: string;  // Returns complete model XML

[GET, Path('/{AClassName}')]
[Produces('application/json')]
function RetrieveMetadata([PathParam] AClassName: string): string;  // Returns class metadata XML

Example:

GET http://localhost:8080/rest/app/classmetadata/TUser

TInstantObjectReferencesResource ​

Resource returning object references (keys only, not full objects).

Path: /keys

Produces: application/json

Methods ​

pascal
[GET, Path('/{AClassName}')]
[Produces('application/json')]
function RetrieveInstantObjectReferenceList(
  [PathParam] AClassName: string;
  [QueryParam] Where: string = '';
  [QueryParam] OrderBy: string = '';
  [QueryParam] ChangedFrom: string = '';
  [QueryParam] ChangedTo: string = '';
  [QueryParam] PageCount: integer = 0;
  [QueryParam] RecordCount: integer = 0): TList<TInstantObjectReference>;

Use Case: Efficient retrieval of object IDs for dropdown lists or autocomplete.

Example:

GET http://localhost:8080/rest/app/keys/TUser?OrderBy=Name

TInstantObjectCountResource ​

Resource returning object counts.

Path: /count

Produces: application/json

Methods ​

pascal
[GET, Path('/{AClassName}')]
[Produces('application/json')]
function GetInstantObjectReferenceCount(
  [PathParam] AClassName: string;
  [QueryParam] Where: string = '';
  [QueryParam] OrderBy: string = '';
  [QueryParam] ChangedFrom: string = '';
  [QueryParam] ChangedTo: string = ''): Integer;

Example:

GET http://localhost:8080/rest/app/count/TUser?Where=Active=True

Returns:

json
42

TWiRLInstantObjects ​

Dependency injection class providing database access within WiRL resources.

Usage:

pascal
type
  TMyResource = class(TBaseResource)
  protected
    [Context] InstantObject: TWiRLInstantObjects;
  public
    function GetData: TInstantObject;
  end;

implementation

function TMyResource.GetData: TInstantObject;
begin
  // Access connector
  Result := TUser.Retrieve('USER001', False, False,
    InstantObject.Connector);
end;

Properties ​

PropertyTypeDescription
ConnectionNamestringFireDAC connection name (from 'environment' header)
ConnectorTInstantFireDACConnectorInstantObjects database connector

Environment Selection:

The connection is selected from HTTP header:

environment: production

If not specified, defaults to 'test'.

EWiRLServerException ​

Structured exception class with HTTP status codes.

HTTP Status Codes ​

pascal
type
  THttpStatusCode = (
    http_200_OK,
    http_201_Created,
    http_400_Bad_Request,
    http_403_Forbidden,
    http_401_Unauthorized,
    http_404_NotFound,
    http_406_NotAcceptable,
    http_500_InternalServerError
  );

Methods ​

pascal
// Create error with status code
constructor CreateError(
  const AErrorCode: THttpStatusCode;
  const ADescription: string;
  const AFieldName: string = '');

// Get JSON error response
function GetErrorJSONContent: string;

// Compose error JSON
class function ComposeErrorJSONContent(
  const AErrorCode: THttpStatusCode;
  const AErrorMsg: string;
  const AFieldName: string = ''): string;

Error Response Format ​

json
{
  "errorCode": "404",
  "attributeName": "email",
  "errorDescription": "User not found"
}

Usage Patterns ​

Basic Server Setup ​

pascal
program MyWiRLServer;

uses
  WiRL.http.Server,
  WiRL.http.Server.Indy,
  Server.Ignition;

begin
  Server := TWiRLServer.Create(nil);
  try
    Server.Port := 8080;
    Server.ThreadPoolSize := 50;

    // Configure engine
    TServerEngine.CreateEngine(Server);

    Server.Active := True;

    WriteLn('Server running on http://localhost:8080/rest');
    ReadLn;

    Server.Active := False;
  finally
    Server.Free;
  end;
end.

Engine Configuration ​

pascal
unit Server.Ignition;

interface

uses
  WiRL.Core.Engine,
  WiRL.http.Server;

type
  TServerEngine = class
  public
    class procedure CreateEngine(AServer: TWiRLServer);
  end;

implementation

uses
  WiRL.Core.Application,
  WiRL.Configuration.Neon,
  InstantObjects.Neon.MessageBodyProvider,
  InstantObjects.WiRL.Data;

class procedure TServerEngine.CreateEngine(AServer: TWiRLServer);
var
  LEngine: TWiRLEngine;
  LApp: TWiRLApplication;
begin
  LEngine := TWiRLEngine.Create(AServer);
  LEngine.BasePath := '/rest';

  // Add application
  LApp := LEngine.AddApplication('/app');

  // Register resources
  LApp.Resources.Add('InstantObjects.WiRL.Server.Resources.*');
  LApp.Resources.Add('MyApp.WiRL.Server.Resources.*');  // Your resources

  // Configure Neon for JSON serialization
  LApp.Plugin.Configure<IWiRLConfigurationNeon>
    .SetUseUTCDate(True)
    .SetMemberCase(TNeonCase.CamelCase)
    .SetVisibility([mvPublic, mvPublished])
    .BackToConfig;

  // Register InstantObjects message body provider
  LApp.WriterRegistry.RegisterWriter<TInstantObject>(
    TInstantObjectNeonProvider.GetWriterInstance);

  LApp.ReaderRegistry.RegisterReader<TInstantObject>(
    TInstantObjectNeonProvider.GetReaderInstance);
end;

end.

Custom Resource with Validation ​

pascal
unit MyApp.WiRL.Server.Resources.User;

interface

uses
  WiRL.Core.Attributes,
  WiRL.http.Accept.MediaType,
  InstantObjects.WiRL.Server.Resources.Base,
  InstantObjects.WiRL.Data,
  InstantPersistence,
  Model;

type
  [Path('/user')]
  TUserResource = class(TBaseResource)
  protected
    [Context] InstantObject: TWiRLInstantObjects;
  public
    [POST, Path('/register')]
    [Consumes(TMediaType.APPLICATION_JSON)]
    [Produces(TMediaType.APPLICATION_JSON)]
    function RegisterUser([BodyParam] AUser: TUser): TUser;
  end;

implementation

uses
  System.SysUtils,
  InstantObjects.WiRL.Server.Exceptions;

function TUserResource.RegisterUser(AUser: TUser): TUser;
begin
  // Validate email
  if AUser.Email = '' then
    raise EWiRLServerException.CreateError(
      http_400_Bad_Request,
      'Email is required',
      'email');

  // Check email length
  CheckStringMaxLength('email', AUser.Email, 100);

  // Check if user already exists
  if TUser.Retrieve(AUser.Id) <> nil then
    raise EWiRLServerException.CreateError(
      http_400_Bad_Request,
      'User already exists');

  // Store user
  AUser.Store;
  Result := AUser;
end;

end.

Using Generic Resources ​

You can use the built-in TInstantObjectResource for standard CRUD without custom code:

pascal
// Register generic resources in engine configuration
LApp.Resources.Add('InstantObjects.WiRL.Server.Resources.*');

This automatically provides:

  • GET /data/TUser/USER001 - Retrieve user
  • GET /data/TUser?Where=Active=True - List users
  • POST /data/TUser/USER001 - Create user
  • PUT /data/TUser/USER001 - Update user
  • DELETE /data/TUser/USER001 - Delete user

JWT Authentication ​

pascal
type
  [Path('/token')]
  TTokenResource = class(TBaseResource)
  protected
    [Context] InstantObject: TWiRLInstantObjects;
  public
    [POST, Path('/login')]
    [Consumes(TMediaType.APPLICATION_FORM_URLENCODED)]
    [Produces(TMediaType.APPLICATION_JSON)]
    function Login(
      [FormParam('username')] const AUsername: string;
      [FormParam('password')] const APassword: string): TJSONObject;
  end;

implementation

uses
  WiRL.Core.Auth.Context,
  WiRL.http.JWT;

function TTokenResource.Login(
  const AUsername, APassword: string): TJSONObject;
var
  User: TUser;
  Token: string;
  LClaims: TJWTClaims;
begin
  User := ValidateCredentials(AUsername, APassword);
  if not Assigned(User) then
    raise EWiRLServerException.CreateError(
      http_401_Unauthorized,
      'Invalid credentials');

  try
    // Create JWT claims
    LClaims := TJWTClaims.Create;
    try
      LClaims.Subject := AUsername;
      LClaims.Roles := 'reader,standard';
      LClaims.Expiration := Now + (1/24);  // 1 hour

      // Generate token
      Token := TJWT.Build(LClaims, 'SECRET_KEY');
    finally
      LClaims.Free;
    end;

    Result := TJSONObject.Create;
    Result.AddPair('token', Token);
    Result.AddPair('expires_in', TJSONNumber.Create(3600));
  finally
    User.Free;
  end;
end;

Exception Handling ​

pascal
function TUserResource.GetUser(const AId: string): TUser;
begin
  Result := TUser.Retrieve(AId);
  if not Assigned(Result) then
    raise EWiRLServerException.CreateError(
      http_404_NotFound,
      Format('User %s not found', [AId]));
end;

Client receives:

json
{
  "errorCode": "404",
  "errorDescription": "User USER001 not found"
}

Restricting Accepted Classes ​

Override AcceptedClassName to restrict which classes can be accessed:

pascal
type
  TSecureResource = class(TInstantObjectResource)
  protected
    procedure AcceptedClassName(const AClassName: string;
      var AAccepted: boolean); override;
  end;

implementation

procedure TSecureResource.AcceptedClassName(const AClassName: string;
  var AAccepted: boolean);
begin
  // Only allow TUser and TContact
  AAccepted := (AClassName = 'TUser') or (AClassName = 'TContact');
end;

Configuration ​

FireDAC Connection ​

Configure FireDAC connections in configuration file or code:

pascal
// Define connection in code
var
  LConnectionDef: IFDStanConnectionDef;
begin
  LConnectionDef := FDManager.ConnectionDefs.AddConnectionDef;
  LConnectionDef.Name := 'test';
  LConnectionDef.Params.Values['DriverID'] := 'FB';
  LConnectionDef.Params.Values['Server'] := 'localhost';
  LConnectionDef.Params.Values['Database'] := 'test.fdb';
end;

Select environment via HTTP header:

environment: production

Role-Based Access ​

pascal
[Path('/admin')]
[RolesAllowed('admin')]  // Requires 'admin' role
TAdminResource = class(TBaseResource)
public
  [GET, Path('/users')]
  [Produces(TMediaType.APPLICATION_JSON)]
  function GetAllUsers: TArray<TUser>;
end;

CORS Configuration ​

pascal
uses
  WiRL.http.Filters.CORS;

// Add CORS filter to application
LApp.Filters.Add(TCORSFilter);

// Configure in filter
TCORSFilter.AllowedOrigins := '*';
TCORSFilter.AllowedMethods := 'GET,POST,PUT,DELETE,OPTIONS';
TCORSFilter.AllowedHeaders := 'Content-Type,Authorization';

Best Practices ​

  1. Use Generic Resources - Leverage TInstantObjectResource for standard CRUD
  2. Validate Inputs - Always use CheckRequired* methods for validation
  3. Handle Exceptions - Use EWiRLServerException with appropriate HTTP status codes
  4. Secure Endpoints - Apply RolesAllowed attribute for protected resources
  5. Use Dependency Injection - Inject TWiRLInstantObjects for database access
  6. Return Appropriate Status Codes - Use correct HTTP status codes for different scenarios
  7. Implement AcceptedClassName - Restrict class access when needed for security

Common Patterns ​

Pagination ​

pascal
function TUserResource.GetUsersPaged(
  PageCount, RecordCount: Integer): TArray<TUser>;
var
  List: TInstantObjectList<TInstantObject>;
begin
  List := RetrieveInstantObjectList('TUser', '', 'Name', '', '',
    PageCount, RecordCount);
  try
    // Convert to array
  finally
    List.Free;
  end;
end;

Filtering with IQL ​

pascal
// Filter active users by name
GET /data/TUser?Where=Active=True AND Name LIKE 'John%'&OrderBy=Name

Custom Queries ​

pascal
function TUserResource.GetActiveUsers: TArray<TUser>;
var
  Selector: TInstantSelector;
  List: TObjectList<TUser>;
begin
  Selector := TInstantSelector.Create(nil);
  try
    Selector.Command.Text :=
      'SELECT * FROM TUser WHERE Active = True ORDER BY Name';
    Selector.Open;

    List := TObjectList<TUser>.Create(False);
    while not Selector.EOF do
    begin
      List.Add(Selector.CurrentObject as TUser);
      Selector.Next;
    end;

    Result := List.ToArray;
  finally
    Selector.Free;
  end;
end;

Dependency Injection Factory ​

The TInstantObjectInjectionFactory class handles automatic injection of TWiRLInstantObjects into resources:

pascal
type
  TInstantObjectInjectionFactory = class(TInterfacedObject, IContextHttpFactory)
  public
    function CreateContextObject(const AObject: TRttiObject;
      AContext: TWiRLContextHttp): TValue;
  end;

Registered automatically in the unit initialization:

pascal
initialization
  TWiRLContextInjectionRegistry.Instance.RegisterFactory<TWiRLInstantObjects>(
    TInstantObjectInjectionFactory);

Complete Example ​

See WiRL REST Server Integration for a complete tutorial including:

  • Server setup and configuration
  • Custom resource implementation
  • JWT authentication
  • Neon JSON configuration
  • Client usage examples

See Also ​

Source Code ​

Files: Multiple units in Source/WiRLServer/Location: Source/WiRLServer/

Main Units:

  • InstantObjects.WiRL.Server.Resources.Base.pas
  • InstantObjects.WiRL.Server.Resources.pas
  • InstantObjects.WiRL.Server.Exceptions.pas
  • InstantObjects.WiRL.Data.pas
  • InstantObjects.Neon.MessageBodyProvider.pas

Summary ​

InstantObjects.WiRL.Server provides comprehensive WiRL REST framework integration.

Key Features:

  • Generic CRUD resources
  • Automatic JSON serialization
  • JWT authentication
  • Exception handling with HTTP status codes
  • Dependency injection
  • Class name filtering

Best for:

  • RESTful web services
  • Modern web applications
  • Mobile app backends
  • Microservices architecture

Released under Mozilla License, Version 2.0.