aster.Web Data / Docs Aster UI ↗

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:

GET /shop/Customer
selectname,region
filterregion ne 'EU'
orderbyregion,name
top5
As a URLGET /shop/Customer?$select=name,region&$filter=region ne 'EU'&$orderby=region,name&$top=5

A 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:

GET /shop/Customer
filterregion eq 'EU'
orderbyname
counttrue
top5
selectid,name
expandinvoices
filterpaid eq false
orderbyissued desc
top3
selectid,issued,amount
expandlines
selectquantity,unitPrice
expandproduct
selectname
As a URLGET /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.