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 classesInstantObjects.MARS.Server.Resources- Generic CRUD resourcesInstantObjects.MARS.Server.Exceptions- Exception handlingInstantObjects.MARS.Data- Database connection injectionInstantObjects.MARS.InjectionService- Dependency injection serviceInstantObjects.Neon.MessageBodyReaders- JSON deserializationInstantObjects.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
| Property | Type | Description |
|---|---|---|
| URL | TMARSURL | Request URL information |
| Request | IMARSRequest | HTTP request interface |
| Response | IMARSResponse | HTTP response interface |
| Token | TMARSToken | JWT authentication token |
| Activation | IMARSActivation | MARS activation context |
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;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')]
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')]
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;
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')]
function RetrieveModel: string; // Returns complete model XML
[GET, Path('/{AClassName}')]
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}')]
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}')]
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:
42TMARSInstantObjects
Dependency injection class providing database access within MARS resources.
Usage:
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
| 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'.
EMARSServerException
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 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
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
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:
// Register generic resources in engine configuration
LApp := FEngine.AddApplication('app', '/app', [
'InstantObjects.MARS.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: 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
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:
{
"errorCode": "404",
"errorDescription": "User USER001 not found"
}Configuration
FireDAC Connection
Configure FireDAC connections in configuration file:
// 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: productionRole-Based Access
[Path('/admin')]
[RolesAllowed('admin')] // Requires 'admin' role
TAdminResource = class(TBaseResource)
public
[GET, Path('/users')]
function GetAllUsers: TArray<TUser>;
end;Best Practices
- Use Generic Resources - Leverage
TInstantObjectResourcefor standard CRUD - Validate Inputs - Always use
CheckRequired*methods for validation - Handle Exceptions - Use
EMARSServerExceptionwith appropriate HTTP status codes - Secure Endpoints - Apply
RolesAllowedattribute for protected resources - Use Dependency Injection - Inject
TMARSInstantObjectsfor database access - Return Appropriate Status Codes - Use correct HTTP status codes for different scenarios
- Document Your API - Use MARS metadata attributes for OpenAPI documentation
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;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
- MARS REST Server Integration - Complete tutorial
- JSON Serialization with Neon - JSON serialization details
- Instant.Neon.Serializers - Neon serialization reference
- WiRL REST Server - Alternative REST framework
- MARS Curiosity GitHub - MARS framework documentation
Source Code
Files: Multiple units in Source/MARSServer/Location: Source/MARSServer/
Main Units:
InstantObjects.MARS.Server.Resources.Base.pasInstantObjects.MARS.Server.Resources.pasInstantObjects.MARS.Server.Exceptions.pasInstantObjects.MARS.Data.pasInstantObjects.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
