Skip to content

InstantObjects.MARS.Server

MARS Curiosity REST framework integration for InstantObjects.

Overview

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

Key Units:

  • InstantObjects.MARS.Server.Resources.Base - Base resource classes
  • InstantObjects.MARS.Server.Resources - Generic CRUD resources
  • InstantObjects.MARS.Server.Exceptions - Exception handling
  • InstantObjects.MARS.Data - Database connection injection
  • InstantObjects.MARS.InjectionService - Dependency injection service
  • InstantObjects.Neon.MessageBodyReaders - JSON deserialization
  • InstantObjects.Neon.MessageBodyWriters - JSON serialization

Key Features:

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

Key Classes

TBaseResource

Base class for all MARS Server resources providing common functionality.

Inheritance: TBaseResource (abstract)

Context Properties

PropertyTypeDescription
URLTMARSURLRequest URL information
RequestIMARSRequestHTTP request interface
ResponseIMARSResponseHTTP response interface
TokenTMARSTokenJWT authentication token
ActivationIMARSActivationMARS activation context

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;

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')]
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')]
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;
  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')]
function RetrieveModel: string;  // Returns complete model XML

[GET, Path('/{AClassName}')]
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}')]
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}')]
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

TMARSInstantObjects

Dependency injection class providing database access within MARS resources.

Usage:

pascal
type
  TMyResource = class(TBaseResource)
  protected
    [Context] InstantObject: TMARSInstantObjects;
  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'.

EMARSServerException

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 MyMARSServer;

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

begin
  TMARSWebServer.CreateServer
    .SetPort(8080)
    .SetThreadPoolSize(50)
    .Start;

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

  TMARSWebServer.Default.Stop;
end.

Engine Configuration

pascal
unit Server.Ignition;

interface

uses
  MARS.Core.Engine;

type
  TServerEngine = class
  private
    class var FEngine: IMARSEngine;
  public
    class constructor CreateEngine;
    class property Default: IMARSEngine read FEngine;
  end;

implementation

uses
  MARS.Core.Application,
  InstantObjects.MARS.Data,
  InstantObjects.MARS.InjectionService,
  InstantObjects.Neon.MessageBodyReaders,
  InstantObjects.Neon.MessageBodyWriters;

class constructor TServerEngine.CreateEngine;
var
  LApp: TMARSApplication;
begin
  FEngine := TMARSEngine.Create;
  FEngine.BasePath := 'rest';

  // Add application
  LApp := FEngine.AddApplication('app', '/app', [
    'MyApp.MARS.Server.Resources.*'  // Your resources
  ]);

  // Register InstantObjects message body writers
  LApp.AddMessageBodyWriters([
    'InstantObjects.Neon.MessageBodyWriters.*'
  ]);

  // Register InstantObjects message body readers
  LApp.AddMessageBodyReaders([
    'InstantObjects.Neon.MessageBodyReaders.*'
  ]);

  // Register InstantObjects injection service
  LApp.RegisterInjectionService(TInstantObjectsInjectionService);
end;

end.

Custom Resource with Validation

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

interface

uses
  MARS.Core.Attributes,
  MARS.Core.MediaType,
  MARS.Core.Token,
  InstantObjects.MARS.Server.Resources.Base,
  InstantObjects.MARS.Data,
  InstantPersistence,
  Model;

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

implementation

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

function TUserResource.RegisterUser(AUser: TUser): TUser;
begin
  // Validate email
  if AUser.Email = '' then
    raise EMARSServerException.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 EMARSServerException.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 := FEngine.AddApplication('app', '/app', [
  'InstantObjects.MARS.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: TMARSInstantObjects;
  public
    [POST, Path('/login')]
    [Consumes(TMediaType.APPLICATION_FORM_URLENCODED)]
    function Login(
      [FormParam('username')] const AUsername: string;
      [FormParam('password')] const APassword: string): TJSONObject;
  end;

implementation

uses
  MARS.mORMotJWT.Token;

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

  try
    Token := TMARSToken.Build(
      AUsername,
      ['reader', 'standard'],  // Roles
      'SECRET_KEY',
      3600  // Expiration in seconds
    );

    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 EMARSServerException.CreateError(
      http_404_NotFound,
      Format('User %s not found', [AId]));
end;

Client receives:

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

Configuration

FireDAC Connection

Configure FireDAC connections in configuration file:

pascal
// Create connection definitions
TMARSFireDAC.CreateConnectionDef('test',
  'DriverID=FB;Server=localhost;Database=test.fdb');

TMARSFireDAC.CreateConnectionDef('production',
  'DriverID=FB;Server=dbserver;Database=production.fdb');

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')]
  function GetAllUsers: TArray<TUser>;
end;

Best Practices

  1. Use Generic Resources - Leverage TInstantObjectResource for standard CRUD
  2. Validate Inputs - Always use CheckRequired* methods for validation
  3. Handle Exceptions - Use EMARSServerException with appropriate HTTP status codes
  4. Secure Endpoints - Apply RolesAllowed attribute for protected resources
  5. Use Dependency Injection - Inject TMARSInstantObjects for database access
  6. Return Appropriate Status Codes - Use correct HTTP status codes for different scenarios
  7. Document Your API - Use MARS metadata attributes for OpenAPI documentation

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;

Complete Example

See MARS REST Server Integration for a complete tutorial including:

  • Server setup and configuration
  • Custom resource implementation
  • JWT authentication
  • OpenAPI documentation
  • Client usage examples

See Also

Source Code

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

Main Units:

  • InstantObjects.MARS.Server.Resources.Base.pas
  • InstantObjects.MARS.Server.Resources.pas
  • InstantObjects.MARS.Server.Exceptions.pas
  • InstantObjects.MARS.Data.pas
  • InstantObjects.MARS.InjectionService.pas

Summary

InstantObjects.MARS.Server provides comprehensive MARS Curiosity REST framework integration.

Key Features:

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

Best for:

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

Released under Mozilla License, Version 2.0.