Photo by Sebastian Sørensen from Pexels

Web APIs with mORMot

Stephan Bester
8 min readJul 1, 2019

--

Among the features of the mORMot framework for Delphi, there are several mechanisms for publishing a self-hosted HTTP service. If you inspect the source code samples and the documentation, you will see various approaches demonstrated, without necessarily realizing the strengths and weaknesses of each. Before attempting to build my own web API using mORMot, I decided to first familiarize myself with the options.

Broadly speaking, there are three ways to do this:

  • The REST ORM
  • Methods-based services
  • Interface-based services

The framework allows these approaches to be used in combination. In fact, based on the class hierarchy, this practice almost seems encouraged. Before I discuss each of the options, however, let me introduce the TSQLHttpServer class, which appears in all of them.

The SQL HTTP server

The TSQLHttpServer class is defined in the mORMotHttpServer unit and plays the role of HTTP server in all the scenarios covered in this article. Despite its name, this is true regardless of whether any actual SQL is involved (or any database, for that matter).

Creating an instance of this class will immediately set up the server, register URIs and start listening for incoming requests, e.g.

HttpServer := TSQLHttpServer.Create('8080', [ServerDB]);

Important: You may find that on modern versions of Windows, the program must be launched with admin rights in order for URI registration to work.

The first parameter, aPort, is a string representing the port to which you wish to bind the server. The server will listen on this port for HTTP requests.

The second parameter, aServers, is an array of TSQLRestServerDB instances. Note that you’ll need one of these even if you aren’t using mORMot for its ORM functionality. I’ve already discussed this class in my article The ORM in mORMot, and once you know how to use both it and TSQLHttpServer, you have everything you need for Option #1 (see below).

For now, know that the TSQLRestServerDB, or more specifically its associated TSQLModel instance, determines the root for your URI endpoints, e.g.

http://localhost:8080/root/book/1

This field, which can be set using either the Root property or a constructor parameter, has a default value of “root”, and accepts any valid URI string.

Furthermore, you probably want to set the AccessControlAllowOrigin property to allow requests from other domains via cross-origin resource sharing (CORS):

HttpServer.AccessControlAllowOrigin := '*';

If your server doesn’t have a database, the TSQLRestServerFullMemory class is a viable replacement for TSQLRestServerDB.

Option #1: The REST ORM

Let’s take a moment to consider that the mORMot framework implements REST at an ORM level instead of an API level. The nice thing about this is that the REST implementation is not tied to any particular protocol. Unfortunately, in the case of mORMot, it also means that when you publish this ORM as a RESTful HTTP web API, the result is a very thin database wrapper (which might be the reason Synopse advises against exposing such an API to the public).

To publish your ORM, simply pass the TSQLRestServerDB instance you’re already using in the aServers array parameter of the TSQLHttpServer constructor. The API will expose CRUD endpoints for each TSQLRecord-derived class in the associated model, with URIs determined by the class name.

For example, if your REST server hosts TFruit objects, you can retrieve a list of them by issuing a GET request to a URL like the following:

http://localhost:8080/root/fruit

This will return a JSON array of objects, rendering only their IDs, e.g.

[{"ID":1},{"ID":2},{"ID":3}]

To include fields other than the ID in the response, specify the field names in the select query string parameter as a comma-delimited list, e.g.

http://localhost:8080/root/fruit?select=id,name

These fields will appear in the JSON response, e.g.

[{"ID":1,"Name":"Apple"},{"ID":2,"Name":"Banana"},{"ID":3,"Name":"Orange"}]

To filter the records, you can add conditions in the where query string parameter, e.g.

http://localhost:8080/root/fruit?where=name="Banana"

You can also sort the records by specifying field names in the sort query string parameter, e.g.

http://localhost:8080/root/fruit?sort=name

Note: Using the names of non-existent fields in the query string will result in a 404 error response.

An individual record can be retrieved by appending its ID to the URI, e.g.

http://localhost:8080/root/fruit/2

You can also submit POST, PUT and DELETE requests, which behave as you would expect from a RESTful API.

A property on the TSQLRestServerDB class, NoAJAXJSON, can be used to toggle between standard “expanded” JSON responses and mORMot’s own “not expanded” format, which more closely resembles an SQL result set (standard JSON is the default). One quirk I have found is that, in the case of empty result sets, the server will use the non-standard format regardless of this setting (judging by comments in the SynSQLite3 unit, this behaviour is intentional).

The obvious advantage of this approach is that you can have a fully functional, JSON-speaking RESTful API for only a few lines of code. On the other hand, the resulting API is not very open to customization, and there is no convenient place for business logic to live. I suppose you could still write validation logic in your TSQLRecord-derived domain classes, but that hardly seems fulfilling.

Option #2: Publishing a methods-based service

In the second approach, you create a subclass of TSQLRestServerDB and extend it with published methods. These methods become available as endpoints in your API and allow you to directly control the HTTP response while giving you access to things like the request path and headers.

When this approach is combined with the first, these methods can be invoked “on” a resource in your existing REST ORM (see further down).

Warning: By using the same TSQLRestServerDB instance you’re using for your ORM, you are by default using Option #1 as well. Consider carefully whether this is your intention.

To create a methods-based service, create a new class that inherits from the TSQLRestServerDB class, and declare a published method, e.g.

TMethodServer = class(TSQLRestServerDB)
published
procedure Eat(Ctxt: TSQLRestServerURIContext);
end;

The parameter list for your method must match the one above exactly.

The Ctxt parameter gives you access to information about the HTTP request, such as the HTTP verb (Ctxt.Method). At the same time, this object is also used to define information about the response. The Ctxt.Results method allows you to return an array of values, for example:

