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 classesInstantObjects.WiRL.Server.Resources- Generic CRUD resourcesInstantObjects.WiRL.Server.Exceptions- Exception handlingInstantObjects.WiRL.Data- Database connection injectionInstantObjects.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
| Property | Type | Description |
|---|---|---|
| URL | TWiRLURL | Request URL information |
| Request | TWiRLRequest | HTTP request interface |
| Response | TWiRLResponse | HTTP response interface |
Validation Methods
// 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
// 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.
[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.
[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 fromChangedTo- Filter by update date toPageCount- 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.
[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:
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.
[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.
[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
[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 XMLExample:
GET http://localhost:8080/rest/app/classmetadata/TUserTInstantObjectReferencesResource
Resource returning object references (keys only, not full objects).
Path: /keys
Produces: application/json
Methods
[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=NameTInstantObjectCountResource
Resource returning object counts.
Path: /count
Produces: application/json
Methods
[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=TrueReturns:
42TWiRLInstantObjects
Dependency injection class providing database access within WiRL resources.
Usage:
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
| Property | Type | Description |
|---|---|---|
| ConnectionName | string | FireDAC connection name (from 'environment' header) |
| Connector | TInstantFireDACConnector | InstantObjects database connector |
Environment Selection:
The connection is selected from HTTP header:
environment: productionIf not specified, defaults to 'test'.
EWiRLServerException
Structured exception class with HTTP status codes.
HTTP Status Codes
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
// 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
{
"errorCode": "404",
"attributeName": "email",
"errorDescription": "User not found"
}Usage Patterns
Basic Server Setup
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
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
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:
// Register generic resources in engine configuration
LApp.Resources.Add('InstantObjects.WiRL.Server.Resources.*');This automatically provides:
GET /data/TUser/USER001- Retrieve userGET /data/TUser?Where=Active=True- List usersPOST /data/TUser/USER001- Create userPUT /data/TUser/USER001- Update userDELETE /data/TUser/USER001- Delete user
JWT Authentication
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
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:
{
"errorCode": "404",
"errorDescription": "User USER001 not found"
}Restricting Accepted Classes
Override AcceptedClassName to restrict which classes can be accessed:
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:
// 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: productionRole-Based Access
[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
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
- Use Generic Resources - Leverage
TInstantObjectResourcefor standard CRUD - Validate Inputs - Always use
CheckRequired*methods for validation - Handle Exceptions - Use
EWiRLServerExceptionwith appropriate HTTP status codes - Secure Endpoints - Apply
RolesAllowedattribute for protected resources - Use Dependency Injection - Inject
TWiRLInstantObjectsfor database access - Return Appropriate Status Codes - Use correct HTTP status codes for different scenarios
- Implement AcceptedClassName - Restrict class access when needed for security
Common Patterns
Pagination
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
// Filter active users by name
GET /data/TUser?Where=Active=True AND Name LIKE 'John%'&OrderBy=NameCustom Queries
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:
type
TInstantObjectInjectionFactory = class(TInterfacedObject, IContextHttpFactory)
public
function CreateContextObject(const AObject: TRttiObject;
AContext: TWiRLContextHttp): TValue;
end;Registered automatically in the unit initialization:
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
- WiRL REST Server Integration - Complete tutorial
- JSON Serialization with Neon - JSON serialization details
- Instant.Neon.Serializers - Neon serialization reference
- MARS REST Server - Alternative REST framework
- WiRL Documentation - WiRL framework documentation
Source Code
Files: Multiple units in Source/WiRLServer/Location: Source/WiRLServer/
Main Units:
InstantObjects.WiRL.Server.Resources.Base.pasInstantObjects.WiRL.Server.Resources.pasInstantObjects.WiRL.Server.Exceptions.pasInstantObjects.WiRL.Data.pasInstantObjects.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
