One language.
One binary.
The whole web.
SpaceFive is a batteries-included web framework written in pure Five. HTTP, WebSocket, PostgreSQL, bcrypt, AES, i18n — all native runtime calls. No Node. No FFI. No ORM server. One static binary talking straight to Postgres, and three real apps running on it right now.
Three apps, same framework,
same aesthetic.
Everything you click is served by a Five handler: hypermedia partial updates, server-rendered forms, CSV export, photo uploads, realtime events.
Asset Manager
Gallery, list and detail views with search, status and category filters, multi-currency values and one-click CSV export.
People / HR
Directory of employees with positions, departments, salaries and photos — built from the very same shell, chips and cards.
Sales / ERP
Customers, products and sales orders with credit control, stock, shipping, invoicing, payments and receivables aging. A dashboard, printable documents and reports.
You already know this language.
Only the screen changed.
If you wrote Clipper, FoxPro or Harbour, a SpaceFive page reads like a dialog you have written a hundred times. Every example below is live: the widget on the right was rendered by the code on the left, for this very request.
MENU hMenu MENUBAR MENUITEM "Assets" ACTION "/assets" MENUITEM "People" ACTION "/people" MENUITEM "+ New asset" ACTION "/assets/new" GOLD ENDMENU cHtml := MENU_HTML( hMenu )
hStat := DB_ONE( cAssetStats, {} ) DASHBOARD hDash STAT "Total assets" VALUE hStat[ "n" ] GOLD STAT "Total value" VALUE Money( hStat[ "total" ], "USD" ) GOLD STAT "Active" VALUE hStat[ "active" ] OK STAT "In maintenance" VALUE hStat[ "maint" ] WARN ENDDASHBOARD cHtml := DASH_HTML( hDash )
aRows := DB_QUERY( cFirstEmployees, {} ) TABLE hList OF aRows LINK "/people/:id" COLUMN "name" HEADER "Employee" THUMB "photo" SUB "emp_no" COLUMN "position" HEADER "Position" COLUMN "status" HEADER "Status" BADGE COLUMN "salary" HEADER "Salary" MONEY CURRENCY "currency" RIGHT ENDTABLE cHtml := TABLE_HTML( hList )
GALLERY hCards OF aRows LINK "/people/:id" CARD PHOTO "photo" INITIALS "name" CARD BADGE "status" CARD TAG "position" CARD TITLE "name" CARD META "department" CARD VALUE "salary" CURRENCY "currency" ENDGALLERY cHtml := GALLERY_HTML( hCards )
FORM hForm ACTION "/people" OF hReq GET "emp_no" LABEL "Employee no." VALUE "E-2001" GET "name" LABEL "Name" COMBO "status" LABEL "Status" VALUE "active" ITEMS aStatuses GET "salary" LABEL "Salary (annual)" VALUE 90000 BUTTON "Save" GOLD LINK "Cancel" ACTION "/" ENDFORM cHtml := FORM_HTML( hForm )
From the DOS screen to the browser
| You wrote | You write now | What it does |
|---|---|---|
| @ 10, 5 SAY "Name" GET cName | GET "name" LABEL "Name" VALUE cName | A labelled text field in a form. |
| MENU oMenu … MENUITEM … ENDMENU | MENU hMenu MENUBAR … MENUITEM … ENDMENU | Header buttons, sidebar chips, tabs — one syntax. |
| TBrowse / dbEdit() | TABLE hList OF aRows … COLUMN … ENDTABLE | A clickable list with thumbnails, badges and money columns. |
| USE customers INDEX ON name | aRows := DB_QUERY( cSql, { cName } ) | PostgreSQL, parameter-bound, no ORM server. |
| SET COLOR TO W+/B | public/skins/<skin>/skin.css | The look is a skin: CSS and small HTML templates a designer can edit. |
| ACTIVATE WINDOW oWnd | FV_SERVE( nPort, "public", {| hReq | APP_DISPATCH( hReq ) } ) | The whole web server, in one line of main.prg. |
Native runtime, no glue.
Every layer is a Five runtime library call. There is nothing to install besides PostgreSQL.
FV_SERVEHTTP, fully owned
Headers and cookies in, status and set-cookie out. Each request runs on its own VM thread.
WsServe · WsSendRealtime
Zero-dependency WebSocket server. The feed on this page is listening to it.
PG_OPEN · PG_QUERYPostgreSQL, direct
Parameter-bound queries over pgx. Idempotent migrations run at boot.
PWHASH · ENCRYPT · SECEQSecure by default
bcrypt passwords, AES-256-GCM sessions, constant-time compares, CSRF double-submit.
T()i18n built in
Catalog-driven translations with a per-request locale. This page is served in six languages from locales/*.json.
hx.jsHypermedia UI
A 54-line HTMX-style shim: partial swaps, CSRF headers, no build step, no framework.
Request is a hash. Response is a hash.
Handler is a block.
This is the real route table and a real handler from the running app.
// asset manager — gallery, list, detail, CRUD, export ROUTE( "GET", "/assets", {| hReq | ASSET_DASHBOARD( hReq ) } ) ROUTE( "GET", "/assets/cards", {| hReq | ASSET_CARDS( hReq ) } ) ROUTE( "POST", "/assets", {| hReq | ASSET_CREATE( hReq ) } ) ROUTE( "GET", "/assets/export.csv", {| hReq | ASSET_EXPORT( hReq ) } ) // main.prg — the whole server FV_SERVE( hCfg[ "port" ], "public", {| hReq | APP_DISPATCH( hReq ) } ) // a handler: query → hash → JSON. The result lives in a variable; RETURN returns it. FUNCTION ASSET_LIST( hReq ) LOCAL aRows := Rows( hReq, 1000, 0 ) LOCAL hRes := RES_JSON( { "ok" => .T., "assets" => aRows, "count" => Len( aRows ) } ) RETURN hRes






