MALDA™ Reference Manual

The AI-First Programming Language - Version 1.0.11

29. Full-Stack Development with MALDA

This chapter shows how to use MALDA as a complete full-stack platform. The goal is to connect concepts already documented in dedicated chapters into one end-to-end architecture. For runnable starters, use malda new webapi, malda new fullstack, malda new game --fullstack (canvas client + score API), and the examples under Examples/Web/ (for example rest_api_server.malda and ui_controls_showcase_minimal.malda).

29.1 What "full-stack" means in MALDA

In MALDA, a full-stack app typically combines:

These map directly to existing manual chapters: Web UI Generation, REST API Server, REST Web Client, Database Support, and Agent Orchestration.

29.1.1 Runtime boundary: MALDA vs JavaScript

So the UI layer can be either server-rendered by MALDA, client-rendered in JavaScript, or a hybrid of both.

29.1.2 Delivery models

Both MALDA-native and hybrid are full-stack because they include UI + API + data layers end to end.

29.1.3 Single-source target decorators

MALDA now supports target decorators so one .malda source can contain server-only, client-only, and shared code in a controlled way:

Existing route decorators (@GET, @POST, @PUT, @DELETE, @PATCH, @OPTIONS, @PAGE, @AIPAGE, @ACTION, @COMPONENT, @LIVE) remain runtime/web decorators and continue to imply server participation.

Validation rules: combining @client() with route decorators is invalid, and @shared() should only use cross-target-safe helpers. Target-specific operations such as direct filesystem/database/server APIs or browser DOM/local storage helpers should stay in explicit server/client declarations.

Desktop IDE workflow: for single-source full-stack files, the Desktop IDE can render one physical .malda as multiple virtual tabs when the source opts in with explicit // @malda-section Name separators. Saving still writes one physical file, and regular include files continue to open/edit as normal physical documents. F5 starts dual debug: the interpreter on the host partition and Web Preview on the client (see 2.6.6). Ctrl+F5 still offers the Server / Client preview / Full stack run dialog.

29.2 Example scenario: Simple items app

A typical sample app supports:

Minimal data model:

{
  "id": 101,
  "ownerName": "Alice",
  "email": "alice@example.com",
  "title": "Cannot login",
  "description": "Password reset link expired",
  "status": "open",
  "priority": "high",
  "createdAt": "2026-02-14T09:15:00Z"
}

29.3 Recommended project structure

/app
  /api
    items-api.malda
  /ui
    dashboard.malda
  /shared
    item-model.malda
  /agents
    triage-agent.malda

Keep business logic in reusable functions/classes, then expose it through both API handlers and UI handlers. This prevents duplicated logic between front-end and back-end flows.

When using single-source target decorators, you can also choose a flatter structure (for example one app.malda entry) while still separating concerns by marking declarations with @server(), @client(), and @shared().

29.4 API layer (Item CRUD + workflow)

Define REST routes for item operations:

function ok(data) {
    return {"status": 200, "data": data};
}

@GET("/api/items")
function getItems(status) {
    // status can come from query string ?status=open
    var items = dbQuery("SELECT * FROM Items WHERE (@status IS NULL OR status = @status)", {"status": status});
    return ok(items);
}

@POST("/api/items")
function createItem(body) {
    var now = dateNow();
    var insert = dbExecute(
        "INSERT INTO Items (ownerName, email, title, description, status, priority, createdAt) VALUES (@ownerName, @email, @title, @description, 'open', 'normal', @createdAt)",
        {
            "ownerName": body.ownerName,
            "email": body.email,
            "title": body.title,
            "description": body.description,
            "createdAt": now
        }
    );
    return {"status": 201, "data": {"id": insert.lastInsertId, "createdAt": now}};
}

@PATCH("/api/items/{id}/status")
function updateItemStatus(id, body) {
    dbExecute("UPDATE Items SET status = @status WHERE id = @id", {"id": id, "status": body.status});
    return ok({"id": id, "status": body.status});
}

Production recommendation: add validation, auth middleware, and consistent error payloads. See 27. REST API Server.

29.5 UI layer (what technology to use)

You have two common approaches:

ModelWhen to choose
MALDA-native full stackFast delivery, fewer moving parts, server-driven pages are enough
Hybrid full stackRich SPA UX, large frontend teams, strong JS ecosystem requirements

Framework options for the browser UI include vanilla JavaScript, React, Vue, Angular, Svelte, Next.js (frontend/API-consumer mode), Nuxt, Solid, and lightweight options like HTMX or Alpine.js. MALDA is backend-agnostic here as long as the frontend can call HTTP APIs.

29.5.0 MALDA-native component pattern

For MALDA-native full stack, this provides a pragmatic server-component baseline:

