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: TBaseResourceTInstantObjectResource

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.