Aster Web Data
An OData API over the databases you already have, built from a YAML model, so a new field or a new screen needs no endpoint code.
You have data in PostgreSQL, MariaDB, SQLite or DuckDB, and a web application that needs it through an API. Aster Web Data reads a YAML description of your tables and serves them as an OData 4.01 API with filtering, sorting, paging, totals, related records and writes. There is no endpoint code to write. When a screen needs something new, the client sends a different query or you add a line to the model, and the API, its $metadata and its OpenAPI document follow. Aster UI builds its grids and forms from the same metadata.
Adding a field a screen needs
A customer list is in use, and the sales team now wants each customer's region on it, with a filter and a sort by region. The table already has the column. The change is one line in the customer's DataSource:
# shop/Customer.ds.yaml
dataSource:
ID: Customer
serverType: sql
tableName: customers
fields:
- {name: id, type: sequence, primaryKey: true}
- {name: name, type: text, length: 100, required: true}
- {name: email, type: text, length: 200}
- {name: region, type: text, length: 8} # the new field
Send the server SIGHUP and it reads the model again. Requests already running finish on the old model, so the change goes live without a restart or a failed request. The field is then in $metadata and the OpenAPI document:
</Record>
</Annotation>
</Property>
+ <Property Name="region" Type="Edm.String" MaxLength="8">
+ <Annotation Term="AsterData.Collation">
+ <Record>
+ <PropertyValue Property="Kind" String="locale"/>
+ <PropertyValue Property="Name" String="en_US.UTF-8"/>
+ </Record>
+ </Annotation>
+ </Property>
<NavigationProperty Name="invoices" Type="Collection(Shop.Invoice)" Partner="customer"/>
</EntityType>
<EntityType Name="Invoice">
A client can select, filter and sort by it at once:
| select | name,region |
|---|---|
| filter | region ne 'EU' |
| orderby | region,name |
| top | 5 |
As a URL
GET /shop/Customer?$select=name,region&$filter=region ne 'EU'&$orderby=region,name&$top=5A relation or a rule is the same kind of edit; Change the model shows what each one changes. Before a change goes live, aster-webdata-server check compares each DataSource with its table. Had the column been missing, it would have said so:
$ aster-webdata-server check --config server.yaml
shop.Customer: shop.public.customers
shop.Invoice: shop.public.invoices
shop.InvoiceLine: shop.public.invoice_lines
shop.Product: shop.public.products
databases.shop: 1 mismatch(es)
table `public.customers` has no column `region`
The SQL a request sends
A screen that lists EU customers, each with their three latest unpaid invoices, each invoice's lines and each line's product name, is one request:
| filter | region eq 'EU' |
|---|---|
| orderby | name |
| count | true |
| top | 5 |
| select | id,name |
| expand | invoices |
| filter | paid eq false |
| orderby | issued desc |
| top | 3 |
| select | id,issued,amount |
| expand | lines |
| select | quantity,unitPrice |
| expand | product |
| select | name |
As a URL
GET /shop/Customer?$filter=region eq 'EU'&$orderby=name&$count=true&$top=5&$select=id,name&$expand=invoices($filter=paid eq false;$orderby=issued desc;$top=3;$select=id,issued,amount;$expand=lines($select=quantity,unitPrice;$expand=product($select=name)))On PostgreSQL it becomes two statements: one SELECT that reads the customers, invoices, lines and products together, and one COUNT for the total. The values are bound as parameters, and the paging and sorting run in the database. The nested levels are read in that one statement wherever each foreign key has an index that leads with its column.
2 statements on PostgreSQL
Move the invoices, lines and products to MariaDB and the client sends the same request. Each database receives the statements for its own tables, and Aster joins the rows:
3 statements on MariaDB, 2 statements on PostgreSQL
What runs on your database shows the full SQL on each database, the indexes it relies on and the privileges it needs.
For the database owner and operators
With explain turned on, the x-aster-explain response header says for each part of a request whether it ran in the database or in Aster, and why when it ran in Aster. A sort or join that Aster would have to run in its own memory over more than limits.residual_rows rows (50,000 by default) is refused with an error that names the limit. Limits lists each limit and its default.
Each read is counted by the shape of its plan, with the literal values removed, so a dashboard shows which kinds of request are slow without recording any value. The server also exports OpenTelemetry traces, metrics and logs; Observability lists them.
Get started
Get started serves a SQLite database on your machine in about five minutes and sends your first requests. If you own the database, start with What runs on your database. To check your requirements first, use Is it a fit?. Why Aster Web Data makes the full case, including when hand-written endpoints are simpler.