How to choose transport: use fragment HTTP as the default for user-initiated actions (forms/buttons). Add LIVE/SSE when the UI must update without a user action (notifications, counters, queues).

Note: LIVE endpoints support channel subscriptions (for example /items/live?channel=items) and componentLiveEmit("items", ...) publishes to that channel only. The malda new fullstack scaffold also ships a sample component at /components/TicketBoard for a quick local demo.

29.5.0.1 Small items example

component ItemBoard() {
    var items = componentStateGet("board", "items", []);
    return "<h1>Open: " + items.length + "</h1>";
}

@ACTION("/items/add")
function addItem(body) {
    var items = componentStateGet("board", "items", []);
    items.append({"title": body.title, "status": "open"});
    componentStateSet("board", "items", items);
    componentLiveEmit("items", {"count": items.length}, "item-added");
    return componentFragment("item-list", "<ul><li>" + body.title + "</li></ul>");
}

@LIVE("/items/live")
function itemsLive() {
    return {"sse": true};
}

29.5.1 MALDA server-side page handlers

@PAGE("/items")
function itemsPage() {
    return "<html><body><h1>Items</h1></body></html>";
}

29.5.2 Browser-side JavaScript calling MALDA API

async function loadItems() {
  const r = await fetch("/api/items?status=open");
  const json = await r.json();
  return json.data;
}

async function closeItem(id) {
  await fetch(`/api/items/${id}/status`, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ status: "closed" })
  });
}

This separation keeps UI concerns focused on rendering and interaction, while MALDA remains the source of truth for business logic and data access.

29.5.3 Migration path from @PAGE to components

  1. Keep existing @PAGE endpoints stable (no breaking migration required).
  2. Extract view logic into component functions.
  3. Add @ACTION handlers for interactive updates and return componentFragment(...).
  4. Add @LIVE endpoint + componentLiveEmit(...) for push notifications.
  5. Adopt this incrementally page-by-page rather than rewriting the entire UI.

Incremental migration tip: start with one form workflow (for example "create item"), validate that fragment updates and state handling are clear to the team, then expand to other pages.

29.5.4 End-to-end UI example

For a complete list/detail flow with CRUD-style controls, start from Examples/Web/ui_controls_showcase_minimal.malda, Examples/Web/ui_form_workflow.malda, and Examples/Web/rest_api_server.malda, then compose pages with ui.crudControls, @PAGE, and @POST. Key patterns:

function crudPage(req, schema, pageTitle, lookups) {
    var queryParams = req.query == null ? {} : req.query;
    var sessionId = ui.sessionId(req, schema.sessionDefault);
    var controlsHtml = ui.crudControls(schema, sessionId, queryParams, lookups);
    return controlsHtml;
}

@PAGE("/web/items")
function itemsPage(req) { return crudPage(req, itemSchema, "Items", {}); }
@POST("/web/items/add")
function addItem(req, body, res) {
    var errors = [];
    if (body.title == null || body.title == "") errors.append("Title required");
    if (errors.length > 0) {
        return {"status": 400, "body": "<ul class='error-list'><li>" + errors[0] + "</li></ul>"};
    }
    var id = dbExecute("INSERT INTO items (...) VALUES (...)", body).lastInsertId;
    return RedirectTo("/web/items?added=" + id);
}

For full schema structure and template paths, see 24. Web UI Server Components (24.3.1.3 CRUD schema reference).

29.6 Database integration pattern

A practical approach is to use:

For provider setup and connection details, see 16. Database Support.

29.7 AI triage workflow (optional but powerful)

Use an agent to classify incoming item urgency and propose next action:

var client = new OpenRouterClient();
var triageAgent = new Agent(
    "TriageAgent",
    "item-triage",
    "Classify items by priority and suggest next action.",
    client
);

function triageItem(item) {
    var prompt = "Classify this item and return JSON with priority and queue: " + toJSON(item);
    var result = triageAgent.think(prompt);
    return parseJSON(result.content);
}

Common integration pattern: call triage right after item creation, then persist priority and queue fields before returning the response.

29.8 End-to-end request flow

  1. User submits item form in Web UI
  2. UI sends POST /api/items
  3. API validates and persists initial item
  4. API optionally calls triage agent
  5. API updates priority/queue and returns JSON
  6. UI refreshes list via GET /api/items

29.9 Configuration and secrets

Keep credentials and environment-specific values outside source code:

Relevant built-ins are documented in 13. Built-in Functions.

29.10 Practical checklist for new full-stack apps

29.10.1 Testing and troubleshooting (Web UI + API)

When integrating Web UI with REST APIs, use these practical checks:

For Web UI-specific debugging (tree divergence, event handlers, template cache), see 24. Web UI Server Components (24.3.15 Common pitfalls and debugging, 24.6 Testing and Troubleshooting).

See Also