DEV Community

Cover image for From an empty folder to a running REST API in 5 minutes. In Delphi. In 2026.
David Lastrucci
David Lastrucci

Posted on

From an empty folder to a running REST API in 5 minutes. In Delphi. In 2026.

Every now and then someone asks me: "Delphi? Is that still a thing?"

It is. It runs in business software, hospitals, factories, in products that have been paying the bills for thirty years. And in 2026 it can do something that many so-called modern stacks still turn into an afternoon of setup: go from an empty folder to a REST server with a database, full CRUD and ready-made configuration, without writing a single line of plumbing code by hand.

Let's see how, step by step.

1. One wizard, five pages

Trysil is an open source ORM for Delphi that I have been maintaining for years. Alongside the library there is the Trysil Expert, an add-in for the Delphi IDE.

Trysil > Create new Trysil API REST opens a wizard:

  • project name and folder;
  • base URI and port;
  • three checkboxes: Multi-tenant, Authorization (login and JWT tokens, RS256-signed too), Log;
  • the database, one of seven: SQLite, PostgreSQL, SQL Server, Firebird, InterBase, MariaDB, Oracle;
  • the name of the Windows service.

Trysil Expert wizard, API REST page: base URI, port and the Multi-tenant, Authorization, RS256 and Log options

Click Finish: the Expert downloads the template, strips the code of the features you did not pick and opens the project in the IDE. No {$IFDEF} scattered across the sources: only what you asked for is there.

The same executable runs as a Windows service, as a tray application (with /debug) and as a console application on Linux.

2. Design the model

Trysil > Design entity model: entities on the left, columns on the right. I add Customer with CompanyName and City. ID and VersionID (for optimistic locking) are already there and cannot be touched.

Trysil Expert entity designer: entities on the left, the columns of the selected entity on the right

The model is saved as one JSON file per entity next to the project, so it goes under version control together with the code.

3. Generate SQL and code

Trysil > Generate DDL script writes the CREATE TABLE for the chosen dialect. If the database already exists, the ALTER checkbox compares the model with a reference database and writes only what is missing: no DROP, no surprises.

Trysil > Generate entity model writes the entity units:

Trysil Expert Generate entity model dialog with the option to generate and register the API REST controllers

This is the Customer unit:

[TTable('Customers')]
[TSequence('CustomersID')]
TCustomer = class
strict private
  [TPrimaryKey]
  [TColumn('ID')]
  FID: TTPrimaryKey;

  [TRequired]
  [TMaxLength(100)]
  [TColumn('CompanyName')]
  FCompanyName: String;

  [TMaxLength(50)]
  [TColumn('City')]
  FCity: String;

  [TVersionColumn]
  [TColumn('VersionID')]
  FVersionID: TTVersion;
public
  property ID: TTPrimaryKey read FID;
  property CompanyName: String read FCompanyName write FCompanyName;
  property City: String read FCity write FCity;
  property VersionID: TTVersion read FVersionID;
end;
Enter fullscreen mode Exit fullscreen mode

A plain class with attributes. No base class, no generated code you must not touch. Trysil enforces the validations ([TRequired], [TMaxLength]) before every write.

With the Generate & register API REST controllers checkbox the Expert also writes the controller and registers it in the server. Here it is, in full:

[TUri('/customer')]
TCustomerController = class(TReadWriteController<TCustomer>)
end;
Enter fullscreen mode Exit fullscreen mode

4. F9

Build, run, and from a terminal:

curl -X POST http://localhost:4450/api/customer \
  -H "Content-Type: application/json" \
  -d '{"companyName":"Acme","city":"Florence"}'

curl http://localhost:4450/api/customer
Enter fullscreen mode Exit fullscreen mode
[{"id":1,"companyName":"Acme","city":"Florence","versionID":1}]
Enter fullscreen mode Exit fullscreen mode

Those three lines of controller expose:

Method Route What it does
GET /customer all records
GET /customer/{id} one record
POST /customer/select filtering, sorting, paging
GET /customer/metadata entity metadata
POST /customer insert with validation
PUT /customer update with optimistic locking
DELETE /customer/{id}/{version} delete

If two clients edit the same record, the second one gets an error instead of overwriting the first: that is what VersionID is for.

5. What's underneath

The generated code is not a black box: it is Trysil, and you can use it without the Expert and without HTTP.

LFilter := LContext.CreateFilterBuilder<TCustomer>()
  .Where('City').Equal('Florence')
  .OrderByAsc('CompanyName')
  .Limit(20)
  .Build;
LContext.Select<TCustomer>(LCustomers, LFilter);
Enter fullscreen mode Exit fullscreen mode

And the rest, when you need it:

  • soft delete and audit trail with one attribute ([TDeletedAt], [TCreatedBy], ...);
  • declarative JOINs with attributes, for read-only entities;
  • RawSelect for GROUP BY, UNION and subqueries mapped to DTOs;
  • multi-tenant: one database per tenant, picked from the Host header;
  • JWT HS256 and RS256, with keys handled as objects;
  • every package also compiles for Linux64.

Why I built it

Delphi has a smaller open source ecosystem than other languages, and people working with it often rewrite the same things from scratch: mapping, CRUD, endpoints, configuration. Trysil exists to take that work away, and the Expert to take away what is left.

If you use Delphi, give it a try and tell me what's missing. If you don't, I hope I at least changed your mind about how "old" it is.

A star on GitHub helps the project get found.

Top comments (0)