Ctxt.Results(['Yum!']);

You can call this method by issuing e.g. a GET request to a URL like this:

http://localhost:8080/root/eat

In this case, a JSON response is returned, e.g.

{"result":["Yum!"]}

When the method lives on a TSQLRestServerDB instance that also publishes a REST ORM, you can call the method in the context of a specific table, e.g.

http://localhost:8080/root/fruit/eat

You can also include an ID in the request path, e.g.

http://localhost:8080/root/fruit/5/eat

The table name (e.g. “Fruit”) and ID (e.g. 5) can then be obtained from the Ctxt parameter as Ctxt.Table.SQLTableName and Ctxt.TableID respectively.

For more information about methods-based services, refer to the Client-Server services via methods section in the mORMot design document.

This approach seems very much tailored to the extension of an existing REST ORM server. It allows a lot of versatility with regard to handling the request, which is most welcome, but the structure is not well organised (especially without the context of an ORM). Both in the URI schema and the class itself, methods basically live in a long, flat list.

Option #3: Publishing an interface-based service

Interface-based services allow RPC-like functionality that is closer to WCF services in the .NET world than to a real RESTful web API. When you register an interface and the type of a class that implements it, mORMot publishes all of its methods as HTTP endpoints. Parameters and return types are automatically converted from and to JSON.

Warning: Once again, if you register the service on the TSQLRestServerDB instance you’re using for your ORM, you’re also using Option #1.

To begin, define an interface for your service. Be sure to inherit from IInvokable so as to have run-time type information (RTTI), and do give your interface a GUID (the classic Delphi keystroke Ctrl + ⇧ Shift + G).

For example:

IGreetingService = interface(IInvokable)
['{CB6CBA42-7236-4E89-4755-2428DABF34FB}']
function Hello(Name: RawUTF8): RawUTF8;
end;

It’s a good idea to use mORMot’s RawUTF8 type instead of string for the sake of compatibility. Next, create a class that implements this interface, e.g.

TGreetingService = class(TInterfacedObject, IGreetingService)
protected
function Hello(Name: RawUTF8): RawUTF8;
end;
implementationfunction TGreetingService.Hello(Name: RawUTF8): RawUTF8;
var
Greeting: string;
begin
Greeting := 'Hello!';
if Name <> '' then
begin
Greeting := Format('Hello, %s!', [Name]);
end;
Result := Greeting;
end;

Now, register the service on your TSQLRestServerDB instance by calling the ServiceRegister method, passing in the type of the concrete class, and an array containing the TypeInfo of the service interface, e.g.

ServerDB.ServiceRegister(TGreetingService, [TypeInfo(IGreetingService)]);

The HTTP server exposes an endpoint for each of the service’s methods, at a URI determined that follows the pattern root/service/method, e.g.

http://localhost:8080/root/greetingservice/hello?name=Moto

The root is, as usual, determined by the Root property of your TSQLModel instance, while the service name is derived from the name of the interface (sans the I-prefix).

You can call this endpoint with GET or POST requests — each results in the function being called on the concrete class and the Result rendered as JSON. (In the case of a procedure, the response is an empty {“result”:[]}). For example:

{"result":["Hello, Moto!"]}

In fact, the same goes for PUT, DELETE and even PATCH or LOCK requests. Parameters are mapped from the query string, but mORMot also checks the request body for JSON. This means you can call the endpoint without a query string, e.g.

http://localhost:8080/root/greetingservice/hello

As long as you supply the arguments in the request body, the end result will be the same, e.g.

{
"name": "Moto"
}

Returning primitive types is OK, but if you want to include objects in the HTTP response, output parameters are the way to go. The mORMot framework has its own set of rules regarding this, and it’s worth going over the documentation to review them. There’s also an article I wrote on some of the framework’s JSON serialization capabilities, which you might find relevant.

Although interface-based services do not provide a Ctxt parameter like methods-based services do, you can still access low-level information about the HTTP request via the threadvar named ServiceContext (declared in the mORMot unit).

There are also two routing options available, REST and JSON RPC, which you choose by setting the ServicesRouting property of the TSQLRestServerDB class to either TSQLRestRoutingREST or TSQLRestRoutingJSON_RPC. According to the documentation, you can apply custom routing by inheriting your own class from TSQLRestServerURIContext. Both of the examples rely on internal members of mORMot classes, however — a luxury that isn’t available to third-party derivatives.

For more information about interface-based services, refer to the Client-Server services via interfaces section in the mORMot design document.

While not particularly RESTful, interface-based services seem like a good fit for other kinds of service-oriented architectures. They are cleaner to work with than methods-based services, and still allow a degree of flexibility.

Conclusion

Publishing a REST ORM is the easiest way to host a web API using mORMot, and produces the closest thing to a RESTful API that the framework provides out-of-the-box. On the flip side, this option also affords the developer the least amount of control over the end result.

At the other end of the spectrum, there are methods-based services, which, though they seem to exist primarily as a way to extend an existing REST ORM, offer lower-level access to the HTTP context. This approach, again, isn’t as clean as using interface-based services, which lets you define the contract for your API using plain Delphi interfaces.

There’s a lot here to play with, and it bears some experimentation to find the option that best suits your design.

Sample source code

I have created examples for each of the approaches discussed above in the following public repository on GitHub:

Used for this article

  • Delphi 10.3.1 Starter
  • mORMot 1.18

--

--

Stephan Bester

Software developer walking the edge between legacy systems and modern technology. I also make music: stephanbmusic.com